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/keybinding/common/abstractKeybindingService.ts b/src/vs/platform/keybinding/common/abstractKeybindingService.ts index aece3b261d6..f8aa3fd5708 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'; @@ -135,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()) { @@ -172,18 +175,28 @@ export abstract class AbstractKeybindingService extends Disposable implements IK }, 500); } - private _enterMultiChordMode(firstChord: string, keypressLabel: string | null): void { - this._currentChords.push({ 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.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 { @@ -298,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.length > 0) { - 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 3f14d059010..b9b0d18a688 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,7 +300,7 @@ export class KeybindingResolver { return items[items.length - 1]; } - public resolve(context: IContext, currentChords: string[] | null, keypress: string): IResolveResult | null { + public resolve(context: IContext, currentChords: string[] | null, keypress: string): ResolutionResult { this._log(`| Resolving ${keypress}${currentChords ? ` chorded from ${currentChords}` : ``}`); let lookupMap: ResolvedKeybindingItem[] | null = null; @@ -291,7 +310,7 @@ export class KeybindingResolver { if (candidates === undefined) { // No bindings with `keypress` this._log(`\\ No keybinding entries.`); - return null; + return NoMatchingKb; } lookupMap = candidates; @@ -301,7 +320,7 @@ export class KeybindingResolver { if (candidates === undefined) { // No chords starting with `currentChords` this._log(`\\ No keybinding entries.`); - return null; + return NoMatchingKb; } lookupMap = []; @@ -327,37 +346,19 @@ export class KeybindingResolver { 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) { + if (currentChords === null && result.chords.length > 1 && result.chords[1] !== null) { // first chord of a multi-chord KB matched 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) { + return MoreChordsNeeded; + } else if (currentChords !== null && currentChords.length + 1 < result.chords.length) { // prefix (ie, a sequence of chords) of a multi-chord KB matched, eg 2 out of 3 matched - still need one more to dispatch the KB 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 - }; + 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..e3397378558 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' }), null, getDispatchStr(runtimeKeybinding)); + assert.ok(r1.kind === ResultKind.KbFound); + assert.strictEqual(r1.commandId, 'yes'); + + const r2 = resolver.resolve(createContext({ bar: 'bz' }), null, getDispatchStr(runtimeKeybinding)); + assert.strictEqual(r2.kind, ResultKind.NoMatchingKb); }); test('resolve key with arguments', () => { @@ -62,7 +67,10 @@ 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' }), null, getDispatchStr(runtimeKeybinding)); + assert.ok(r.kind === ResultKind.KbFound); + assert.strictEqual(r.commandArgs, commandArgs); }); test('KeybindingResolver.handleRemovals simple 1', () => { @@ -408,28 +416,26 @@ suite('KeybindingResolver', () => { const expectedKeybinding = decodeKeybinding(_expectedKey, OS)!; let previousChord: string[] | null = null; + 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}`); + assert.ok(result.kind === ResultKind.MoreChordsNeeded, `Enters multi chord for ${commandId} at chord ${i}`); } if (previousChord === null) { previousChord = []; 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/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/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); }