#37524: Faster WorkbenchKeybindingService._getResolver
This commit is contained in:
parent
840b74974b
commit
728f71d1bf
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue