diff --git a/build/.moduleignore b/build/.moduleignore index 04ba4fd8f50..2aa99ddc295 100644 --- a/build/.moduleignore +++ b/build/.moduleignore @@ -21,16 +21,16 @@ fsevents/test/** @vscode/sqlite3/src/** !@vscode/sqlite3/build/Release/*.node +@vscode/windows-mutex/binding.gyp +@vscode/windows-mutex/build/** +@vscode/windows-mutex/src/** +!@vscode/windows-mutex/**/*.node + @vscode/windows-registry/binding.gyp @vscode/windows-registry/src/** @vscode/windows-registry/build/** !@vscode/windows-registry/build/Release/*.node -windows-mutex/binding.gyp -windows-mutex/build/** -windows-mutex/src/** -!windows-mutex/**/*.node - native-keymap/binding.gyp native-keymap/build/** native-keymap/src/** diff --git a/build/azure-pipelines/product-build-pr.yml b/build/azure-pipelines/product-build-pr.yml index 1d7b379aa03..789996060ec 100644 --- a/build/azure-pipelines/product-build-pr.yml +++ b/build/azure-pipelines/product-build-pr.yml @@ -96,7 +96,7 @@ jobs: - job: Windowsx64UnitTests displayName: Windows (Unit Tests) - pool: 1es-oss-windows-2022-x64 + pool: 1es-oss-windows-2019-x64 timeoutInMinutes: 30 variables: VSCODE_ARCH: x64 @@ -112,7 +112,7 @@ jobs: - job: Windowsx64IntegrationTests displayName: Windows (Integration Tests) - pool: 1es-oss-windows-2022-x64 + pool: 1es-oss-windows-2019-x64 timeoutInMinutes: 30 variables: VSCODE_ARCH: x64 @@ -128,7 +128,7 @@ jobs: # - job: Windowsx64SmokeTests # displayName: Windows (Smoke Tests) - # pool: 1es-oss-windows-2022-x64 + # pool: 1es-oss-windows-2019-x64 # timeoutInMinutes: 30 # variables: # VSCODE_ARCH: x64 @@ -153,7 +153,7 @@ jobs: - job: Windowsx64MaintainNodeModulesCache displayName: Windows (Maintain node_modules cache) - pool: 1es-oss-windows-2022-x64 + pool: 1es-oss-windows-2019-x64 timeoutInMinutes: 30 variables: VSCODE_ARCH: x64 diff --git a/build/builtin/index.html b/build/builtin/index.html index 13c84e0375c..dc2a7ca6d0d 100644 --- a/build/builtin/index.html +++ b/build/builtin/index.html @@ -5,7 +5,6 @@ - Manage Built-in Extensions @@ -43,4 +42,4 @@
- \ No newline at end of file + diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 6c59db8c15e..a783a0758b1 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -2360,7 +2360,12 @@ export class Repository implements Disposable { private updateBranchProtectionMatcher(): void { const scopedConfig = workspace.getConfiguration('git', Uri.file(this.repository.root)); - const branchProtectionGlobs = scopedConfig.get('branchProtection')!.map(bp => bp.trim()).filter(bp => bp !== ''); + const branchProtectionConfig = scopedConfig.get('branchProtection') ?? []; + const branchProtectionValues = Array.isArray(branchProtectionConfig) ? branchProtectionConfig : [branchProtectionConfig]; + + const branchProtectionGlobs = branchProtectionValues + .map(bp => typeof bp === 'string' ? bp.trim() : '') + .filter(bp => bp !== ''); if (branchProtectionGlobs.length === 0) { this.isBranchProtectedMatcher = undefined; diff --git a/extensions/github/package.json b/extensions/github/package.json index 68cbd72cbfa..f9e5cf40ef4 100644 --- a/extensions/github/package.json +++ b/extensions/github/package.json @@ -27,7 +27,6 @@ }, "enabledApiProposals": [ "contribShareMenu", - "contribEditorLineNumberMenu", "contribEditSessions" ], "contributes": { diff --git a/extensions/github/src/commands.ts b/extensions/github/src/commands.ts index 8a653e523c4..b43f7b1cf11 100644 --- a/extensions/github/src/commands.ts +++ b/extensions/github/src/commands.ts @@ -17,7 +17,7 @@ async function copyVscodeDevLink(gitAPI: GitAPI, useSelection: boolean, context: try { const permalink = getLink(gitAPI, useSelection, getVscodeDevHost(), 'headlink', context, includeRange); if (permalink) { - return vscode.env.clipboard.writeText(permalink); + return vscode.env.clipboard.writeText(encodeURI(permalink)); } } catch (err) { vscode.window.showErrorMessage(err.message); diff --git a/extensions/typescript-language-features/media/nodejsWalkthroughIcon.png b/extensions/typescript-language-features/media/nodejsWalkthroughIcon.png deleted file mode 100644 index 10a8e5b822b..00000000000 Binary files a/extensions/typescript-language-features/media/nodejsWalkthroughIcon.png and /dev/null differ diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 30ccbd8558f..9fef982d600 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -1562,59 +1562,6 @@ } ] } - ], - "walkthroughs": [ - { - "id": "nodejsWelcome", - "title": "%walkthroughs.nodejsWelcome.title%", - "icon": "media/nodejsWalkthroughIcon.png", - "description": "%walkthroughs.nodejsWelcome.description%", - "when": "false", - "steps": [ - { - "id": "walkthroughs.nodejsWelcome.downloadNode.forMacOrWindows", - "title": "%walkthroughs.nodejsWelcome.downloadNode.forMacOrWindows.title%", - "description": "%walkthroughs.nodejsWelcome.downloadNode.forMacOrWindows.description%", - "media": { - "svg": "resources/walkthroughs/install-node-js.svg" - }, - "when": "isWindows || isMac" - }, - { - "id": "walkthroughs.nodejsWelcome.downloadNode.forLinux", - "title": "%walkthroughs.nodejsWelcome.downloadNode.forLinux.title%", - "description": "%walkthroughs.nodejsWelcome.downloadNode.forLinux.description%", - "media": { - "svg": "resources/walkthroughs/install-node-js.svg" - }, - "when": "isLinux" - }, - { - "id": "walkthroughs.nodejsWelcome.makeJsFile", - "title": "%walkthroughs.nodejsWelcome.makeJsFile.title%", - "description": "%walkthroughs.nodejsWelcome.makeJsFile.description%", - "media": { - "svg": "resources/walkthroughs/create-a-js-file.svg" - } - }, - { - "id": "walkthroughs.nodejsWelcome.debugJsFile", - "title": "%walkthroughs.nodejsWelcome.debugJsFile.title%", - "description": "%walkthroughs.nodejsWelcome.debugJsFile.description%", - "media": { - "svg": "resources/walkthroughs/debug-and-run.svg" - } - }, - { - "id": "walkthroughs.nodejsWelcome.learnMoreAboutJs", - "title": "%walkthroughs.nodejsWelcome.learnMoreAboutJs.title%", - "description": "%walkthroughs.nodejsWelcome.learnMoreAboutJs.description%", - "media": { - "svg": "resources/walkthroughs/learn-more.svg" - } - } - ] - } ] }, "repository": { diff --git a/extensions/typescript-language-features/src/experimentationService.ts b/extensions/typescript-language-features/src/experimentationService.ts index 86d1fd42b55..5dc458277d4 100644 --- a/extensions/typescript-language-features/src/experimentationService.ts +++ b/extensions/typescript-language-features/src/experimentationService.ts @@ -18,7 +18,7 @@ export class ExperimentationService { constructor(telemetryReporter: IExperimentationTelemetryReporter, id: string, version: string, globalState: vscode.Memento) { this._telemetryReporter = telemetryReporter; - this._experimentationServicePromise = createExperimentationService(this._telemetryReporter, id, version, globalState); + this._experimentationServicePromise = createTasExperimentationService(this._telemetryReporter, id, version, globalState); } public async getTreatmentVariable(name: K, defaultValue: ExperimentTypes[K]): Promise { @@ -32,7 +32,7 @@ export class ExperimentationService { } } -export async function createExperimentationService( +export async function createTasExperimentationService( reporter: IExperimentationTelemetryReporter, id: string, version: string, diff --git a/extensions/typescript-language-features/src/extension.ts b/extensions/typescript-language-features/src/extension.ts index c92617f80a2..22fdd25bb71 100644 --- a/extensions/typescript-language-features/src/extension.ts +++ b/extensions/typescript-language-features/src/extension.ts @@ -17,7 +17,6 @@ import { NodeLogDirectoryProvider } from './tsServer/logDirectoryProvider.electr import { ElectronServiceProcessFactory } from './tsServer/serverProcess.electron'; import { DiskTypeScriptVersionProvider } from './tsServer/versionProvider.electron'; import { ActiveJsTsEditorTracker } from './ui/activeJsTsEditorTracker'; -import { JsWalkthroughState, registerJsNodeWalkthrough } from './ui/jsNodeWalkthrough.electron'; import { ElectronServiceConfigurationProvider } from './configuration/configuration.electron'; import { onCaseInsensitiveFileSystem } from './utils/fs.electron'; import { Logger } from './logging/logger'; @@ -43,9 +42,6 @@ export function activate( const activeJsTsEditorTracker = new ActiveJsTsEditorTracker(); context.subscriptions.push(activeJsTsEditorTracker); - const jsWalkthroughState = new JsWalkthroughState(); - context.subscriptions.push(jsWalkthroughState); - let experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined; const packageInfo = getPackageInfo(context); if (packageInfo) { @@ -77,7 +73,6 @@ export function activate( }); registerBaseCommands(commandManager, lazyClientHost, pluginManager, activeJsTsEditorTracker); - registerJsNodeWalkthrough(commandManager, jsWalkthroughState); import('./task/taskProvider').then(module => { context.subscriptions.push(module.register(lazyClientHost.map(x => x.serviceClient))); diff --git a/extensions/typescript-language-features/src/ui/jsNodeWalkthrough.electron.ts b/extensions/typescript-language-features/src/ui/jsNodeWalkthrough.electron.ts deleted file mode 100644 index 1c9ceeb87ff..00000000000 --- a/extensions/typescript-language-features/src/ui/jsNodeWalkthrough.electron.ts +++ /dev/null @@ -1,198 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as cp from 'child_process'; -import * as vscode from 'vscode'; - -import { CommandManager } from '../commands/commandManager'; -import { Disposable } from '../utils/dispose'; - - -export async function nodeWasResolvable(): Promise { - let execStr: string; - switch (process.platform) { - case 'win32': - execStr = 'where node'; - break; - case 'aix': - case 'cygwin': - case 'darwin': - case 'freebsd': - case 'haiku': - case 'linux': - case 'netbsd': - case 'openbsd': - case 'sunos': - execStr = 'which node'; - break; - default: - return false; - } - - return new Promise(resolve => { - cp.exec(execStr, { windowsHide: true }, err => { - resolve(!err); - }); - }); -} - -export class JsWalkthroughState extends Disposable { - exampleJsDocument: vscode.TextDocument | undefined = undefined; - - override dispose() { - this.exampleJsDocument = undefined; - } -} - -export class CreateNewJSFileCommand { - public static readonly id = 'javascript-walkthrough.commands.createJsFile'; - public readonly id = CreateNewJSFileCommand.id; - - constructor( - private readonly walkthroughState: JsWalkthroughState - ) { } - - public execute() { - createNewJSFile(this.walkthroughState); - } -} - -export class DebugJsFileCommand { - public static readonly id = 'javascript-walkthrough.commands.debugJsFile'; - public readonly id = DebugJsFileCommand.id; - - constructor( - private readonly walkthroughState: JsWalkthroughState - ) { } - - public execute() { - debugJsFile(this.walkthroughState); - } -} - -export class NodeInstallationFoundCommand { - public static readonly id = 'javascript-walkthrough.commands.nodeInstallationFound'; - public readonly id = NodeInstallationFoundCommand.id; - public execute() { } -} - -async function createNewJSFile(walkthroughState: JsWalkthroughState) { - const newFile = await vscode.workspace.openTextDocument({ - language: 'javascript', - content: `// Write a message to the console.\nconsole.log('hello world!');\n`, - }); - walkthroughState.exampleJsDocument = newFile; - return vscode.window.showTextDocument(newFile, vscode.ViewColumn.Beside); -} - -async function debugJsFile(walkthroughState: JsWalkthroughState) { - const hasNode = await nodeWasResolvable(); - if (!hasNode) { - const reloadResponse = vscode.l10n.t("Reload VS Code"); - const debugAnywayResponse = vscode.l10n.t("Try Debugging Anyway"); - const dismissResponse = vscode.l10n.t("Dismiss"); - const response = await vscode.window.showErrorMessage( - // The message - vscode.l10n.t("We couldn\'t find Node.js on this computer. If you just installed it, you might need to reload VS Code."), - // The options - reloadResponse, - debugAnywayResponse, - dismissResponse, - ); - - if (response === undefined || response === dismissResponse) { - return; - } - if (response === reloadResponse) { - vscode.commands.executeCommand('workbench.action.reloadWindow'); - return; - } - } - tryDebugRelevantDocument(walkthroughState.exampleJsDocument, 'javascript', ['.mjs', '.js'], () => createNewJSFile(walkthroughState)); -} - -type DocSearchResult = - | { kind: 'visible'; editor: vscode.TextEditor } - | { kind: 'hidden'; uri: vscode.Uri } - | { kind: 'not-found' }; - -async function tryDebugRelevantDocument(lastDocument: vscode.TextDocument | undefined, languageId: string, languageExtensions: [string, ...string[]], createFileAndFocus: () => Promise): Promise { - let searchResult!: DocSearchResult; - for (const languageExtension of languageExtensions) { - searchResult = tryFindRelevantDocument(lastDocument, languageId, languageExtension); - if (searchResult.kind !== 'not-found') { - break; - } - } - - let editor: vscode.TextEditor; - // If not, make one. - switch (searchResult.kind) { - case 'visible': - // Focus if necessary. - editor = searchResult.editor; - if (vscode.window.activeTextEditor !== editor) { - await vscode.window.showTextDocument(editor.document, { - viewColumn: vscode.ViewColumn.Beside, - }); - } - break; - case 'hidden': - editor = await vscode.window.showTextDocument(searchResult.uri, { - viewColumn: vscode.ViewColumn.Beside, - }); - break; - case 'not-found': - editor = await createFileAndFocus(); - break; - } - - await Promise.all([ - vscode.commands.executeCommand('workbench.action.debug.start'), - vscode.commands.executeCommand('workbench.debug.action.focusRepl'), - ]); - -} - -/** Tries to find a relevant {@link vscode.TextEditor} or a {@link vscode.Uri} for an open document */ -function tryFindRelevantDocument(lastDocument: vscode.TextDocument | undefined, languageId: string, languageExtension: string): DocSearchResult { - let editor: vscode.TextEditor | undefined; - - // Try to find the document created from the last step. - if (lastDocument) { - editor = vscode.window.visibleTextEditors.find(editor => editor.document === lastDocument); - } - - // If we couldn't find that, find a visible document with the desired language. - editor ??= vscode.window.visibleTextEditors.find(editor => editor.document.languageId === languageId); - if (editor) { - return { - kind: 'visible', - editor, - }; - } - - // If we still couldn't find that, find a possibly not-visible document. - for (const tabGroup of vscode.window.tabGroups.all) { - for (const tab of tabGroup.tabs) { - if (tab.input instanceof vscode.TabInputText && tab.input.uri.path.endsWith(languageExtension)) { - return { - kind: 'hidden', - uri: tab.input.uri, - }; - } - } - } - - return { kind: 'not-found' }; -} - -export function registerJsNodeWalkthrough( - commandManager: CommandManager, - jsWalkthroughState: JsWalkthroughState, -) { - commandManager.register(new CreateNewJSFileCommand(jsWalkthroughState)); - commandManager.register(new DebugJsFileCommand(jsWalkthroughState)); -} diff --git a/package.json b/package.json index b51f8352437..196a85cd516 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,6 @@ "@types/webpack": "^5.28.1", "@types/wicg-file-system-access": "^2020.9.5", "@types/windows-foreground-love": "^0.3.0", - "@types/windows-mutex": "^0.4.0", "@types/windows-process-tree": "^0.2.0", "@types/winreg": "^1.2.30", "@types/yauzl": "^2.9.1", @@ -230,9 +229,9 @@ "url": "https://github.com/microsoft/vscode/issues" }, "optionalDependencies": { + "@vscode/windows-mutex": "0.4.2", "@vscode/windows-registry": "1.0.6", "windows-foreground-love": "0.5.0", - "windows-mutex": "0.4.1", "windows-process-tree": "0.4.0" }, "resolutions": { diff --git a/src/vscode-dts/vscode.proposed.contribEditorLineNumberMenu.d.ts b/src/typings/windows-mutex.d.ts similarity index 65% rename from src/vscode-dts/vscode.proposed.contribEditorLineNumberMenu.d.ts rename to src/typings/windows-mutex.d.ts index 7ee400c56b3..d2cb1f8560e 100644 --- a/src/vscode-dts/vscode.proposed.contribEditorLineNumberMenu.d.ts +++ b/src/typings/windows-mutex.d.ts @@ -3,6 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// empty placeholder declaration for the `editor/lineNumber/context` menu contribution point +declare module '@vscode/windows-mutex' { + export class Mutex { + constructor(name: string); + isActive(): boolean; + release(): void; + } -// https://github.com/microsoft/vscode/issues/175945 @joyceerhl + export function isActive(name: string): boolean; +} diff --git a/src/vs/base/test/node/pfs/fixtures/index.html b/src/vs/base/test/node/pfs/fixtures/index.html index bccd24d9272..165a4ecbccd 100644 --- a/src/vs/base/test/node/pfs/fixtures/index.html +++ b/src/vs/base/test/node/pfs/fixtures/index.html @@ -1,7 +1,6 @@ - Strada diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 12213667782..da5f77daa4c 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -1221,7 +1221,7 @@ export class CodeApplication extends Disposable { const win32MutexName = this.productService.win32MutexName; if (isWindows && win32MutexName) { try { - const WindowsMutex = await import('windows-mutex'); + const WindowsMutex = await import('@vscode/windows-mutex'); const mutex = new WindowsMutex.Mutex(win32MutexName); once(this.lifecycleMainService.onWillShutdown)(() => mutex.release()); } catch (error) { diff --git a/src/vs/editor/contrib/hover/browser/hover.ts b/src/vs/editor/contrib/hover/browser/hover.ts index 10df5c93513..3e7b591bc73 100644 --- a/src/vs/editor/contrib/hover/browser/hover.ts +++ b/src/vs/editor/contrib/hover/browser/hover.ts @@ -30,6 +30,7 @@ import { MarkerHoverParticipant } from 'vs/editor/contrib/hover/browser/markerHo import 'vs/css!./hover'; import { InlineSuggestionHintsContentWidget } from 'vs/editor/contrib/inlineCompletions/browser/inlineSuggestionHintsWidget'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; export class ModesHoverController implements IEditorContribution { @@ -205,7 +206,7 @@ export class ModesHoverController implements IEditorContribution { const resolvedKeyboardEvent = this._keybindingService.softDispatch(e, this._editor.getDomNode()); // If the beginning of a multi-chord keybinding is pressed, or the command aims to focus the hover, set the variable to true, otherwise false - const mightTriggerFocus = (resolvedKeyboardEvent?.enterMultiChord || (resolvedKeyboardEvent?.commandId === 'editor.action.showHover' && this._contentWidget?.isVisible())); + const mightTriggerFocus = (resolvedKeyboardEvent?.kind === ResultKind.MoreChordsNeeded || (resolvedKeyboardEvent && resolvedKeyboardEvent.kind === ResultKind.KbFound && resolvedKeyboardEvent.commandId === 'editor.action.showHover' && this._contentWidget?.isVisible())); if (e.keyCode !== KeyCode.Ctrl && e.keyCode !== KeyCode.Alt && e.keyCode !== KeyCode.Meta && e.keyCode !== KeyCode.Shift && !mightTriggerFocus) { diff --git a/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts b/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts index 345ca8d44a3..03572dd0c35 100644 --- a/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts +++ b/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts @@ -109,10 +109,10 @@ flakySuite('Native Modules (all platforms)', () => { (!isWindows ? suite.skip : suite)('Native Modules (Windows)', () => { - (process.type === 'renderer' ? test.skip /* TODO@electron module is not context aware yet and thus cannot load in Electron renderer used by tests */ : test)('windows-mutex', async () => { - const mutex = await import('windows-mutex'); - assert.ok(mutex && typeof mutex.isActive === 'function', testErrorMessage('windows-mutex')); - assert.ok(typeof mutex.isActive === 'function', testErrorMessage('windows-mutex')); + (process.type === 'renderer' ? test.skip /* TODO@electron module is not context aware yet and thus cannot load in Electron renderer used by tests */ : test)('@vscode/windows-mutex', async () => { + const mutex = await import('@vscode/windows-mutex'); + assert.ok(mutex && typeof mutex.isActive === 'function', testErrorMessage('@vscode/windows-mutex')); + assert.ok(typeof mutex.isActive === 'function', testErrorMessage('@vscode/windows-mutex')); }); test('windows-foreground-love', async () => { diff --git a/src/vs/platform/files/test/node/fixtures/resolver/index.html b/src/vs/platform/files/test/node/fixtures/resolver/index.html index bccd24d9272..165a4ecbccd 100644 --- a/src/vs/platform/files/test/node/fixtures/resolver/index.html +++ b/src/vs/platform/files/test/node/fixtures/resolver/index.html @@ -1,7 +1,6 @@ - Strada diff --git a/src/vs/platform/files/test/node/fixtures/service/index.html b/src/vs/platform/files/test/node/fixtures/service/index.html index bccd24d9272..165a4ecbccd 100644 --- a/src/vs/platform/files/test/node/fixtures/service/index.html +++ b/src/vs/platform/files/test/node/fixtures/service/index.html @@ -1,7 +1,6 @@ - Strada diff --git a/src/vs/platform/keybinding/common/abstractKeybindingService.ts b/src/vs/platform/keybinding/common/abstractKeybindingService.ts index 22fc1ada9c1..8b0c59b2018 100644 --- a/src/vs/platform/keybinding/common/abstractKeybindingService.ts +++ b/src/vs/platform/keybinding/common/abstractKeybindingService.ts @@ -6,6 +6,7 @@ import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; import * as arrays from 'vs/base/common/arrays'; import { IntervalTimer, TimeoutTimer } from 'vs/base/common/async'; +import { illegalState } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { IME } from 'vs/base/common/ime'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -16,7 +17,7 @@ import * as nls from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService, IKeyboardEvent, KeybindingsSchemaContribution } from 'vs/platform/keybinding/common/keybinding'; -import { IResolveResult, KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; +import { ResolutionResult, KeybindingResolver, ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -30,6 +31,7 @@ interface CurrentChord { const HIGH_FREQ_COMMANDS = /^(cursor|delete|undo|redo|tab|editor\.action\.clipboard)/; export abstract class AbstractKeybindingService extends Disposable implements IKeybindingService { + public _serviceBrand: undefined; protected readonly _onDidUpdateKeybindings: Emitter = this._register(new Emitter()); @@ -44,7 +46,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK * "cmd+k" would be stored in this array, when on pressing "cmd+i", the service * would invoke the command bound by the keybinding */ - private _currentChords: CurrentChord[] | null; + private _currentChords: CurrentChord[]; private _currentChordChecker: IntervalTimer; private _currentChordStatusMessage: IDisposable | null; @@ -55,7 +57,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK protected _logging: boolean; public get inChordMode(): boolean { - return !!this._currentChords; + return this._currentChords.length > 0; } constructor( @@ -67,7 +69,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK ) { super(); - this._currentChords = null; + this._currentChords = []; this._currentChordChecker = new IntervalTimer(); this._currentChordStatusMessage = null; this._ignoreSingleModifiers = KeybindingModifierSet.EMPTY; @@ -134,7 +136,9 @@ export abstract class AbstractKeybindingService extends Disposable implements IK return this._dispatch(e, target); } - public softDispatch(e: IKeyboardEvent, target: IContextKeyServiceTarget): IResolveResult | null { + // TODO@ulugbekna: update namings to align with `_doDispatch` + // TODO@ulugbekna: this fn doesn't seem to take into account single-modifier keybindings, eg `shift shift` + public softDispatch(e: IKeyboardEvent, target: IContextKeyServiceTarget): ResolutionResult | null { this._log(`/ Soft dispatching keyboard event`); const keybinding = this.resolveKeyboardEvent(e); if (keybinding.hasMultipleChords()) { @@ -149,7 +153,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK } const contextValue = this._contextKeyService.getContext(target); - const currentChords = this._currentChords ? this._currentChords.map((({ keypress }) => keypress)) : null; + const currentChords = this._currentChords.map((({ keypress }) => keypress)); return this._getResolver().resolve(contextValue, currentChords, firstChord); } @@ -171,25 +175,28 @@ export abstract class AbstractKeybindingService extends Disposable implements IK }, 500); } - private _enterMultiChordMode(firstChord: string, keypressLabel: string | null): void { - this._currentChords = [{ - keypress: firstChord, - label: keypressLabel - }]; - this._currentChordStatusMessage = this._notificationService.status(nls.localize('first.chord', "({0}) was pressed. Waiting for second key of chord...", keypressLabel)); - this._scheduleLeaveChordMode(); - IME.disable(); - } + private _expectAnotherChord(firstChord: string, keypressLabel: string | null): void { + + this._currentChords.push({ keypress: firstChord, label: keypressLabel }); + + switch (this._currentChords.length) { + case 0: + throw illegalState('impossible'); + case 1: + // TODO@ulugbekna: revise this message and the one below (at least, fix terminology) + this._currentChordStatusMessage = this._notificationService.status(nls.localize('first.chord', "({0}) was pressed. Waiting for second key of chord...", keypressLabel)); + break; + default: { + const fullKeypressLabel = this._currentChords.map(({ label }) => label).join(', '); + this._currentChordStatusMessage = this._notificationService.status(nls.localize('next.chord', "({0}) was pressed. Waiting for next key of chord...", fullKeypressLabel)); + } + } - private _continueMultiChordMode(nextChord: string, keypressLabel: string | null): void { - this._currentChords = this._currentChords ? this._currentChords : []; - this._currentChords.push({ - keypress: nextChord, - label: keypressLabel - }); - const fullKeypressLabel = this._currentChords.map(({ label }) => label).join(', '); - this._currentChordStatusMessage = this._notificationService.status(nls.localize('next.chord', "({0}) was pressed. Waiting for next key of chord...", fullKeypressLabel)); this._scheduleLeaveChordMode(); + + if (IME.enabled) { + IME.disable(); + } } private _leaveChordMode(): void { @@ -198,7 +205,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK this._currentChordStatusMessage = null; } this._currentChordChecker.cancel(); - this._currentChords = null; + this._currentChords = []; IME.enable(); } @@ -287,10 +294,10 @@ export abstract class AbstractKeybindingService extends Disposable implements IK // hence we disregard `_currentChord` and use the same modifier instead. const [dispatchKeyname,] = userKeypress.getSingleModifierDispatchChords(); userPressedChord = dispatchKeyname; - currentChords = dispatchKeyname ? [dispatchKeyname] : []; + currentChords = dispatchKeyname ? [dispatchKeyname] : []; // TODO@ulugbekna: in the `else` case we assign an empty array - make sure `resolve` can handle an empty array well } else { [userPressedChord,] = userKeypress.getDispatchChords(); - currentChords = this._currentChords ? this._currentChords.map(({ keypress }) => keypress) : null; + currentChords = this._currentChords.map(({ keypress }) => keypress); } if (userPressedChord === null) { @@ -304,47 +311,72 @@ export abstract class AbstractKeybindingService extends Disposable implements IK const resolveResult = this._getResolver().resolve(contextValue, currentChords, userPressedChord); - this._logService.trace('KeybindingService#dispatch', keypressLabel, resolveResult?.commandId); + switch (resolveResult.kind) { - if (resolveResult && resolveResult.enterMultiChord) { - shouldPreventDefault = true; - this._enterMultiChordMode(userPressedChord, keypressLabel); - this._log(`+ Entering chord mode...`); - return shouldPreventDefault; - } + case ResultKind.NoMatchingKb: { - if (this._currentChords) { - if (resolveResult && !resolveResult.leaveMultiChord) { - shouldPreventDefault = true; - this._continueMultiChordMode(userPressedChord, keypressLabel); - this._log(`+ Continuing chord mode...`); + this._logService.trace('KeybindingService#dispatch', keypressLabel, `[ No matching keybinding ]`); + + if (this.inChordMode) { + const currentChordsLabel = this._currentChords.map(({ label }) => label).join(', '); + this._log(`+ Leaving multi-chord mode: Nothing bound to "${currentChordsLabel}, ${keypressLabel}".`); + this._notificationService.status(nls.localize('missing.chord', "The key combination ({0}, {1}) is not a command.", currentChordsLabel, keypressLabel), { hideAfter: 10 * 1000 /* 10s */ }); + this._leaveChordMode(); + + shouldPreventDefault = true; + } return shouldPreventDefault; - } else if (!resolveResult || !resolveResult.commandId) { - const currentChordsLabel = this._currentChords.map(({ label }) => label).join(', '); - this._log(`+ Leaving chord mode: Nothing bound to "${currentChordsLabel}, ${keypressLabel}".`); - this._notificationService.status(nls.localize('missing.chord', "The key combination ({0}, {1}) is not a command.", currentChordsLabel, keypressLabel), { hideAfter: 10 * 1000 /* 10s */ }); + } + + case ResultKind.MoreChordsNeeded: { + + this._logService.trace('KeybindingService#dispatch', keypressLabel, `[ Several keybindings match - more chords needed ]`); + shouldPreventDefault = true; + this._expectAnotherChord(userPressedChord, keypressLabel); + this._log(this._currentChords.length === 1 ? `+ Entering multi-chord mode...` : `+ Continuing multi-chord mode...`); + return shouldPreventDefault; + } + + case ResultKind.KbFound: { + + this._logService.trace('KeybindingService#dispatch', keypressLabel, `[ Will dispatch command ${resolveResult.commandId} ]`); + + if (resolveResult.commandId === null) { + + if (this.inChordMode) { + const currentChordsLabel = this._currentChords.map(({ label }) => label).join(', '); + this._log(`+ Leaving chord mode: Nothing bound to "${currentChordsLabel}, ${keypressLabel}".`); + this._notificationService.status(nls.localize('missing.chord', "The key combination ({0}, {1}) is not a command.", currentChordsLabel, keypressLabel), { hideAfter: 10 * 1000 /* 10s */ }); + this._leaveChordMode(); + } + + shouldPreventDefault = true; + + } else { + if (this.inChordMode) { + this._leaveChordMode(); + } + + if (!resolveResult.isBubble) { + shouldPreventDefault = true; + } + + this._log(`+ Invoking command ${resolveResult.commandId}.`); + if (typeof resolveResult.commandArgs === 'undefined') { + this._commandService.executeCommand(resolveResult.commandId).then(undefined, err => this._notificationService.warn(err)); + } else { + this._commandService.executeCommand(resolveResult.commandId, resolveResult.commandArgs).then(undefined, err => this._notificationService.warn(err)); + } + + if (!HIGH_FREQ_COMMANDS.test(resolveResult.commandId)) { + this._telemetryService.publicLog2('workbenchActionExecuted', { id: resolveResult.commandId, from: 'keybinding', detail: userKeypress.getUserSettingsLabel() ?? undefined }); + } + } + + return shouldPreventDefault; } } - - this._leaveChordMode(); - - if (resolveResult && resolveResult.commandId) { - if (!resolveResult.bubble) { - shouldPreventDefault = true; - } - this._log(`+ Invoking command ${resolveResult.commandId}.`); - if (typeof resolveResult.commandArgs === 'undefined') { - this._commandService.executeCommand(resolveResult.commandId).then(undefined, err => this._notificationService.warn(err)); - } else { - this._commandService.executeCommand(resolveResult.commandId, resolveResult.commandArgs).then(undefined, err => this._notificationService.warn(err)); - } - if (!HIGH_FREQ_COMMANDS.test(resolveResult.commandId)) { - this._telemetryService.publicLog2('workbenchActionExecuted', { id: resolveResult.commandId, from: 'keybinding', detail: userKeypress.getUserSettingsLabel() ?? undefined }); - } - } - - return shouldPreventDefault; } mightProducePrintableCharacter(event: IKeyboardEvent): boolean { diff --git a/src/vs/platform/keybinding/common/keybinding.ts b/src/vs/platform/keybinding/common/keybinding.ts index 3d4851e8cde..ba156046afa 100644 --- a/src/vs/platform/keybinding/common/keybinding.ts +++ b/src/vs/platform/keybinding/common/keybinding.ts @@ -9,7 +9,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { ResolvedKeybinding, Keybinding } from 'vs/base/common/keybindings'; import { IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IResolveResult } from 'vs/platform/keybinding/common/keybindingResolver'; +import { ResolutionResult } from 'vs/platform/keybinding/common/keybindingResolver'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; export interface IUserFriendlyKeybinding { @@ -63,7 +63,7 @@ export interface IKeybindingService { /** * Resolve and dispatch `keyboardEvent`, but do not invoke the command or change inner state. */ - softDispatch(keyboardEvent: IKeyboardEvent, target: IContextKeyServiceTarget): IResolveResult | null; + softDispatch(keyboardEvent: IKeyboardEvent, target: IContextKeyServiceTarget): ResolutionResult | null; dispatchByUserSettingsLabel(userSettingsLabel: string, target: IContextKeyServiceTarget): void; diff --git a/src/vs/platform/keybinding/common/keybindingResolver.ts b/src/vs/platform/keybinding/common/keybindingResolver.ts index 938a78d85a8..d69e0136eb8 100644 --- a/src/vs/platform/keybinding/common/keybindingResolver.ts +++ b/src/vs/platform/keybinding/common/keybindingResolver.ts @@ -6,16 +6,35 @@ import { implies, ContextKeyExpression, ContextKeyExprType, IContext, IContextKeyService, expressionsAreEqualWithConstantSubstitution } from 'vs/platform/contextkey/common/contextkey'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; -export interface IResolveResult { - /** Whether the resolved keybinding is entering a multi chord */ - enterMultiChord: boolean; - /** Whether the resolved keybinding is leaving (and executing) a multi chord keybinding */ - leaveMultiChord: boolean; - commandId: string | null; - commandArgs: any; - bubble: boolean; +//#region resolution-result + +export const enum ResultKind { + /** No keybinding found this sequence of chords */ + NoMatchingKb, + + /** There're several keybindings that have the given sequence of chords as a prefix */ + MoreChordsNeeded, + + /** A single keybinding found to be dispatched/invoked */ + KbFound } +export type ResolutionResult = + | { kind: ResultKind.NoMatchingKb } + | { kind: ResultKind.MoreChordsNeeded } + | { kind: ResultKind.KbFound; commandId: string | null; commandArgs: any; isBubble: boolean }; + + +// util definitions to make working with the above types easier within this module: + +const NoMatchingKb: ResolutionResult = { kind: ResultKind.NoMatchingKb }; +const MoreChordsNeeded: ResolutionResult = { kind: ResultKind.MoreChordsNeeded }; +function KbFound(commandId: string | null, commandArgs: any, isBubble: boolean): ResolutionResult { + return { kind: ResultKind.KbFound, commandId, commandArgs, isBubble }; +} + +//#endregion + /** * Stores mappings from keybindings to commands and from commands to keybindings. * Given a sequence of chords, `resolve`s which keybinding it matches @@ -281,82 +300,70 @@ export class KeybindingResolver { return items[items.length - 1]; } - public resolve(context: IContext, currentChords: string[] | null, keypress: string): IResolveResult | null { - this._log(`| Resolving ${keypress}${currentChords ? ` chorded from ${currentChords}` : ``}`); + /** + * Looks up a keybinding trigged as a result of pressing a sequence of chords - `[...currentChords, keypress]` + * + * Example: resolving 3 chords pressed sequentially - `cmd+k cmd+p cmd+i`: + * `currentChords = [ 'cmd+k' , 'cmd+p' ]` and `keypress = `cmd+i` - last pressed chord + */ + public resolve(context: IContext, currentChords: string[], keypress: string): ResolutionResult { + + const pressedChords = [...currentChords, keypress]; + + this._log(`| Resolving ${pressedChords}`); + + const kbCandidates = this._map.get(pressedChords[0]); + if (kbCandidates === undefined) { + // No bindings with such 0-th chord + this._log(`\\ No keybinding entries.`); + return NoMatchingKb; + } + let lookupMap: ResolvedKeybindingItem[] | null = null; - if (currentChords !== null) { + if (pressedChords.length < 2) { + lookupMap = kbCandidates; + } else { // Fetch all chord bindings for `currentChords` - const candidates = this._map.get(currentChords[0]); - if (typeof candidates === 'undefined') { - // No chords starting with `currentChords` - this._log(`\\ No keybinding entries.`); - return null; - } - lookupMap = []; - for (let i = 0, len = candidates.length; i < len; i++) { - const candidate = candidates[i]; - if (candidate.chords.length <= currentChords.length) { + for (let i = 0, len = kbCandidates.length; i < len; i++) { + + const candidate = kbCandidates[i]; + + if (pressedChords.length > candidate.chords.length) { // # of pressed chords can't be less than # of chords in a keybinding to invoke continue; } let prefixMatches = true; - for (let i = 1; i < currentChords.length; i++) { - if (candidate.chords[i] !== currentChords[i]) { + for (let i = 1; i < pressedChords.length; i++) { + if (candidate.chords[i] !== pressedChords[i]) { prefixMatches = false; break; } } - if (prefixMatches && candidate.chords[currentChords.length] === keypress) { + if (prefixMatches) { lookupMap.push(candidate); } } - } else { - const candidates = this._map.get(keypress); - if (typeof candidates === 'undefined') { - // No bindings with `keypress` - this._log(`\\ No keybinding entries.`); - return null; - } - - lookupMap = candidates; } + // check there's a keybinding with a matching when clause const result = this._findCommand(context, lookupMap); if (!result) { this._log(`\\ From ${lookupMap.length} keybinding entries, no when clauses matched the context.`); - return null; + return NoMatchingKb; } - if (currentChords === null && result.chords.length > 1 && result.chords[1] !== null) { - this._log(`\\ From ${lookupMap.length} keybinding entries, matched chord, when: ${printWhenExplanation(result.when)}, source: ${printSourceExplanation(result)}.`); - return { - enterMultiChord: true, - leaveMultiChord: false, - commandId: null, - commandArgs: null, - bubble: false - }; - } else if (currentChords !== null && currentChords.length + 1 < result.chords.length) { - this._log(`\\ From ${lookupMap.length} keybinding entries, continued chord, when: ${printWhenExplanation(result.when)}, source: ${printSourceExplanation(result)}.`); - return { - enterMultiChord: false, - leaveMultiChord: false, - commandId: null, - commandArgs: null, - bubble: false - }; + // check we got all chords necessary to be sure a particular keybinding needs to be invoked + if (pressedChords.length < result.chords.length) { + // The chord sequence is not complete + this._log(`\\ From ${lookupMap.length} keybinding entries, awaiting ${result.chords.length - pressedChords.length} more chord(s), when: ${printWhenExplanation(result.when)}, source: ${printSourceExplanation(result)}.`); + return MoreChordsNeeded; } this._log(`\\ From ${lookupMap.length} keybinding entries, matched ${result.command}, when: ${printWhenExplanation(result.when)}, source: ${printSourceExplanation(result)}.`); - return { - enterMultiChord: false, - leaveMultiChord: result.chords.length > 1, - commandId: result.command, - commandArgs: result.commandArgs, - bubble: result.bubble - }; + + return KbFound(result.command, result.commandArgs, result.bubble); } private _findCommand(context: IContext, matches: ResolvedKeybindingItem[]): ResolvedKeybindingItem | null { diff --git a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts index 53001faeb45..72e8026e5ca 100644 --- a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts +++ b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts @@ -8,7 +8,7 @@ import { decodeKeybinding, createSimpleKeybinding, KeyCodeChord } from 'vs/base/ import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { OS } from 'vs/base/common/platform'; import { ContextKeyExpr, ContextKeyExpression, IContext } from 'vs/platform/contextkey/common/contextkey'; -import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; +import { KeybindingResolver, ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { createUSLayoutResolvedKeybinding } from 'vs/platform/keybinding/test/common/keybindingsTestUtils'; @@ -50,8 +50,13 @@ suite('KeybindingResolver', () => { assert.strictEqual(contextRules.evaluate(createContext({ bar: 'bz' })), false); const resolver = new KeybindingResolver([keybindingItem], [], () => { }); - assert.strictEqual(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(runtimeKeybinding))!.commandId, 'yes'); - assert.strictEqual(resolver.resolve(createContext({ bar: 'bz' }), null, getDispatchStr(runtimeKeybinding)), null); + + const r1 = resolver.resolve(createContext({ bar: 'baz' }), [], getDispatchStr(runtimeKeybinding)); + assert.ok(r1.kind === ResultKind.KbFound); + assert.strictEqual(r1.commandId, 'yes'); + + const r2 = resolver.resolve(createContext({ bar: 'bz' }), [], getDispatchStr(runtimeKeybinding)); + assert.strictEqual(r2.kind, ResultKind.NoMatchingKb); }); test('resolve key with arguments', () => { @@ -62,247 +67,253 @@ suite('KeybindingResolver', () => { const keybindingItem = kbItem(keybinding, 'yes', commandArgs, contextRules, true); const resolver = new KeybindingResolver([keybindingItem], [], () => { }); - assert.strictEqual(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(runtimeKeybinding))!.commandArgs, commandArgs); + + const r = resolver.resolve(createContext({ bar: 'baz' }), [], getDispatchStr(runtimeKeybinding)); + assert.ok(r.kind === ResultKind.KbFound); + assert.strictEqual(r.commandArgs, commandArgs); }); - test('KeybindingResolver.handleRemovals simple 1', () => { - const defaults = [ - kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true) - ]; - const overrides = [ - kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), false) - ]; - const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); - assert.deepStrictEqual(actual, [ - kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), - kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), false), - ]); + suite('handle keybinding removals', () => { + + test('simple 1', () => { + const defaults = [ + kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true) + ]; + const overrides = [ + kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), false) + ]; + const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); + assert.deepStrictEqual(actual, [ + kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), + kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), false), + ]); + }); + + test('simple 2', () => { + const defaults = [ + kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), + kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) + ]; + const overrides = [ + kbItem(KeyCode.KeyC, 'yes3', null, ContextKeyExpr.equals('3', 'c'), false) + ]; + const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); + assert.deepStrictEqual(actual, [ + kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), + kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true), + kbItem(KeyCode.KeyC, 'yes3', null, ContextKeyExpr.equals('3', 'c'), false), + ]); + }); + + test('removal with not matching when', () => { + const defaults = [ + kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), + kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) + ]; + const overrides = [ + kbItem(KeyCode.KeyA, '-yes1', null, ContextKeyExpr.equals('1', 'b'), false) + ]; + const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); + assert.deepStrictEqual(actual, [ + kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), + kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) + ]); + }); + + test('removal with not matching keybinding', () => { + const defaults = [ + kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), + kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) + ]; + const overrides = [ + kbItem(KeyCode.KeyB, '-yes1', null, ContextKeyExpr.equals('1', 'a'), false) + ]; + const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); + assert.deepStrictEqual(actual, [ + kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), + kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) + ]); + }); + + test('removal with matching keybinding and when', () => { + const defaults = [ + kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), + kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) + ]; + const overrides = [ + kbItem(KeyCode.KeyA, '-yes1', null, ContextKeyExpr.equals('1', 'a'), false) + ]; + const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); + assert.deepStrictEqual(actual, [ + kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) + ]); + }); + + test('removal with unspecified keybinding', () => { + const defaults = [ + kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), + kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) + ]; + const overrides = [ + kbItem(0, '-yes1', null, ContextKeyExpr.equals('1', 'a'), false) + ]; + const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); + assert.deepStrictEqual(actual, [ + kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) + ]); + }); + + test('removal with unspecified when', () => { + const defaults = [ + kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), + kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) + ]; + const overrides = [ + kbItem(KeyCode.KeyA, '-yes1', null, undefined, false) + ]; + const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); + assert.deepStrictEqual(actual, [ + kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) + ]); + }); + + test('removal with unspecified when and unspecified keybinding', () => { + const defaults = [ + kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), + kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) + ]; + const overrides = [ + kbItem(0, '-yes1', null, undefined, false) + ]; + const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); + assert.deepStrictEqual(actual, [ + kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) + ]); + }); + + test('issue #138997 - removal in default list', () => { + const defaults = [ + kbItem(KeyCode.KeyA, 'yes1', null, undefined, true), + kbItem(KeyCode.KeyB, 'yes2', null, undefined, true), + kbItem(0, '-yes1', null, undefined, false) + ]; + const overrides: ResolvedKeybindingItem[] = []; + const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); + assert.deepStrictEqual(actual, [ + kbItem(KeyCode.KeyB, 'yes2', null, undefined, true) + ]); + }); + + test('issue #612#issuecomment-222109084 cannot remove keybindings for commands with ^', () => { + const defaults = [ + kbItem(KeyCode.KeyA, '^yes1', null, ContextKeyExpr.equals('1', 'a'), true), + kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) + ]; + const overrides = [ + kbItem(KeyCode.KeyA, '-yes1', null, undefined, false) + ]; + const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); + assert.deepStrictEqual(actual, [ + kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) + ]); + }); + + test('issue #140884 Unable to reassign F1 as keybinding for Show All Commands', () => { + const defaults = [ + kbItem(KeyCode.KeyA, 'command1', null, undefined, true), + ]; + const overrides = [ + kbItem(KeyCode.KeyA, '-command1', null, undefined, false), + kbItem(KeyCode.KeyA, 'command1', null, undefined, false), + ]; + const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); + assert.deepStrictEqual(actual, [ + kbItem(KeyCode.KeyA, 'command1', null, undefined, false) + ]); + }); + + test('issue #141638: Keyboard Shortcuts: Change When Expression might actually remove keybinding in Insiders', () => { + const defaults = [ + kbItem(KeyCode.KeyA, 'command1', null, undefined, true), + ]; + const overrides = [ + kbItem(KeyCode.KeyA, 'command1', null, ContextKeyExpr.equals('a', '1'), false), + kbItem(KeyCode.KeyA, '-command1', null, undefined, false), + ]; + const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); + assert.deepStrictEqual(actual, [ + kbItem(KeyCode.KeyA, 'command1', null, ContextKeyExpr.equals('a', '1'), false) + ]); + }); + + test('issue #157751: Auto-quoting of context keys prevents removal of keybindings via UI', () => { + const defaults = [ + kbItem(KeyCode.KeyA, 'command1', null, ContextKeyExpr.deserialize(`editorTextFocus && activeEditor != workbench.editor.notebook && editorLangId in julia.supportedLanguageIds`), true), + ]; + const overrides = [ + kbItem(KeyCode.KeyA, '-command1', null, ContextKeyExpr.deserialize(`editorTextFocus && activeEditor != 'workbench.editor.notebook' && editorLangId in 'julia.supportedLanguageIds'`), false), + ]; + const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); + assert.deepStrictEqual(actual, []); + }); + + test('issue #160604: Remove keybindings with when clause does not work', () => { + const defaults = [ + kbItem(KeyCode.KeyA, 'command1', null, undefined, true), + ]; + const overrides = [ + kbItem(KeyCode.KeyA, '-command1', null, ContextKeyExpr.true(), false), + ]; + const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); + assert.deepStrictEqual(actual, []); + }); + + test('contextIsEntirelyIncluded', () => { + const toContextKeyExpression = (expr: ContextKeyExpression | string | null) => { + if (typeof expr === 'string' || !expr) { + return ContextKeyExpr.deserialize(expr); + } + return expr; + }; + const assertIsIncluded = (a: ContextKeyExpression | string | null, b: ContextKeyExpression | string | null) => { + assert.strictEqual(KeybindingResolver.whenIsEntirelyIncluded(toContextKeyExpression(a), toContextKeyExpression(b)), true); + }; + const assertIsNotIncluded = (a: ContextKeyExpression | string | null, b: ContextKeyExpression | string | null) => { + assert.strictEqual(KeybindingResolver.whenIsEntirelyIncluded(toContextKeyExpression(a), toContextKeyExpression(b)), false); + }; + + assertIsIncluded(null, null); + assertIsIncluded(null, ContextKeyExpr.true()); + assertIsIncluded(ContextKeyExpr.true(), null); + assertIsIncluded(ContextKeyExpr.true(), ContextKeyExpr.true()); + assertIsIncluded('key1', null); + assertIsIncluded('key1', ''); + assertIsIncluded('key1', 'key1'); + assertIsIncluded('key1', ContextKeyExpr.true()); + assertIsIncluded('!key1', ''); + assertIsIncluded('!key1', '!key1'); + assertIsIncluded('key2', ''); + assertIsIncluded('key2', 'key2'); + assertIsIncluded('key1 && key1 && key2 && key2', 'key2'); + assertIsIncluded('key1 && key2', 'key2'); + assertIsIncluded('key1 && key2', 'key1'); + assertIsIncluded('key1 && key2', ''); + assertIsIncluded('key1', 'key1 || key2'); + assertIsIncluded('key1 || !key1', 'key2 || !key2'); + assertIsIncluded('key1', 'key1 || key2 && key3'); + + assertIsNotIncluded('key1', '!key1'); + assertIsNotIncluded('!key1', 'key1'); + assertIsNotIncluded('key1 && key2', 'key3'); + assertIsNotIncluded('key1 && key2', 'key4'); + assertIsNotIncluded('key1', 'key2'); + assertIsNotIncluded('key1 || key2', 'key2'); + assertIsNotIncluded('', 'key2'); + assertIsNotIncluded(null, 'key2'); + }); }); - test('KeybindingResolver.handleRemovals simple 2', () => { - const defaults = [ - kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), - kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) - ]; - const overrides = [ - kbItem(KeyCode.KeyC, 'yes3', null, ContextKeyExpr.equals('3', 'c'), false) - ]; - const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); - assert.deepStrictEqual(actual, [ - kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), - kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true), - kbItem(KeyCode.KeyC, 'yes3', null, ContextKeyExpr.equals('3', 'c'), false), - ]); - }); - - test('KeybindingResolver.handleRemovals removal with not matching when', () => { - const defaults = [ - kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), - kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) - ]; - const overrides = [ - kbItem(KeyCode.KeyA, '-yes1', null, ContextKeyExpr.equals('1', 'b'), false) - ]; - const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); - assert.deepStrictEqual(actual, [ - kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), - kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) - ]); - }); - - test('KeybindingResolver.handleRemovals removal with not matching keybinding', () => { - const defaults = [ - kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), - kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) - ]; - const overrides = [ - kbItem(KeyCode.KeyB, '-yes1', null, ContextKeyExpr.equals('1', 'a'), false) - ]; - const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); - assert.deepStrictEqual(actual, [ - kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), - kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) - ]); - }); - - test('KeybindingResolver.handleRemovals removal with matching keybinding and when', () => { - const defaults = [ - kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), - kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) - ]; - const overrides = [ - kbItem(KeyCode.KeyA, '-yes1', null, ContextKeyExpr.equals('1', 'a'), false) - ]; - const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); - assert.deepStrictEqual(actual, [ - kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) - ]); - }); - - test('KeybindingResolver.handleRemovals removal with unspecified keybinding', () => { - const defaults = [ - kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), - kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) - ]; - const overrides = [ - kbItem(0, '-yes1', null, ContextKeyExpr.equals('1', 'a'), false) - ]; - const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); - assert.deepStrictEqual(actual, [ - kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) - ]); - }); - - test('KeybindingResolver.handleRemovals removal with unspecified when', () => { - const defaults = [ - kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), - kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) - ]; - const overrides = [ - kbItem(KeyCode.KeyA, '-yes1', null, undefined, false) - ]; - const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); - assert.deepStrictEqual(actual, [ - kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) - ]); - }); - - test('KeybindingResolver.handleRemovals removal with unspecified when and unspecified keybinding', () => { - const defaults = [ - kbItem(KeyCode.KeyA, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true), - kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) - ]; - const overrides = [ - kbItem(0, '-yes1', null, undefined, false) - ]; - const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); - assert.deepStrictEqual(actual, [ - kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) - ]); - }); - - test('issue #138997 KeybindingResolver.handleRemovals removal in default list', () => { - const defaults = [ - kbItem(KeyCode.KeyA, 'yes1', null, undefined, true), - kbItem(KeyCode.KeyB, 'yes2', null, undefined, true), - kbItem(0, '-yes1', null, undefined, false) - ]; - const overrides: ResolvedKeybindingItem[] = []; - const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); - assert.deepStrictEqual(actual, [ - kbItem(KeyCode.KeyB, 'yes2', null, undefined, true) - ]); - }); - - test('issue #612#issuecomment-222109084 cannot remove keybindings for commands with ^', () => { - const defaults = [ - kbItem(KeyCode.KeyA, '^yes1', null, ContextKeyExpr.equals('1', 'a'), true), - kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) - ]; - const overrides = [ - kbItem(KeyCode.KeyA, '-yes1', null, undefined, false) - ]; - const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); - assert.deepStrictEqual(actual, [ - kbItem(KeyCode.KeyB, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true) - ]); - }); - - test('issue #140884 Unable to reassign F1 as keybinding for Show All Commands', () => { - const defaults = [ - kbItem(KeyCode.KeyA, 'command1', null, undefined, true), - ]; - const overrides = [ - kbItem(KeyCode.KeyA, '-command1', null, undefined, false), - kbItem(KeyCode.KeyA, 'command1', null, undefined, false), - ]; - const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); - assert.deepStrictEqual(actual, [ - kbItem(KeyCode.KeyA, 'command1', null, undefined, false) - ]); - }); - - test('issue #141638: Keyboard Shortcuts: Change When Expression might actually remove keybinding in Insiders', () => { - const defaults = [ - kbItem(KeyCode.KeyA, 'command1', null, undefined, true), - ]; - const overrides = [ - kbItem(KeyCode.KeyA, 'command1', null, ContextKeyExpr.equals('a', '1'), false), - kbItem(KeyCode.KeyA, '-command1', null, undefined, false), - ]; - const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); - assert.deepStrictEqual(actual, [ - kbItem(KeyCode.KeyA, 'command1', null, ContextKeyExpr.equals('a', '1'), false) - ]); - }); - - test('issue #157751: Auto-quoting of context keys prevents removal of keybindings via UI', () => { - const defaults = [ - kbItem(KeyCode.KeyA, 'command1', null, ContextKeyExpr.deserialize(`editorTextFocus && activeEditor != workbench.editor.notebook && editorLangId in julia.supportedLanguageIds`), true), - ]; - const overrides = [ - kbItem(KeyCode.KeyA, '-command1', null, ContextKeyExpr.deserialize(`editorTextFocus && activeEditor != 'workbench.editor.notebook' && editorLangId in 'julia.supportedLanguageIds'`), false), - ]; - const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); - assert.deepStrictEqual(actual, []); - }); - - test('issue #160604: Remove keybindings with when clause does not work', () => { - const defaults = [ - kbItem(KeyCode.KeyA, 'command1', null, undefined, true), - ]; - const overrides = [ - kbItem(KeyCode.KeyA, '-command1', null, ContextKeyExpr.true(), false), - ]; - const actual = KeybindingResolver.handleRemovals([...defaults, ...overrides]); - assert.deepStrictEqual(actual, []); - }); - - test('contextIsEntirelyIncluded', () => { - const toContextKeyExpression = (expr: ContextKeyExpression | string | null) => { - if (typeof expr === 'string' || !expr) { - return ContextKeyExpr.deserialize(expr); - } - return expr; - }; - const assertIsIncluded = (a: ContextKeyExpression | string | null, b: ContextKeyExpression | string | null) => { - assert.strictEqual(KeybindingResolver.whenIsEntirelyIncluded(toContextKeyExpression(a), toContextKeyExpression(b)), true); - }; - const assertIsNotIncluded = (a: ContextKeyExpression | string | null, b: ContextKeyExpression | string | null) => { - assert.strictEqual(KeybindingResolver.whenIsEntirelyIncluded(toContextKeyExpression(a), toContextKeyExpression(b)), false); - }; - - assertIsIncluded(null, null); - assertIsIncluded(null, ContextKeyExpr.true()); - assertIsIncluded(ContextKeyExpr.true(), null); - assertIsIncluded(ContextKeyExpr.true(), ContextKeyExpr.true()); - assertIsIncluded('key1', null); - assertIsIncluded('key1', ''); - assertIsIncluded('key1', 'key1'); - assertIsIncluded('key1', ContextKeyExpr.true()); - assertIsIncluded('!key1', ''); - assertIsIncluded('!key1', '!key1'); - assertIsIncluded('key2', ''); - assertIsIncluded('key2', 'key2'); - assertIsIncluded('key1 && key1 && key2 && key2', 'key2'); - assertIsIncluded('key1 && key2', 'key2'); - assertIsIncluded('key1 && key2', 'key1'); - assertIsIncluded('key1 && key2', ''); - assertIsIncluded('key1', 'key1 || key2'); - assertIsIncluded('key1 || !key1', 'key2 || !key2'); - assertIsIncluded('key1', 'key1 || key2 && key3'); - - assertIsNotIncluded('key1', '!key1'); - assertIsNotIncluded('!key1', 'key1'); - assertIsNotIncluded('key1 && key2', 'key3'); - assertIsNotIncluded('key1 && key2', 'key4'); - assertIsNotIncluded('key1', 'key2'); - assertIsNotIncluded('key1 || key2', 'key2'); - assertIsNotIncluded('', 'key2'); - assertIsNotIncluded(null, 'key2'); - }); - - test('resolve command', () => { + suite('resolve command', () => { function _kbItem(keybinding: number | number[], command: string, when: ContextKeyExpression | undefined): ResolvedKeybindingItem { return kbItem(keybinding, command, null, when, true); @@ -370,30 +381,40 @@ suite('KeybindingResolver', () => { undefined ), _kbItem( - KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyC), + KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyC), // cmd+k cmd+c 'comment lines', undefined ), _kbItem( - KeyChord(KeyMod.CtrlCmd | KeyCode.KeyG, KeyMod.CtrlCmd | KeyCode.KeyC), + KeyChord(KeyMod.CtrlCmd | KeyCode.KeyG, KeyMod.CtrlCmd | KeyCode.KeyC), // cmd+g cmd+c 'unreachablechord', undefined ), _kbItem( - KeyMod.CtrlCmd | KeyCode.KeyG, + KeyMod.CtrlCmd | KeyCode.KeyG, // cmd+g 'eleven', undefined ), _kbItem( - [KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyA, KeyCode.KeyB], + [KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyA, KeyCode.KeyB], // cmd+k a b 'long multi chord', undefined ), + _kbItem( + [KeyMod.CtrlCmd | KeyCode.KeyB, KeyMod.CtrlCmd | KeyCode.KeyC], // cmd+b cmd+c + 'shadowed by long-multi-chord-2', + undefined + ), + _kbItem( + [KeyMod.CtrlCmd | KeyCode.KeyB, KeyMod.CtrlCmd | KeyCode.KeyC, KeyCode.KeyI], // cmd+b cmd+c i + 'long-multi-chord-2', + undefined + ) ]; const resolver = new KeybindingResolver(items, [], () => { }); - const testKey = (commandId: string, expectedKeys: number[] | number[][]) => { + const testKbLookupByCommand = (commandId: string, expectedKeys: number[] | number[][]) => { // Test lookup const lookupResult = resolver.lookupKeybindings(commandId); assert.strictEqual(lookupResult.length, expectedKeys.length, 'Length mismatch @ commandId ' + commandId); @@ -407,68 +428,93 @@ suite('KeybindingResolver', () => { const testResolve = (ctx: IContext, _expectedKey: number | number[], commandId: string) => { const expectedKeybinding = decodeKeybinding(_expectedKey, OS)!; - let previousChord: string[] | null = null; + const previousChord: string[] = []; + for (let i = 0, len = expectedKeybinding.chords.length; i < len; i++) { + const chord = getDispatchStr(expectedKeybinding.chords[i]); + const result = resolver.resolve(ctx, previousChord, chord); + if (i === len - 1) { // if it's the final chord, then we should find a valid command, // and there should not be a chord. - assert.ok(result !== null, `Enters multi chord for ${commandId} at chord ${i}`); - assert.strictEqual(result!.commandId, commandId, `Enters multi chord for ${commandId} at chord ${i}`); - assert.strictEqual(result!.enterMultiChord, false, `Enters multi chord for ${commandId} at chord ${i}`); + assert.ok(result.kind === ResultKind.KbFound, `Enters multi chord for ${commandId} at chord ${i}`); + assert.strictEqual(result.commandId, commandId, `Enters multi chord for ${commandId} at chord ${i}`); } else if (i > 0) { // if this is an intermediate chord, we should not find a valid command, // and there should be an open chord we continue. - assert.ok(result !== null, `Continues multi chord for ${commandId} at chord ${i}`); - assert.strictEqual(result!.commandId, null, `Continues multi chord for ${commandId} at chord ${i}`); - assert.strictEqual(result!.enterMultiChord, false, `Is already in multi chord for ${commandId} at chord ${i}`); - assert.strictEqual(result!.leaveMultiChord, false, `Does not leave multi chord for ${commandId} at chord ${i}`); + assert.ok(result.kind === ResultKind.MoreChordsNeeded, `Continues multi chord for ${commandId} at chord ${i}`); } else { // if it's not the final chord and not an intermediate, then we should not // find a valid command, and we should enter a chord. - assert.ok(result !== null, `Enters multi chord for ${commandId} at chord ${i}`); - assert.strictEqual(result!.commandId, null, `Enters multi chord for ${commandId} at chord ${i}`); - assert.strictEqual(result!.enterMultiChord, true, `Enters multi chord for ${commandId} at chord ${i}`); - } - if (previousChord === null) { - previousChord = []; + assert.ok(result.kind === ResultKind.MoreChordsNeeded, `Enters multi chord for ${commandId} at chord ${i}`); } previousChord.push(chord); } }; - testKey('first', []); + test('resolve command - 1', () => { + testKbLookupByCommand('first', []); + }); - testKey('second', [KeyCode.KeyZ, KeyCode.KeyX]); - testResolve(createContext({ key2: true }), KeyCode.KeyX, 'second'); - testResolve(createContext({}), KeyCode.KeyZ, 'second'); + test('resolve command - 2', () => { + testKbLookupByCommand('second', [KeyCode.KeyZ, KeyCode.KeyX]); + testResolve(createContext({ key2: true }), KeyCode.KeyX, 'second'); + testResolve(createContext({}), KeyCode.KeyZ, 'second'); + }); - testKey('third', [KeyCode.KeyX]); - testResolve(createContext({ key3: true }), KeyCode.KeyX, 'third'); + test('resolve command - 3', () => { + testKbLookupByCommand('third', [KeyCode.KeyX]); + testResolve(createContext({ key3: true }), KeyCode.KeyX, 'third'); + }); - testKey('fourth', []); + test('resolve command - 4', () => { + testKbLookupByCommand('fourth', []); + }); - testKey('fifth', [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyY, KeyCode.KeyZ)]); - testResolve(createContext({}), KeyChord(KeyMod.CtrlCmd | KeyCode.KeyY, KeyCode.KeyZ), 'fifth'); + test('resolve command - 5', () => { + testKbLookupByCommand('fifth', [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyY, KeyCode.KeyZ)]); + testResolve(createContext({}), KeyChord(KeyMod.CtrlCmd | KeyCode.KeyY, KeyCode.KeyZ), 'fifth'); + }); - testKey('seventh', [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyK)]); - testResolve(createContext({}), KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyK), 'seventh'); + test('resolve command - 6', () => { + testKbLookupByCommand('seventh', [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyK)]); + testResolve(createContext({}), KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyK), 'seventh'); + }); - testKey('uncomment lines', [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyU)]); - testResolve(createContext({}), KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyU), 'uncomment lines'); + test('resolve command - 7', () => { + testKbLookupByCommand('uncomment lines', [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyU)]); + testResolve(createContext({}), KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyU), 'uncomment lines'); + }); - testKey('comment lines', [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyC)]); - testResolve(createContext({}), KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyC), 'comment lines'); + test('resolve command - 8', () => { + testKbLookupByCommand('comment lines', [KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyC)]); + testResolve(createContext({}), KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyC), 'comment lines'); + }); - testKey('unreachablechord', []); + test('resolve command - 9', () => { + testKbLookupByCommand('unreachablechord', []); + }); - testKey('eleven', [KeyMod.CtrlCmd | KeyCode.KeyG]); - testResolve(createContext({}), KeyMod.CtrlCmd | KeyCode.KeyG, 'eleven'); + test('resolve command - 10', () => { + testKbLookupByCommand('eleven', [KeyMod.CtrlCmd | KeyCode.KeyG]); + testResolve(createContext({}), KeyMod.CtrlCmd | KeyCode.KeyG, 'eleven'); + }); - testKey('sixth', []); + test('resolve command - 11', () => { + testKbLookupByCommand('sixth', []); + }); - testKey('long multi chord', [[KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyA, KeyCode.KeyB]]); - testResolve(createContext({}), [KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyA, KeyCode.KeyB], 'long multi chord'); + test('resolve command - 12', () => { + testKbLookupByCommand('long multi chord', [[KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyA, KeyCode.KeyB]]); + testResolve(createContext({}), [KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyA, KeyCode.KeyB], 'long multi chord'); + }); + + const emptyContext = createContext({}); + + test('KBs having common prefix - the one defined later is returned', () => { + testResolve(emptyContext, [KeyMod.CtrlCmd | KeyCode.KeyB, KeyMod.CtrlCmd | KeyCode.KeyC, KeyCode.KeyI], 'long-multi-chord-2'); + }); }); }); diff --git a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts index dcb6155d25f..d700bf491df 100644 --- a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts +++ b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts @@ -8,7 +8,7 @@ import { ResolvedKeybinding, KeyCodeChord, Keybinding } from 'vs/base/common/key import { OS } from 'vs/base/common/platform'; import { ContextKeyExpression, ContextKeyValue, IContextKey, IContextKeyChangeEvent, IContextKeyService, IContextKeyServiceTarget, IScopedContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; -import { IResolveResult } from 'vs/platform/keybinding/common/keybindingResolver'; +import { ResolutionResult } from 'vs/platform/keybinding/common/keybindingResolver'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; @@ -135,7 +135,7 @@ export class MockKeybindingService implements IKeybindingService { return 0; } - public softDispatch(keybinding: IKeyboardEvent, target: IContextKeyServiceTarget): IResolveResult | null { + public softDispatch(keybinding: IKeyboardEvent, target: IContextKeyServiceTarget): ResolutionResult | null { return null; } diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 62e4fdf6b7e..992f2f2f09e 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -26,6 +26,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator, IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; import { Registry } from 'vs/platform/registry/common/platform'; import { IStyleOverride, defaultFindWidgetStyles, defaultListStyles, getListStyles } from 'vs/platform/theme/browser/defaultStyles'; @@ -809,7 +810,7 @@ function createKeyboardNavigationEventFilter(keybindingService: IKeybindingServi const result = keybindingService.softDispatch(event, event.target); - if (result?.enterMultiChord) { + if (result?.kind === ResultKind.MoreChordsNeeded) { inMultiChord = true; return false; } diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 685dc0a1341..2763ae394f4 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -1054,10 +1054,6 @@ class QuickPick extends QuickInput implements IQuickPi this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked(); this.ui.visibleCount.setCount(this.ui.list.getVisibleCount()); this.ui.count.setCount(this.ui.list.getCheckedCount()); - // Ensure no item is focused when using a screenreader when items update (#57501 & #166920 & #176848) - if (this.ui.isScreenReaderOptimized() && ariaLabel && visibilities.inputBox) { - this._itemActivation = ItemActivation.NONE; - } switch (this._itemActivation) { case ItemActivation.NONE: this._itemActivation = ItemActivation.FIRST; // only valid once, then unset @@ -1364,11 +1360,6 @@ export class QuickInputController extends Disposable { } }, 0); })); - this._register(list.onDidChangeFocus(() => { - if (this.comboboxAccessibility) { - this.getUI().inputBox.setAttribute('aria-activedescendant', this.getUI().list.getActiveDescendant() || ''); - } - })); const focusTracker = dom.trackFocus(container); this._register(focusTracker); @@ -1733,12 +1724,14 @@ export class QuickInputController extends Disposable { ui.inputBox.setAttribute('role', 'combobox'); ui.inputBox.setAttribute('aria-haspopup', 'true'); ui.inputBox.setAttribute('aria-autocomplete', 'list'); - ui.inputBox.setAttribute('aria-activedescendant', ui.list.getActiveDescendant() || ''); + ui.inputBox.setAttribute('aria-controls', ui.list.id); + ui.inputBox.setAttribute('aria-expanded', 'true'); } else { ui.inputBox.removeAttribute('role'); ui.inputBox.removeAttribute('aria-haspopup'); ui.inputBox.removeAttribute('aria-autocomplete'); - ui.inputBox.removeAttribute('aria-activedescendant'); + ui.inputBox.removeAttribute('aria-controls'); + ui.inputBox.removeAttribute('aria-expanded'); } } } diff --git a/src/vs/platform/quickinput/browser/quickInputList.ts b/src/vs/platform/quickinput/browser/quickInputList.ts index aac73b3a9a1..19bb8d2e36c 100644 --- a/src/vs/platform/quickinput/browser/quickInputList.ts +++ b/src/vs/platform/quickinput/browser/quickInputList.ts @@ -286,6 +286,7 @@ export class QuickInputList { readonly id: string; private container: HTMLElement; + private liveElement: HTMLElement; private list: List; private inputElements: Array = []; private elements: ListElement[] = []; @@ -325,6 +326,10 @@ export class QuickInputList { ) { this.id = id; this.container = dom.append(this.parent, $('.quick-input-list')); + this.liveElement = dom.append(this.container, $('div', { + 'aria-live': 'polite', + 'aria-relevant': 'additions' + })); const delegate = new ListElementDelegate(); const accessibilityProvider = new QuickInputAccessibilityProvider(); @@ -337,6 +342,19 @@ export class QuickInputList { } as IListOptions); this.list.getHTMLElement().id = id; this.disposables.push(this.list); + this.disposables.push(this.list.onDidChangeFocus(e => { + const item = $('div', { + 'aria-labelledby': this.getActiveDescendant() ?? '' + }); + if (this.liveElement.hasChildNodes()) { + dom.reset(this.liveElement, item); + } else { + // give NVDA time to register that the newly created live region - ref https://github.com/nvaccess/nvda/issues/8873 + setTimeout(() => { + dom.reset(this.liveElement, item); + }, 500); + } + })); this.disposables.push(this.list.onKeyDown(e => { const event = new StandardKeyboardEvent(e); switch (event.keyCode) { diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index cadd4a0dd7b..5dd48d144af 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -224,7 +224,7 @@ export class Win32UpdateService extends AbstractUpdateService { }); const readyMutexName = `${this.productService.win32MutexName}-ready`; - const mutex = await import('windows-mutex'); + const mutex = await import('@vscode/windows-mutex'); // poll for mutex-ready pollUntil(() => mutex.isActive(readyMutexName)) diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index 269b9af1bd9..cb7929d181d 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -29,6 +29,7 @@ import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/wo import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; +import { ResolutionResult, ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; class InspectContextKeysAction extends Action2 { @@ -259,7 +260,7 @@ class ToggleScreencastModeAction extends Action2 { const shortcut = keybindingService.softDispatch(event, event.target); // Hide the single arrow key pressed - if (shortcut?.commandId && configurationService.getValue('screencastMode.hideSingleEditorCursorMoves') && ( + if (shortcut && shortcut.kind === ResultKind.KbFound && shortcut.commandId && configurationService.getValue('screencastMode.hideSingleEditorCursorMoves') && ( ['cursorLeft', 'cursorRight', 'cursorUp', 'cursorDown'].includes(shortcut.commandId)) ) { return; @@ -278,7 +279,7 @@ class ToggleScreencastModeAction extends Action2 { const format = configurationService.getValue<'keys' | 'command' | 'commandWithGroup' | 'commandAndKeys' | 'commandWithGroupAndKeys'>('screencastMode.keyboardShortcutsFormat'); const keybinding = keybindingService.resolveKeyboardEvent(event); - const command = shortcut?.commandId ? MenuRegistry.getCommand(shortcut.commandId) : null; + const command = (this._isKbFound(shortcut) && shortcut.commandId) ? MenuRegistry.getCommand(shortcut.commandId) : null; let titleLabel = ''; let keyLabel: string | undefined | null = keybinding.getLabel(); @@ -290,7 +291,7 @@ class ToggleScreencastModeAction extends Action2 { titleLabel = `${typeof command.category === 'string' ? command.category : command.category.value}: ${titleLabel} `; } - if (shortcut?.commandId) { + if (this._isKbFound(shortcut) && shortcut.commandId) { const keybindings = keybindingService.lookupKeybindings(shortcut.commandId) .filter(k => k.getLabel()?.endsWith(keyLabel ?? '')); @@ -306,7 +307,7 @@ class ToggleScreencastModeAction extends Action2 { append(keyboardMarker, $('span.title', {}, `${titleLabel} `)); } - if (onlyKeyboardShortcuts || !titleLabel || shortcut?.commandId && (format === 'keys' || format === 'commandAndKeys' || format === 'commandWithGroupAndKeys')) { + if (onlyKeyboardShortcuts || !titleLabel || (this._isKbFound(shortcut) && shortcut.commandId) && (format === 'keys' || format === 'commandAndKeys' || format === 'commandWithGroupAndKeys')) { // Fix label for arrow keys keyLabel = keyLabel?.replace('UpArrow', '↑') ?.replace('DownArrow', '↓') @@ -322,6 +323,10 @@ class ToggleScreencastModeAction extends Action2 { ToggleScreencastModeAction.disposable = disposables; } + + private _isKbFound(resolutionResult: ResolutionResult | null): resolutionResult is { kind: ResultKind.KbFound; commandId: string | null; commandArgs: any; isBubble: boolean } { + return resolutionResult !== null && resolutionResult.kind === ResultKind.KbFound; + } } class LogStorageAction extends Action2 { diff --git a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts index 64dc789c045..1dbc24af2da 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts @@ -19,6 +19,7 @@ import { fromNow } from 'vs/base/common/date'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; import { defaultButtonStyles, defaultCheckboxStyles, defaultDialogStyles, defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; export class BrowserDialogHandler extends AbstractDialogHandler { @@ -127,7 +128,7 @@ export class BrowserDialogHandler extends AbstractDialogHandler { type: this.getDialogType(type), keyEventProcessor: (event: StandardKeyboardEvent) => { const resolved = this.keybindingService.softDispatch(event, this.layoutService.container); - if (resolved?.commandId) { + if (resolved && resolved.kind === ResultKind.KbFound && resolved.commandId) { if (BrowserDialogHandler.ALLOWABLE_COMMANDS.indexOf(resolved.commandId) === -1) { EventHelper.stop(event, true); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 85c116e3115..70d75777da4 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -88,6 +88,7 @@ import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { TerminalExtensionsRegistry } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; +import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; const enum Constants { /** @@ -941,7 +942,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Respect chords if the allowChords setting is set and it's not Escape. Escape is // handled specially for Zen Mode's Escape, Escape chord, plus it's important in // terminals generally - const isValidChord = resolveResult?.enterMultiChord && this._configHelper.config.allowChords && event.key !== 'Escape'; + const isValidChord = resolveResult?.kind === ResultKind.MoreChordsNeeded && this._configHelper.config.allowChords && event.key !== 'Escape'; if (this._keybindingService.inChordMode || isValidChord) { event.preventDefault(); return false; @@ -961,7 +962,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // for keyboard events that resolve to commands described // within commandsToSkipShell, either alert or skip processing by xterm.js - if (resolveResult && resolveResult.commandId && this._skipTerminalCommands.some(k => k === resolveResult.commandId) && !this._configHelper.config.sendKeybindingsToShell) { + if (resolveResult && resolveResult.kind === ResultKind.KbFound && resolveResult.commandId && this._skipTerminalCommands.some(k => k === resolveResult.commandId) && !this._configHelper.config.sendKeybindingsToShell) { // don't alert when terminal is opened or closed if (this._storageService.getBoolean(SHOW_TERMINAL_CONFIG_PROMPT_KEY, StorageScope.APPLICATION, true) && this._hasHadInput && diff --git a/src/vs/workbench/contrib/webview/browser/pre/fake.html b/src/vs/workbench/contrib/webview/browser/pre/fake.html index 726a69397f8..960d1186be5 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/fake.html +++ b/src/vs/workbench/contrib/webview/browser/pre/fake.html @@ -3,7 +3,6 @@ - Fake diff --git a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html index 471bebaa5fe..6e8462b4c2c 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html @@ -7,8 +7,6 @@ - - diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index 514dfccb746..251b047c7c2 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index.html @@ -10,8 +10,6 @@ - - diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index f19d36af357..78815028817 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -311,8 +311,7 @@ const apiMenus: IAPIMenu[] = [ { key: 'editor/lineNumber/context', id: MenuId.EditorLineNumberContext, - description: localize('editorLineNumberContext', "The contributed editor line number context menu"), - proposed: 'contribEditorLineNumberMenu' + description: localize('editorLineNumberContext', "The contributed editor line number context menu") }, { key: 'mergeEditor/result/title', diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 0b80f5c768c..6d3ac59883e 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -14,7 +14,6 @@ export const allApiProposals = Object.freeze({ contribCommentThreadAdditionalMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentThreadAdditionalMenu.d.ts', contribEditSessions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditSessions.d.ts', contribEditorContentMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditorContentMenu.d.ts', - contribEditorLineNumberMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditorLineNumberMenu.d.ts', contribLabelFormatterWorkspaceTooltip: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribLabelFormatterWorkspaceTooltip.d.ts', contribMenuBarHome: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMenuBarHome.d.ts', contribMergeEditorMenus: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMergeEditorMenus.d.ts', diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 56cbf5259aa..def35449ea6 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -25,6 +25,7 @@ import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { stripIcons } from 'vs/base/common/iconLabels'; import { defaultButtonStyles, defaultCheckboxStyles, defaultDialogStyles, defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; export class ProgressService extends Disposable implements IProgressService { @@ -556,7 +557,7 @@ export class ProgressService extends Disposable implements IProgressService { disableDefaultAction: options.sticky, keyEventProcessor: (event: StandardKeyboardEvent) => { const resolved = this.keybindingService.softDispatch(event, this.layoutService.container); - if (resolved?.commandId) { + if (resolved && resolved.kind === ResultKind.KbFound && resolved.commandId) { if (!allowableCommands.includes(resolved.commandId)) { EventHelper.stop(event, true); } diff --git a/src/vs/workbench/services/search/test/node/fixtures/index.html b/src/vs/workbench/services/search/test/node/fixtures/index.html index bccd24d9272..165a4ecbccd 100644 --- a/src/vs/workbench/services/search/test/node/fixtures/index.html +++ b/src/vs/workbench/services/search/test/node/fixtures/index.html @@ -1,7 +1,6 @@ - Strada diff --git a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts index 50fd21a0883..c8095086203 100644 --- a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts @@ -157,7 +157,7 @@ flakySuite('TextSearch-integration', function () { contentPattern: { pattern: 'e' } }; - return doSearchTest(config, 788); + return doSearchTest(config, 781); }); test('Text: e (with excludes)', () => { @@ -167,7 +167,7 @@ flakySuite('TextSearch-integration', function () { excludePattern: { '**/examples': true } }; - return doSearchTest(config, 394); + return doSearchTest(config, 387); }); test('Text: e (with includes)', () => { @@ -344,12 +344,12 @@ flakySuite('TextSearch-integration', function () { return doSearchTest(config, 4).then(results => { assert.strictEqual(results.length, 4); - assert.strictEqual((results[0].results![0]).lineNumber, 25); + assert.strictEqual((results[0].results![0]).lineNumber, 24); assert.strictEqual((results[0].results![0]).text, ' compiler.addUnit(prog,"input.ts");'); // assert.strictEqual((results[1].results[0]).preview.text, ' compiler.typeCheck();\n'); // See https://github.com/BurntSushi/ripgrep/issues/1095 - assert.strictEqual((results[2].results![0]).lineNumber, 27); + assert.strictEqual((results[2].results![0]).lineNumber, 26); assert.strictEqual((results[2].results![0]).text, ' compiler.emit();'); - assert.strictEqual((results[3].results![0]).lineNumber, 28); + assert.strictEqual((results[3].results![0]).lineNumber, 27); assert.strictEqual((results[3].results![0]).text, ''); }); }); diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 863434bb693..115daed94aa 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -6499,7 +6499,7 @@ declare module 'vscode' { /** * Outputs the given trace message to the channel. Use this method to log verbose information. * - * The message is only loggeed if the channel is configured to display {@link LogLevel.Trace trace} log level. + * The message is only logged if the channel is configured to display {@link LogLevel.Trace trace} log level. * * @param message trace message to log */ @@ -6508,7 +6508,7 @@ declare module 'vscode' { /** * Outputs the given debug message to the channel. * - * The message is only loggeed if the channel is configured to display {@link LogLevel.Debug debug} log level or lower. + * The message is only logged if the channel is configured to display {@link LogLevel.Debug debug} log level or lower. * * @param message debug message to log */ @@ -6517,7 +6517,7 @@ declare module 'vscode' { /** * Outputs the given information message to the channel. * - * The message is only loggeed if the channel is configured to display {@link LogLevel.Info info} log level or lower. + * The message is only logged if the channel is configured to display {@link LogLevel.Info info} log level or lower. * * @param message info message to log */ @@ -6526,7 +6526,7 @@ declare module 'vscode' { /** * Outputs the given warning message to the channel. * - * The message is only loggeed if the channel is configured to display {@link LogLevel.Warning warning} log level or lower. + * The message is only logged if the channel is configured to display {@link LogLevel.Warning warning} log level or lower. * * @param message warning message to log */ @@ -6535,7 +6535,7 @@ declare module 'vscode' { /** * Outputs the given error or error message to the channel. * - * The message is only loggeed if the channel is configured to display {@link LogLevel.Error error} log level or lower. + * The message is only logged if the channel is configured to display {@link LogLevel.Error error} log level or lower. * * @param error Error or error message to log */ diff --git a/yarn.lock b/yarn.lock index 7589e4449b9..2eff8542500 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1153,11 +1153,6 @@ resolved "https://registry.yarnpkg.com/@types/windows-foreground-love/-/windows-foreground-love-0.3.0.tgz#26bc230b2568aa7ab7c56d35bb5653c0a6965a42" integrity sha512-tFUVA/fiofNqOh6lZlymvQiQYPY+cZXZPR9mn9wN6/KS8uwx0zgH4Ij/jmFyRYr+x+DGZWEIeknS2BMi7FZJAQ== -"@types/windows-mutex@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@types/windows-mutex/-/windows-mutex-0.4.0.tgz#d27070418aa26047c6c860c704952ff26aeb961b" - integrity sha512-zUMH4nutIURLARU6f10Ls6XcZWhUkwmzVEALs26dt1ZbaLv/qxsGdYiePUbwhQmrQUp3EPZ1HxopWpzzC8ot5A== - "@types/windows-process-tree@^0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@types/windows-process-tree/-/windows-process-tree-0.2.0.tgz#2fa205c838a8ef0a07697cd747c954653978d22c" @@ -1391,6 +1386,14 @@ dependencies: node-addon-api "^3.0.2" +"@vscode/windows-mutex@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@vscode/windows-mutex/-/windows-mutex-0.4.2.tgz#3f796896c3bc2cf32bfe7148e608edb0cb7247db" + integrity sha512-7MSBH22cI7OwxXImjJ3VJ7QeHrEbumtyi/xqTgv6xqZCIZfUk/eu5MMBD2Vkehut8dGZU4xIjqt5AGdm0uPixQ== + dependencies: + bindings "^1.2.1" + nan "^2.14.0" + "@vscode/windows-registry@1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@vscode/windows-registry/-/windows-registry-1.0.6.tgz#8b9fb9a55bf5a0be4ea11849c45ae94c6910e3e4" @@ -10842,14 +10845,6 @@ windows-foreground-love@0.5.0: resolved "https://registry.yarnpkg.com/windows-foreground-love/-/windows-foreground-love-0.5.0.tgz#7672b04eb05f934a6543cacdc3cd16ff34e3cb10" integrity sha512-yjBwmKEmQBDk3Z7yg/U9hizGWat8C6Pe4MQWl5bN6mvPU81Bt6HV2k/6mGlK3ETJLW1hCLhYx2wcGh+ykUUCyA== -windows-mutex@0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/windows-mutex/-/windows-mutex-0.4.1.tgz#2eccdfc312e15e7f212fb16280f060fc6b5f00cd" - integrity sha512-RW1xN6yzw8hAcfy1BKVClhJZg/PzuNz4Qz+CNhYmmdzJiwKSU9CqvVcmAvNrtdinNkXfSqTZVBVh5kUATg6xOA== - dependencies: - bindings "^1.2.1" - nan "^2.14.0" - windows-process-tree@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/windows-process-tree/-/windows-process-tree-0.4.0.tgz#31ac49c5da557e628ce7e37a5800972173d3349a"