未验证 提交 fa3a5015 编写于 作者: A Alex Dima

Add command to help with troubleshooting keybindings

上级 56adc7d9
......@@ -205,6 +205,40 @@ const altKeyMod = KeyMod.Alt;
const shiftKeyMod = KeyMod.Shift;
const metaKeyMod = (platform.isMacintosh ? KeyMod.CtrlCmd : KeyMod.WinCtrl);
export function printKeyboardEvent(e: KeyboardEvent): string {
let modifiers: string[] = [];
if (e.ctrlKey) {
modifiers.push(`ctrl`);
}
if (e.shiftKey) {
modifiers.push(`shift`);
}
if (e.altKey) {
modifiers.push(`alt`);
}
if (e.metaKey) {
modifiers.push(`meta`);
}
return `modifiers: [${modifiers.join(',')}], code: ${e.code}, keyCode: ${e.keyCode}, key: ${e.key}`;
}
export function printStandardKeyboardEvent(e: StandardKeyboardEvent): string {
let modifiers: string[] = [];
if (e.ctrlKey) {
modifiers.push(`ctrl`);
}
if (e.shiftKey) {
modifiers.push(`shift`);
}
if (e.altKey) {
modifiers.push(`alt`);
}
if (e.metaKey) {
modifiers.push(`meta`);
}
return `modifiers: [${modifiers.join(',')}], code: ${e.code}, keyCode: ${e.keyCode} ('${KeyCodeUtils.toString(e.keyCode)}')`;
}
export class StandardKeyboardEvent implements IKeyboardEvent {
readonly _standardKeyboardEventBrand = true;
......
......@@ -89,6 +89,7 @@ function createCodeActionKeybinding(keycode: KeyCode, command: string, commandAr
command,
commandArgs,
undefined,
false);
false,
null);
}
......@@ -326,7 +326,8 @@ export class StandaloneKeybindingService extends AbstractKeybindingService {
command: commandId,
when: when,
weight1: 1000,
weight2: 0
weight2: 0,
extensionId: null
});
toDispose.add(toDisposable(() => {
......@@ -357,7 +358,7 @@ export class StandaloneKeybindingService extends AbstractKeybindingService {
if (!this._cachedResolver) {
const defaults = this._toNormalizedKeybindingItems(KeybindingsRegistry.getDefaultKeybindings(), true);
const overrides = this._toNormalizedKeybindingItems(this._dynamicKeybindings, false);
this._cachedResolver = new KeybindingResolver(defaults, overrides);
this._cachedResolver = new KeybindingResolver(defaults, overrides, (str) => this._log(str));
}
return this._cachedResolver;
}
......@@ -374,11 +375,11 @@ export class StandaloneKeybindingService extends AbstractKeybindingService {
if (!keybinding) {
// This might be a removal keybinding item in user settings => accept it
result[resultLen++] = new ResolvedKeybindingItem(undefined, item.command, item.commandArgs, when, isDefault);
result[resultLen++] = new ResolvedKeybindingItem(undefined, item.command, item.commandArgs, when, isDefault, null);
} else {
const resolvedKeybindings = this.resolveKeybinding(keybinding);
for (const resolvedKeybinding of resolvedKeybindings) {
result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault);
result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault, null);
}
}
}
......
......@@ -35,6 +35,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK
private _currentChord: CurrentChord | null;
private _currentChordChecker: IntervalTimer;
private _currentChordStatusMessage: IDisposable | null;
protected _logging: boolean;
public get inChordMode(): boolean {
return !!this._currentChord;
......@@ -52,6 +53,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK
this._currentChord = null;
this._currentChordChecker = new IntervalTimer();
this._currentChordStatusMessage = null;
this._logging = false;
}
public dispose(): void {
......@@ -71,6 +73,19 @@ export abstract class AbstractKeybindingService extends Disposable implements IK
return '';
}
public toggleLogging(): boolean {
this._logging = !this._logging;
return this._logging;
}
protected _log(str: string): void {
if (this._logging) {
this._logService.info(`[KeybindingService]: ${str}`);
} else {
this._logService.trace(`[KeybindingService]: ${str}`);
}
}
public getDefaultKeybindings(): readonly ResolvedKeybindingItem[] {
return this._getResolver().getDefaultKeybindings();
}
......@@ -170,6 +185,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK
}
const [firstPart,] = keybinding.getDispatchParts();
if (firstPart === null) {
this._log(`\\ Keyboard event cannot be dispatched.`);
// cannot be dispatched, probably only modifier keys
return shouldPreventDefault;
}
......
......@@ -103,6 +103,8 @@ export interface IKeybindingService {
registerSchemaContribution(contribution: KeybindingsSchemaContribution): void;
toggleLogging(): boolean;
_dumpDebugInfo(): string;
_dumpDebugInfoJSON(): string;
}
......
......@@ -20,13 +20,19 @@ export interface IResolveResult {
}
export class KeybindingResolver {
private readonly _log: (str: string) => void;
private readonly _defaultKeybindings: ResolvedKeybindingItem[];
private readonly _keybindings: ResolvedKeybindingItem[];
private readonly _defaultBoundCommands: Map<string, boolean>;
private readonly _map: Map<string, ResolvedKeybindingItem[]>;
private readonly _lookupMap: Map<string, ResolvedKeybindingItem[]>;
constructor(defaultKeybindings: ResolvedKeybindingItem[], overrides: ResolvedKeybindingItem[]) {
constructor(
defaultKeybindings: ResolvedKeybindingItem[],
overrides: ResolvedKeybindingItem[],
log: (str: string) => void
) {
this._log = log;
this._defaultKeybindings = defaultKeybindings;
this._defaultBoundCommands = new Map<string, boolean>();
......@@ -254,6 +260,7 @@ export class KeybindingResolver {
}
public resolve(context: IContext, currentChord: string | null, keypress: string): IResolveResult | null {
this._log(`| Resolving ${keypress}${currentChord ? ` chorded from ${currentChord}` : ``}`);
let lookupMap: ResolvedKeybindingItem[] | null = null;
if (currentChord !== null) {
......@@ -262,6 +269,7 @@ export class KeybindingResolver {
const candidates = this._map.get(currentChord);
if (typeof candidates === 'undefined') {
// No chords starting with `currentChord`
this._log(`\\ No keybinding entries.`);
return null;
}
......@@ -277,6 +285,7 @@ export class KeybindingResolver {
const candidates = this._map.get(keypress);
if (typeof candidates === 'undefined') {
// No bindings with `keypress`
this._log(`\\ No keybinding entries.`);
return null;
}
......@@ -285,11 +294,13 @@ export class KeybindingResolver {
let result = this._findCommand(context, lookupMap);
if (!result) {
this._log(`\\ From ${lookupMap.length} keybinding entries, no when clauses matched the context.`);
return null;
}
// TODO@chords
if (currentChord === null && result.keypressParts.length > 1 && result.keypressParts[1] !== null) {
this._log(`\\ From ${lookupMap.length} keybinding entries, matched chord, when: ${printWhenExplanation(result.when)}, source: ${printSourceExplanation(result)}.`);
return {
enterChord: true,
leaveChord: false,
......@@ -299,6 +310,7 @@ export class KeybindingResolver {
};
}
this._log(`\\ From ${lookupMap.length} keybinding entries, matched ${result.command}, when: ${printWhenExplanation(result.when)}, source: ${printSourceExplanation(result)}.`);
return {
enterChord: false,
leaveChord: result.keypressParts.length > 1,
......@@ -362,3 +374,23 @@ export class KeybindingResolver {
return unboundCommands;
}
}
function printWhenExplanation(when: ContextKeyExpression | undefined): string {
if (!when) {
return `no when condition`;
}
return `${when.serialize()}`;
}
function printSourceExplanation(kb: ResolvedKeybindingItem): string {
if (kb.isDefault) {
if (kb.extensionId) {
return `built-in extension ${kb.extensionId}`;
}
return `built-in`;
}
if (kb.extensionId) {
return `user extension ${kb.extensionId}`;
}
return `user`;
}
......@@ -16,6 +16,7 @@ export interface IKeybindingItem {
when: ContextKeyExpression | null | undefined;
weight1: number;
weight2: number;
extensionId: string | null;
}
export interface IKeybindings {
......@@ -51,6 +52,7 @@ export interface IKeybindingRule2 {
args?: any;
weight: number;
when: ContextKeyExpression | undefined;
extensionId?: string;
}
export const enum KeybindingWeight {
......@@ -161,7 +163,8 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry {
commandArgs: rule.args,
when: rule.when,
weight1: rule.weight,
weight2: 0
weight2: 0,
extensionId: rule.extensionId || null
};
}
}
......@@ -219,7 +222,8 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry {
commandArgs: commandArgs,
when: when,
weight1: weight1,
weight2: weight2
weight2: weight2,
extensionId: null
});
this._cachedMergedKeybindings = null;
}
......
......@@ -17,8 +17,9 @@ export class ResolvedKeybindingItem {
public readonly commandArgs: any;
public readonly when: ContextKeyExpression | undefined;
public readonly isDefault: boolean;
public readonly extensionId: string | null;
constructor(resolvedKeybinding: ResolvedKeybinding | undefined, command: string | null, commandArgs: any, when: ContextKeyExpression | undefined, isDefault: boolean) {
constructor(resolvedKeybinding: ResolvedKeybinding | undefined, command: string | null, commandArgs: any, when: ContextKeyExpression | undefined, isDefault: boolean, extensionId: string | null) {
this.resolvedKeybinding = resolvedKeybinding;
this.keypressParts = resolvedKeybinding ? removeElementsAfterNulls(resolvedKeybinding.getDispatchParts()) : [];
this.bubble = (command ? command.charCodeAt(0) === CharCode.Caret : false);
......@@ -26,6 +27,7 @@ export class ResolvedKeybindingItem {
this.commandArgs = commandArgs;
this.when = when;
this.isDefault = isDefault;
this.extensionId = extensionId;
}
}
......
......@@ -168,7 +168,7 @@ suite('AbstractKeybindingService', () => {
setFilter() { }
};
let resolver = new KeybindingResolver(items, []);
let resolver = new KeybindingResolver(items, [], () => { });
return new TestKeybindingService(resolver, contextKeyService, commandService, notificationService);
};
......@@ -190,7 +190,8 @@ suite('AbstractKeybindingService', () => {
command,
null,
when,
true
true,
null
);
}
......
......@@ -27,7 +27,8 @@ suite('KeybindingResolver', () => {
command,
commandArgs,
when,
isDefault
isDefault,
null
);
}
......@@ -44,7 +45,7 @@ suite('KeybindingResolver', () => {
assert.equal(KeybindingResolver.contextMatchesRules(createContext({ bar: 'baz' }), contextRules), true);
assert.equal(KeybindingResolver.contextMatchesRules(createContext({ bar: 'bz' }), contextRules), false);
let resolver = new KeybindingResolver([keybindingItem], []);
let resolver = new KeybindingResolver([keybindingItem], [], () => { });
assert.equal(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(runtimeKeybinding))!.commandId, 'yes');
assert.equal(resolver.resolve(createContext({ bar: 'bz' }), null, getDispatchStr(runtimeKeybinding)), null);
});
......@@ -56,7 +57,7 @@ suite('KeybindingResolver', () => {
let contextRules = ContextKeyExpr.equals('bar', 'baz');
let keybindingItem = kbItem(keybinding, 'yes', commandArgs, contextRules, true);
let resolver = new KeybindingResolver([keybindingItem], []);
let resolver = new KeybindingResolver([keybindingItem], [], () => { });
assert.equal(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(runtimeKeybinding))!.commandArgs, commandArgs);
});
......@@ -307,7 +308,7 @@ suite('KeybindingResolver', () => {
)
];
let resolver = new KeybindingResolver(items, []);
let resolver = new KeybindingResolver(items, [], () => { });
let testKey = (commandId: string, expectedKeys: number[]) => {
// Test lookup
......
......@@ -136,6 +136,10 @@ export class MockKeybindingService implements IKeybindingService {
return false;
}
public toggleLogging(): boolean {
return false;
}
public _dumpDebugInfo(): string {
return '';
}
......
......@@ -6,7 +6,7 @@
import * as nls from 'vs/nls';
import * as browser from 'vs/base/browser/browser';
import * as dom from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { printKeyboardEvent, printStandardKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Emitter, Event } from 'vs/base/common/event';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { Keybinding, ResolvedKeybinding, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
......@@ -31,7 +31,7 @@ import { IUserKeybindingItem, KeybindingIO, OutputBuilder } from 'vs/workbench/s
import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { MenuRegistry } from 'vs/platform/actions/common/actions';
import { Action2, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { commandsExtensionPoint } from 'vs/workbench/api/common/menusExtensionPoint';
import { Disposable } from 'vs/base/common/lifecycle';
......@@ -48,6 +48,8 @@ import { ScanCode, ScanCodeUtils, IMMUTABLE_CODE_TO_KEY_CODE } from 'vs/base/com
import { flatten } from 'vs/base/common/arrays';
import { BrowserFeatures, KeyboardSupport } from 'vs/base/browser/canIUse';
import { ILogService } from 'vs/platform/log/common/log';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
interface ContributedKeyBinding {
command: string;
......@@ -236,7 +238,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
let keybindings: IKeybindingRule2[] = [];
for (let extension of extensions) {
this._handleKeybindingsExtensionPointUser(extension.description.isBuiltin, extension.value, extension.collector, keybindings);
this._handleKeybindingsExtensionPointUser(extension.description.identifier, extension.description.isBuiltin, extension.value, extension.collector, keybindings);
}
KeybindingsRegistry.setExtensionKeybindings(keybindings);
......@@ -247,8 +249,10 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
this._register(extensionService.onDidRegisterExtensions(() => this.updateSchema()));
this._register(dom.addDisposableListener(window, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
let keyEvent = new StandardKeyboardEvent(e);
let shouldPreventDefault = this._dispatch(keyEvent, keyEvent.target);
const keyEvent = new StandardKeyboardEvent(e);
this._log(`/ Received keydown event - ${printKeyboardEvent(e)}`);
this._log(`| Converted keydown event - ${printStandardKeyboardEvent(keyEvent)}`);
const shouldPreventDefault = this._dispatch(keyEvent, keyEvent.target);
if (shouldPreventDefault) {
keyEvent.preventDefault();
}
......@@ -345,7 +349,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
if (!this._cachedResolver) {
const defaults = this._resolveKeybindingItems(KeybindingsRegistry.getDefaultKeybindings(), true);
const overrides = this._resolveUserKeybindingItems(this.userKeybindings.keybindings.map((k) => KeybindingIO.readUserKeybindingItem(k)), false);
this._cachedResolver = new KeybindingResolver(defaults, overrides);
this._cachedResolver = new KeybindingResolver(defaults, overrides, (str) => this._log(str));
}
return this._cachedResolver;
}
......@@ -364,7 +368,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
const keybinding = item.keybinding;
if (!keybinding) {
// This might be a removal keybinding item in user settings => accept it
result[resultLen++] = new ResolvedKeybindingItem(undefined, item.command, item.commandArgs, when, isDefault);
result[resultLen++] = new ResolvedKeybindingItem(undefined, item.command, item.commandArgs, when, isDefault, item.extensionId);
} else {
if (this._assertBrowserConflicts(keybinding, item.command)) {
continue;
......@@ -373,7 +377,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
const resolvedKeybindings = this.resolveKeybinding(keybinding);
for (let i = resolvedKeybindings.length - 1; i >= 0; i--) {
const resolvedKeybinding = resolvedKeybindings[i];
result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault);
result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault, item.extensionId);
}
}
}
......@@ -388,11 +392,11 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
const parts = item.parts;
if (parts.length === 0) {
// This might be a removal keybinding item in user settings => accept it
result[resultLen++] = new ResolvedKeybindingItem(undefined, item.command, item.commandArgs, when, isDefault);
result[resultLen++] = new ResolvedKeybindingItem(undefined, item.command, item.commandArgs, when, isDefault, null);
} else {
const resolvedKeybindings = this._keyboardMapper.resolveUserBinding(parts);
for (const resolvedKeybinding of resolvedKeybindings) {
result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault);
result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault, null);
}
}
}
......@@ -481,22 +485,22 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
return this._keyboardMapper.resolveUserBinding(parts);
}
private _handleKeybindingsExtensionPointUser(isBuiltin: boolean, keybindings: ContributedKeyBinding | ContributedKeyBinding[], collector: ExtensionMessageCollector, result: IKeybindingRule2[]): void {
private _handleKeybindingsExtensionPointUser(extensionId: ExtensionIdentifier, isBuiltin: boolean, keybindings: ContributedKeyBinding | ContributedKeyBinding[], collector: ExtensionMessageCollector, result: IKeybindingRule2[]): void {
if (isContributedKeyBindingsArray(keybindings)) {
for (let i = 0, len = keybindings.length; i < len; i++) {
this._handleKeybinding(isBuiltin, i + 1, keybindings[i], collector, result);
this._handleKeybinding(extensionId, isBuiltin, i + 1, keybindings[i], collector, result);
}
} else {
this._handleKeybinding(isBuiltin, 1, keybindings, collector, result);
this._handleKeybinding(extensionId, isBuiltin, 1, keybindings, collector, result);
}
}
private _handleKeybinding(isBuiltin: boolean, idx: number, keybindings: ContributedKeyBinding, collector: ExtensionMessageCollector, result: IKeybindingRule2[]): void {
private _handleKeybinding(extensionId: ExtensionIdentifier, isBuiltin: boolean, idx: number, keybindings: ContributedKeyBinding, collector: ExtensionMessageCollector, result: IKeybindingRule2[]): void {
let rejects: string[] = [];
if (isValidContributedKeyBinding(keybindings, rejects)) {
let rule = this._asCommandRule(isBuiltin, idx++, keybindings);
let rule = this._asCommandRule(extensionId, isBuiltin, idx++, keybindings);
if (rule) {
result.push(rule);
}
......@@ -512,7 +516,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
}
}
private _asCommandRule(isBuiltin: boolean, idx: number, binding: ContributedKeyBinding): IKeybindingRule2 | undefined {
private _asCommandRule(extensionId: ExtensionIdentifier, isBuiltin: boolean, idx: number, binding: ContributedKeyBinding): IKeybindingRule2 | undefined {
let { command, args, when, key, mac, linux, win } = binding;
......@@ -542,7 +546,8 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
primary: KeybindingParser.parseKeybinding(key, OS),
mac: mac ? { primary: KeybindingParser.parseKeybinding(mac, OS) } : null,
linux: linux ? { primary: KeybindingParser.parseKeybinding(linux, OS) } : null,
win: win ? { primary: KeybindingParser.parseKeybinding(win, OS) } : null
win: win ? { primary: KeybindingParser.parseKeybinding(win, OS) } : null,
extensionId: extensionId.value
};
if (!desc.primary && !desc.mac && !desc.linux && !desc.win) {
......@@ -740,6 +745,26 @@ let schema: IJSONSchema = {
}
};
const preferencesCategory = nls.localize('preferences', "Preferences");
class ToggleKeybindingsLogAction extends Action2 {
constructor() {
super({
id: 'workbench.action.toggleKeybindingsLog',
title: { value: nls.localize('toggleKeybindingsLog', "Toggle Keyboard Shortcuts Troubleshooting"), original: 'Toggle Keyboard Shortcuts Troubleshooting' },
category: preferencesCategory,
f1: true
});
}
run(accessor: ServicesAccessor): void {
accessor.get(IKeybindingService).toggleLogging();
}
}
registerAction2(ToggleKeybindingsLogAction);
let schemaRegistry = Registry.as<IJSONContributionRegistry>(Extensions.JSONContribution);
schemaRegistry.registerSchema(schemaId, schema);
......
......@@ -312,7 +312,7 @@ suite('KeybindingsEditing', () => {
}
}
const keybinding = parts.length > 0 ? new USLayoutResolvedKeybinding(new ChordKeybinding(parts), OS) : undefined;
return new ResolvedKeybindingItem(keybinding, command || 'some command', null, when ? ContextKeyExpr.deserialize(when) : undefined, isDefault === undefined ? true : isDefault);
return new ResolvedKeybindingItem(keybinding, command || 'some command', null, when ? ContextKeyExpr.deserialize(when) : undefined, isDefault === undefined ? true : isDefault, null);
}
});
......@@ -176,7 +176,7 @@ export class KeybindingsEditorModel extends EditorModel {
const commandsWithDefaultKeybindings = this.keybindingsService.getDefaultKeybindings().map(keybinding => keybinding.command);
for (const command of KeybindingResolver.getAllUnboundCommands(boundCommands)) {
const keybindingItem = new ResolvedKeybindingItem(undefined, command, null, undefined, commandsWithDefaultKeybindings.indexOf(command) === -1);
const keybindingItem = new ResolvedKeybindingItem(undefined, command, null, undefined, commandsWithDefaultKeybindings.indexOf(command) === -1, null);
this._keybindingItemsSortedByPrecedence.push(KeybindingsEditorModel.toKeybindingEntry(command, keybindingItem, workbenchActionsRegistry, actionLabels));
}
this._keybindingItems = this._keybindingItemsSortedByPrecedence.slice(0).sort((a, b) => KeybindingsEditorModel.compareKeybindingData(a, b));
......
......@@ -658,7 +658,7 @@ suite('KeybindingsEditorModel', () => {
}
}
const keybinding = parts.length > 0 ? new USLayoutResolvedKeybinding(new ChordKeybinding(parts), OS) : undefined;
return new ResolvedKeybindingItem(keybinding, command || 'some command', null, when ? ContextKeyExpr.deserialize(when) : undefined, isDefault === undefined ? true : isDefault);
return new ResolvedKeybindingItem(keybinding, command || 'some command', null, when ? ContextKeyExpr.deserialize(when) : undefined, isDefault === undefined ? true : isDefault, null);
}
function asResolvedKeybindingItems(keybindingEntries: IKeybindingItemEntry[], keepUnassigned: boolean = false): ResolvedKeybindingItem[] {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册