diff --git a/src/vs/base/common/keyCodes.ts b/src/vs/base/common/keyCodes.ts index b3a1ee94fbe..92267e535c2 100644 --- a/src/vs/base/common/keyCodes.ts +++ b/src/vs/base/common/keyCodes.ts @@ -476,6 +476,14 @@ export class SimpleKeybinding { ); } + public getHashCode(): string { + let ctrl = this.ctrlKey ? '1' : '0'; + let shift = this.shiftKey ? '1' : '0'; + let alt = this.altKey ? '1' : '0'; + let meta = this.metaKey ? '1' : '0'; + return `${ctrl}${shift}${alt}${meta}${this.keyCode}`; + } + public isModifierKey(): boolean { return ( this.keyCode === KeyCode.Unknown @@ -509,6 +517,10 @@ export class ChordKeybinding { this.firstPart = firstPart; this.chordPart = chordPart; } + + public getHashCode(): string { + return `${this.firstPart.getHashCode()};${this.chordPart.getHashCode()}`; + } } export type Keybinding = SimpleKeybinding | ChordKeybinding; diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index b9e9f550b03..108eabbba6f 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -321,7 +321,7 @@ export class ContextKeyNotExpr implements ContextKeyExpr { } export class ContextKeyAndExpr implements ContextKeyExpr { - private expr: ContextKeyExpr[]; + public readonly expr: ContextKeyExpr[]; constructor(expr: ContextKeyExpr[]) { this.expr = ContextKeyAndExpr._normalizeArr(expr); diff --git a/src/vs/platform/keybinding/common/keybindingResolver.ts b/src/vs/platform/keybinding/common/keybindingResolver.ts index 28cef4ad27c..492f2c0a098 100644 --- a/src/vs/platform/keybinding/common/keybindingResolver.ts +++ b/src/vs/platform/keybinding/common/keybindingResolver.ts @@ -5,7 +5,7 @@ 'use strict'; import { isFalsyOrEmpty } from 'vs/base/common/arrays'; -import { ContextKeyExpr, IContext } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContext, ContextKeyAndExpr } from 'vs/platform/contextkey/common/contextkey'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; @@ -121,7 +121,7 @@ export class KeybindingResolver { continue; } - if (KeybindingResolver.whenIsEntirelyIncluded(true, conflict.when, item.when)) { + if (KeybindingResolver.whenIsEntirelyIncluded(conflict.when, item.when)) { // `item` completely overwrites `conflict` // Remove conflict from the lookupMap this._removeFromLookupMap(conflict); @@ -160,15 +160,10 @@ export class KeybindingResolver { } /** - * Returns true if `a` is completely covered by `b`. - * Returns true if `b` is a more relaxed `a`. - * Return true if (`a` === true implies `b` === true). + * Returns true if it is provable `a` implies `b`. + * **Precondition**: Assumes `a` and `b` are normalized! */ - public static whenIsEntirelyIncluded(inNormalizedForm: boolean, a: ContextKeyExpr, b: ContextKeyExpr): boolean { - if (!inNormalizedForm) { - a = a ? a.normalize() : null; - b = b ? b.normalize() : null; - } + public static whenIsEntirelyIncluded(a: ContextKeyExpr, b: ContextKeyExpr): boolean { if (!b) { return true; } @@ -176,16 +171,22 @@ export class KeybindingResolver { return false; } - let aRulesArr = a.serialize().split(' && '); - let bRulesArr = b.serialize().split(' && '); + const aExpressions: ContextKeyExpr[] = ((a instanceof ContextKeyAndExpr) ? a.expr : [a]); + const bExpressions: ContextKeyExpr[] = ((b instanceof ContextKeyAndExpr) ? b.expr : [b]); - let aRules: { [rule: string]: boolean; } = Object.create(null); - for (let i = 0, len = aRulesArr.length; i < len; i++) { - aRules[aRulesArr[i]] = true; - } + let aIndex = 0; + for (let bIndex = 0; bIndex < bExpressions.length; bIndex++) { + let bExpr = bExpressions[bIndex]; + let bExprMatched = false; + while (!bExprMatched && aIndex < aExpressions.length) { + let aExpr = aExpressions[aIndex]; + if (aExpr.equals(bExpr)) { + bExprMatched = true; + } + aIndex++; + } - for (let i = 0, len = bRulesArr.length; i < len; i++) { - if (!aRules[bRulesArr[i]]) { + if (!bExprMatched) { return false; } } diff --git a/src/vs/platform/keybinding/common/keybindingsRegistry.ts b/src/vs/platform/keybinding/common/keybindingsRegistry.ts index 98736111499..da9ec6f38fd 100644 --- a/src/vs/platform/keybinding/common/keybindingsRegistry.ts +++ b/src/vs/platform/keybinding/common/keybindingsRegistry.ts @@ -75,6 +75,7 @@ export interface IKeybindingsRegistry { class KeybindingsRegistryImpl implements IKeybindingsRegistry { private _keybindings: IKeybindingItem[]; + private _keybindingsSorted: boolean; public WEIGHT = { editorCore: (importance: number = 0): number => { @@ -96,6 +97,7 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry { constructor() { this._keybindings = []; + this._keybindingsSorted = true; } /** @@ -144,11 +146,14 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry { let actualKb = KeybindingsRegistryImpl.bindToCurrentPlatform(rule); if (actualKb && actualKb.primary) { - this.registerDefaultKeybinding(createKeybinding(actualKb.primary, OS), rule.id, rule.weight, 0, rule.when); + this._registerDefaultKeybinding(createKeybinding(actualKb.primary, OS), rule.id, rule.weight, 0, rule.when); } if (actualKb && Array.isArray(actualKb.secondary)) { - actualKb.secondary.forEach((k, i) => this.registerDefaultKeybinding(createKeybinding(k, OS), rule.id, rule.weight, -i - 1, rule.when)); + for (let i = 0, len = actualKb.secondary.length; i < len; i++) { + const k = actualKb.secondary[i]; + this._registerDefaultKeybinding(createKeybinding(k, OS), rule.id, rule.weight, -i - 1, rule.when); + } } } @@ -156,7 +161,7 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry { let actualKb = KeybindingsRegistryImpl.bindToCurrentPlatform2(rule); if (actualKb && actualKb.primary) { - this.registerDefaultKeybinding(actualKb.primary, rule.id, rule.weight, 0, rule.when); + this._registerDefaultKeybinding(actualKb.primary, rule.id, rule.weight, 0, rule.when); } } @@ -199,7 +204,7 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry { } } - private registerDefaultKeybinding(keybinding: Keybinding, commandId: string, weight1: number, weight2: number, when: ContextKeyExpr): void { + private _registerDefaultKeybinding(keybinding: Keybinding, commandId: string, weight1: number, weight2: number, when: ContextKeyExpr): void { if (OS === OperatingSystem.Windows) { if (keybinding.type === KeybindingType.Chord) { this._assertNoCtrlAlt(keybinding.firstPart, commandId); @@ -215,12 +220,15 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry { weight1: weight1, weight2: weight2 }); + this._keybindingsSorted = false; } public getDefaultKeybindings(): IKeybindingItem[] { - let result = this._keybindings.slice(0); - result.sort(sorter); - return result; + if (!this._keybindingsSorted) { + this._keybindings.sort(sorter); + this._keybindingsSorted = true; + } + return this._keybindings.slice(0); } } export const KeybindingsRegistry: IKeybindingsRegistry = new KeybindingsRegistryImpl(); diff --git a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts index dc22f50d3ce..10276cbc206 100644 --- a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts +++ b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts @@ -28,7 +28,7 @@ suite('KeybindingResolver', () => { resolvedKeybinding, command, commandArgs, - when, + when ? when.normalize() : null, isDefault ); } @@ -194,10 +194,14 @@ suite('KeybindingResolver', () => { test('contextIsEntirelyIncluded', function () { let assertIsIncluded = (a: ContextKeyExpr[], b: ContextKeyExpr[]) => { - assert.equal(KeybindingResolver.whenIsEntirelyIncluded(false, new ContextKeyAndExpr(a), new ContextKeyAndExpr(b)), true); + let tmpA = new ContextKeyAndExpr(a).normalize(); + let tmpB = new ContextKeyAndExpr(b).normalize(); + assert.equal(KeybindingResolver.whenIsEntirelyIncluded(tmpA, tmpB), true); }; let assertIsNotIncluded = (a: ContextKeyExpr[], b: ContextKeyExpr[]) => { - assert.equal(KeybindingResolver.whenIsEntirelyIncluded(false, new ContextKeyAndExpr(a), new ContextKeyAndExpr(b)), false); + let tmpA = new ContextKeyAndExpr(a).normalize(); + let tmpB = new ContextKeyAndExpr(b).normalize(); + assert.equal(KeybindingResolver.whenIsEntirelyIncluded(tmpA, tmpB), false); }; let key1IsTrue = ContextKeyExpr.equals('key1', true); let key1IsNotFalse = ContextKeyExpr.notEquals('key1', false); diff --git a/src/vs/workbench/services/keybinding/common/keyboardMapper.ts b/src/vs/workbench/services/keybinding/common/keyboardMapper.ts index 688be484967..b12c9023ed2 100644 --- a/src/vs/workbench/services/keybinding/common/keyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/keyboardMapper.ts @@ -15,3 +15,36 @@ export interface IKeyboardMapper { resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding; resolveUserBinding(firstPart: SimpleKeybinding | ScanCodeBinding, chordPart: SimpleKeybinding | ScanCodeBinding): ResolvedKeybinding[]; } + +export class CachedKeyboardMapper implements IKeyboardMapper { + + private _actual: IKeyboardMapper; + private _cache: Map; + + constructor(actual) { + this._actual = actual; + this._cache = new Map(); + } + + public dumpDebugInfo(): string { + return this._actual.dumpDebugInfo(); + } + + public resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[] { + let hashCode = keybinding.getHashCode(); + if (!this._cache.has(hashCode)) { + let r = this._actual.resolveKeybinding(keybinding); + this._cache.set(hashCode, r); + return r; + } + return this._cache.get(hashCode); + } + + public resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding { + return this._actual.resolveKeyboardEvent(keyboardEvent); + } + + public resolveUserBinding(firstPart: SimpleKeybinding | ScanCodeBinding, chordPart: SimpleKeybinding | ScanCodeBinding): ResolvedKeybinding[] { + return this._actual.resolveUserBinding(firstPart, chordPart); + } +} diff --git a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts index 25f31b72287..d3e29c3b221 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts @@ -28,7 +28,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { KeybindingIO, OutputBuilder, IUserKeybindingItem } from 'vs/workbench/services/keybinding/common/keybindingIO'; import * as nativeKeymap from 'native-keymap'; -import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; +import { IKeyboardMapper, CachedKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; import { WindowsKeyboardMapper, IWindowsKeyboardMapping, windowsKeyboardMappingEquals } from 'vs/workbench/services/keybinding/common/windowsKeyboardMapper'; import { IMacLinuxKeyboardMapping, MacLinuxKeyboardMapper, macLinuxKeyboardMappingEquals } from 'vs/workbench/services/keybinding/common/macLinuxKeyboardMapper'; import { MacLinuxFallbackKeyboardMapper } from 'vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper'; @@ -115,7 +115,9 @@ export class KeyboardMapperFactory { this._initialized = true; this._rawMapping = rawMapping; - this._keyboardMapper = KeyboardMapperFactory._createKeyboardMapper(this._layoutInfo, this._rawMapping); + this._keyboardMapper = new CachedKeyboardMapper( + KeyboardMapperFactory._createKeyboardMapper(this._layoutInfo, this._rawMapping) + ); this._onDidChangeKeyboardMapper.fire(); }