mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 17:19:48 +01:00
keybindingResolver: refactor: use a discriminated union to
make it easier to reason about keybinding resolution result
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user