Skip to content

Commit fa3a501

Browse files
committed
Add command to help with troubleshooting keybindings
1 parent 56adc7d commit fa3a501

15 files changed

Lines changed: 158 additions & 35 deletions

File tree

src/vs/base/browser/keyboardEvent.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,40 @@ const altKeyMod = KeyMod.Alt;
205205
const shiftKeyMod = KeyMod.Shift;
206206
const metaKeyMod = (platform.isMacintosh ? KeyMod.CtrlCmd : KeyMod.WinCtrl);
207207

208+
export function printKeyboardEvent(e: KeyboardEvent): string {
209+
let modifiers: string[] = [];
210+
if (e.ctrlKey) {
211+
modifiers.push(`ctrl`);
212+
}
213+
if (e.shiftKey) {
214+
modifiers.push(`shift`);
215+
}
216+
if (e.altKey) {
217+
modifiers.push(`alt`);
218+
}
219+
if (e.metaKey) {
220+
modifiers.push(`meta`);
221+
}
222+
return `modifiers: [${modifiers.join(',')}], code: ${e.code}, keyCode: ${e.keyCode}, key: ${e.key}`;
223+
}
224+
225+
export function printStandardKeyboardEvent(e: StandardKeyboardEvent): string {
226+
let modifiers: string[] = [];
227+
if (e.ctrlKey) {
228+
modifiers.push(`ctrl`);
229+
}
230+
if (e.shiftKey) {
231+
modifiers.push(`shift`);
232+
}
233+
if (e.altKey) {
234+
modifiers.push(`alt`);
235+
}
236+
if (e.metaKey) {
237+
modifiers.push(`meta`);
238+
}
239+
return `modifiers: [${modifiers.join(',')}], code: ${e.code}, keyCode: ${e.keyCode} ('${KeyCodeUtils.toString(e.keyCode)}')`;
240+
}
241+
208242
export class StandardKeyboardEvent implements IKeyboardEvent {
209243

210244
readonly _standardKeyboardEventBrand = true;

src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ function createCodeActionKeybinding(keycode: KeyCode, command: string, commandAr
8989
command,
9090
commandArgs,
9191
undefined,
92-
false);
92+
false,
93+
null);
9394
}
9495

src/vs/editor/standalone/browser/simpleServices.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,8 @@ export class StandaloneKeybindingService extends AbstractKeybindingService {
326326
command: commandId,
327327
when: when,
328328
weight1: 1000,
329-
weight2: 0
329+
weight2: 0,
330+
extensionId: null
330331
});
331332

332333
toDispose.add(toDisposable(() => {
@@ -357,7 +358,7 @@ export class StandaloneKeybindingService extends AbstractKeybindingService {
357358
if (!this._cachedResolver) {
358359
const defaults = this._toNormalizedKeybindingItems(KeybindingsRegistry.getDefaultKeybindings(), true);
359360
const overrides = this._toNormalizedKeybindingItems(this._dynamicKeybindings, false);
360-
this._cachedResolver = new KeybindingResolver(defaults, overrides);
361+
this._cachedResolver = new KeybindingResolver(defaults, overrides, (str) => this._log(str));
361362
}
362363
return this._cachedResolver;
363364
}
@@ -374,11 +375,11 @@ export class StandaloneKeybindingService extends AbstractKeybindingService {
374375

375376
if (!keybinding) {
376377
// This might be a removal keybinding item in user settings => accept it
377-
result[resultLen++] = new ResolvedKeybindingItem(undefined, item.command, item.commandArgs, when, isDefault);
378+
result[resultLen++] = new ResolvedKeybindingItem(undefined, item.command, item.commandArgs, when, isDefault, null);
378379
} else {
379380
const resolvedKeybindings = this.resolveKeybinding(keybinding);
380381
for (const resolvedKeybinding of resolvedKeybindings) {
381-
result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault);
382+
result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault, null);
382383
}
383384
}
384385
}

src/vs/platform/keybinding/common/abstractKeybindingService.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK
3535
private _currentChord: CurrentChord | null;
3636
private _currentChordChecker: IntervalTimer;
3737
private _currentChordStatusMessage: IDisposable | null;
38+
protected _logging: boolean;
3839

3940
public get inChordMode(): boolean {
4041
return !!this._currentChord;
@@ -52,6 +53,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK
5253
this._currentChord = null;
5354
this._currentChordChecker = new IntervalTimer();
5455
this._currentChordStatusMessage = null;
56+
this._logging = false;
5557
}
5658

5759
public dispose(): void {
@@ -71,6 +73,19 @@ export abstract class AbstractKeybindingService extends Disposable implements IK
7173
return '';
7274
}
7375

76+
public toggleLogging(): boolean {
77+
this._logging = !this._logging;
78+
return this._logging;
79+
}
80+
81+
protected _log(str: string): void {
82+
if (this._logging) {
83+
this._logService.info(`[KeybindingService]: ${str}`);
84+
} else {
85+
this._logService.trace(`[KeybindingService]: ${str}`);
86+
}
87+
}
88+
7489
public getDefaultKeybindings(): readonly ResolvedKeybindingItem[] {
7590
return this._getResolver().getDefaultKeybindings();
7691
}
@@ -170,6 +185,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK
170185
}
171186
const [firstPart,] = keybinding.getDispatchParts();
172187
if (firstPart === null) {
188+
this._log(`\\ Keyboard event cannot be dispatched.`);
173189
// cannot be dispatched, probably only modifier keys
174190
return shouldPreventDefault;
175191
}

