keybindingResolver: refactor: use a discriminated union to

make it easier to reason about keybinding resolution result
This commit is contained in:
Ulugbek Abdullaev
2023-03-29 16:54:18 +02:00
parent 9a90445e3c
commit 02f8853ec7
11 changed files with 165 additions and 110 deletions
+2 -1
View File
@@ -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) {
@@ -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<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('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<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: resolveResult.commandId, from: 'keybinding', detail: userKeypress.getUserSettingsLabel() ?? undefined });
}
}
return shouldPreventDefault;
}
mightProducePrintableCharacter(event: IKeyboardEvent): boolean {
@@ -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;
@@ -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 {
@@ -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(<KeyCodeChord>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 = [];
@@ -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;
}
+2 -1
View File
@@ -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;
}
@@ -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 {
@@ -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);
}
@@ -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 &&
@@ -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);
}