#37524: Faster WorkbenchKeybindingService._getResolver

This commit is contained in:
Alex Dima 2017-11-21 17:52:25 +01:00
parent 840b74974b
commit 728f71d1bf
7 changed files with 91 additions and 31 deletions

View file

@ -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;

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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);

View file

@ -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<string, ResolvedKeybinding[]>;
constructor(actual) {
this._actual = actual;
this._cache = new Map<string, ResolvedKeybinding[]>();
}
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);
}
}

View file

@ -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();
}