src/vs/platform/keybinding/common/keybinding.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ export interface IKeybindingService {
103103

104104
registerSchemaContribution(contribution: KeybindingsSchemaContribution): void;
105105

106+
toggleLogging(): boolean;
107+
106108
_dumpDebugInfo(): string;
107109
_dumpDebugInfoJSON(): string;
108110
}

src/vs/platform/keybinding/common/keybindingResolver.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,19 @@ export interface IResolveResult {
2020
}
2121

2222
export class KeybindingResolver {
23+
private readonly _log: (str: string) => void;
2324
private readonly _defaultKeybindings: ResolvedKeybindingItem[];
2425
private readonly _keybindings: ResolvedKeybindingItem[];
2526
private readonly _defaultBoundCommands: Map<string, boolean>;
2627
private readonly _map: Map<string, ResolvedKeybindingItem[]>;
2728
private readonly _lookupMap: Map<string, ResolvedKeybindingItem[]>;
2829

29-
constructor(defaultKeybindings: ResolvedKeybindingItem[], overrides: ResolvedKeybindingItem[]) {
30+
constructor(
31+
defaultKeybindings: ResolvedKeybindingItem[],
32+
overrides: ResolvedKeybindingItem[],
33+
log: (str: string) => void
34+
) {
35+
this._log = log;
3036
this._defaultKeybindings = defaultKeybindings;
3137

3238
this._defaultBoundCommands = new Map<string, boolean>();
@@ -254,6 +260,7 @@ export class KeybindingResolver {
254260
}
255261

256262
public resolve(context: IContext, currentChord: string | null, keypress: string): IResolveResult | null {
263+
this._log(`| Resolving ${keypress}${currentChord ? ` chorded from ${currentChord}` : ``}`);
257264
let lookupMap: ResolvedKeybindingItem[] | null = null;
258265

259266
if (currentChord !== null) {
@@ -262,6 +269,7 @@ export class KeybindingResolver {
262269
const candidates = this._map.get(currentChord);
263270
if (typeof candidates === 'undefined') {
264271
// No chords starting with `currentChord`
272+
this._log(`\\ No keybinding entries.`);
265273
return null;
266274
}
267275

@@ -277,6 +285,7 @@ export class KeybindingResolver {
277285
const candidates = this._map.get(keypress);
278286
if (typeof candidates === 'undefined') {
279287
// No bindings with `keypress`
288+
this._log(`\\ No keybinding entries.`);
280289
return null;
281290
}
282291

@@ -285,11 +294,13 @@ export class KeybindingResolver {
285294

286295
let result = this._findCommand(context, lookupMap);
287296
if (!result) {
297+
this._log(`\\ From ${lookupMap.length} keybinding entries, no when clauses matched the context.`);
288298
return null;
289299
}
290300

291301
// TODO@chords
292302
if (currentChord === null && result.keypressParts.length > 1 && result.keypressParts[1] !== null) {
303+
this._log(`\\ From ${lookupMap.length} keybinding entries, matched chord, when: ${printWhenExplanation(result.when)}, source: ${printSourceExplanation(result)}.`);
293304
return {
294305
enterChord: true,
295306
leaveChord: false,
@@ -299,6 +310,7 @@ export class KeybindingResolver {
299310
};
300311
}
301312

313+
this._log(`\\ From ${lookupMap.length} keybinding entries, matched ${result.command}, when: ${printWhenExplanation(result.when)}, source: ${printSourceExplanation(result)}.`);
302314
return {
303315
enterChord: false,
304316
leaveChord: result.keypressParts.length > 1,
@@ -362,3 +374,23 @@ export class KeybindingResolver {
362374
return unboundCommands;
363375
}
364376
}
377+
378+
function printWhenExplanation(when: ContextKeyExpression | undefined): string {
379+
if (!when) {
380+
return `no when condition`;
381+
}
382+
return `${when.serialize()}`;
383+
}
384+
385+
function printSourceExplanation(kb: ResolvedKeybindingItem): string {
386+
if (kb.isDefault) {
387+
if (kb.extensionId) {
388+
return `built-in extension ${kb.extensionId}`;
389+
}
390+
return `built-in`;
391+
}
392+
if (kb.extensionId) {
393+
return `user extension ${kb.extensionId}`;
394+
}
395+
return `user`;
396+
}

src/vs/platform/keybinding/common/keybindingsRegistry.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface IKeybindingItem {
1616
when: ContextKeyExpression | null | undefined;
1717
weight1: number;
1818
weight2: number;
19+
extensionId: string | null;
1920
}
2021

2122
export interface IKeybindings {
@@ -51,6 +52,7 @@ export interface IKeybindingRule2 {
5152
args?: any;
5253
weight: number;
5354
when: ContextKeyExpression | undefined;
55+
extensionId?: string;
5456
}
5557

5658
export const enum KeybindingWeight {
@@ -161,7 +163,8 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry {
161163
commandArgs: rule.args,
162164
when: rule.when,
163165
weight1: rule.weight,
164-
weight2: 0
166+
weight2: 0,
167+
extensionId: rule.extensionId || null
165168
};
166169
}
167170
}
@@ -219,7 +222,8 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry {
219222
commandArgs: commandArgs,
220223
when: when,
221224
weight1: weight1,
222-
weight2: weight2
225+
weight2: weight2,
226+
extensionId: null
223227
});
224228
this._cachedMergedKeybindings = null;
225229
}

src/vs/platform/keybinding/common/resolvedKeybindingItem.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,17 @@ export class ResolvedKeybindingItem {
1717
public readonly commandArgs: any;
1818
public readonly when: ContextKeyExpression | undefined;
1919
public readonly isDefault: boolean;
20+
public readonly extensionId: string | null;
2021

21-
constructor(resolvedKeybinding: ResolvedKeybinding | undefined, command: string | null, commandArgs: any, when: ContextKeyExpression | undefined, isDefault: boolean) {
22+
constructor(resolvedKeybinding: ResolvedKeybinding | undefined, command: string | null, commandArgs: any, when: ContextKeyExpression | undefined, isDefault: boolean, extensionId: string | null) {
2223
this.resolvedKeybinding = resolvedKeybinding;
2324
this.keypressParts = resolvedKeybinding ? removeElementsAfterNulls(resolvedKeybinding.getDispatchParts()) : [];
2425
this.bubble = (command ? command.charCodeAt(0) === CharCode.Caret : false);
2526
this.command = this.bubble ? command!.substr(1) : command;
2627
this.commandArgs = commandArgs;
2728
this.when = when;
2829
this.isDefault = isDefault;
30+
this.extensionId = extensionId;
2931
}
3032
}
3133

src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ suite('AbstractKeybindingService', () => {
168168
setFilter() { }
169169
};
170170

171-
let resolver = new KeybindingResolver(items, []);
171+
let resolver = new KeybindingResolver(items, [], () => { });
172172

173173
return new TestKeybindingService(resolver, contextKeyService, commandService, notificationService);
174174
};
@@ -190,7 +190,8 @@ suite('AbstractKeybindingService', () => {
190190
command,
191191
null,
192192
when,
193-
true
193+
true,
194+
null
194195
);
195196
}
196197

src/vs/platform/keybinding/test/common/keybindingResolver.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ suite('KeybindingResolver', () => {
2727
command,
2828
commandArgs,
2929
when,
30-
isDefault
30+
isDefault,
31+
null
3132
);
3233
}
3334

@@ -44,7 +45,7 @@ suite('KeybindingResolver', () => {
4445
assert.equal(KeybindingResolver.contextMatchesRules(createContext({ bar: 'baz' }), contextRules), true);
4546
assert.equal(KeybindingResolver.contextMatchesRules(createContext({ bar: 'bz' }), contextRules), false);
4647

47-
let resolver = new KeybindingResolver([keybindingItem], []);
48+
let resolver = new KeybindingResolver([keybindingItem], [], () => { });
4849
assert.equal(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(runtimeKeybinding))!.commandId, 'yes');
4950
assert.equal(resolver.resolve(createContext({ bar: 'bz' }), null, getDispatchStr(runtimeKeybinding)), null);
5051
});
@@ -56,7 +57,7 @@ suite('KeybindingResolver', () => {
5657
let contextRules = ContextKeyExpr.equals('bar', 'baz');
5758
let keybindingItem = kbItem(keybinding, 'yes', commandArgs, contextRules, true);
5859

59-
let resolver = new KeybindingResolver([keybindingItem], []);
60+
let resolver = new KeybindingResolver([keybindingItem], [], () => { });
6061
assert.equal(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(runtimeKeybinding))!.commandArgs, commandArgs);
6162
});
6263

@@ -307,7 +308,7 @@ suite('KeybindingResolver', () => {
307308
)
308309
];
309310

310-
let resolver = new KeybindingResolver(items, []);
311+
let resolver = new KeybindingResolver(items, [], () => { });
311312

312313
let testKey = (commandId: string, expectedKeys: number[]) => {
313314
// Test lookup

0 commit comments

Comments
 (0)