Skip to content

Commit 0566e65

Browse files
toumorokoshialexdima
authored andcommitted
ResolvedKeybindingItem now supports keychords of any length.
1 parent 2711be7 commit 0566e65

16 files changed

Lines changed: 106 additions & 116 deletions

src/vs/base/common/keybindingParser.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,17 +107,25 @@ export class KeybindingParser {
107107
return [new SimpleKeybinding(mods.ctrl, mods.shift, mods.alt, mods.meta, keyCode), mods.remains];
108108
}
109109

110-
static parseUserBinding(input: string): [SimpleKeybinding | ScanCodeBinding | null, SimpleKeybinding | ScanCodeBinding | null] {
110+
static parseUserBinding(input: string): Array<SimpleKeybinding | ScanCodeBinding> {
111111
// TODO@chords: allow users to define N chords
112112
if (!input) {
113-
return [null, null];
113+
return [];
114114
}
115115

116-
let [firstPart, remains] = this.parseSimpleUserBinding(input);
117-
let chordPart: SimpleKeybinding | ScanCodeBinding | null = null;
118-
if (remains.length > 0) {
119-
[chordPart] = this.parseSimpleUserBinding(remains);
116+
let parts = [];
117+
let remains = input;
118+
while (remains.length > 0) {
119+
let [part, nextRemains] = this.parseSimpleUserBinding(remains);
120+
parts.push(part);
121+
// check equality to break out of a possible infinite loop.
122+
// if nothing was consumed it implies that an empty keybinding
123+
// was returned.
124+
if (remains === nextRemains) {
125+
break;
126+
}
127+
remains = nextRemains;
120128
}
121-
return [firstPart, chordPart];
129+
return parts;
122130
}
123131
}

src/vs/platform/driver/electron-main/driver.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,10 @@ export class Driver implements IDriver, IWindowDriverRegistry {
8383
async dispatchKeybinding(windowId: number, keybinding: string): Promise<void> {
8484
await this.whenUnfrozen(windowId);
8585

86-
const [first, second] = KeybindingParser.parseUserBinding(keybinding);
86+
const parts = KeybindingParser.parseUserBinding(keybinding);
8787

88-
if (!first) {
89-
return;
90-
}
91-
92-
await this._dispatchKeybinding(windowId, first);
93-
94-
if (second) {
95-
await this._dispatchKeybinding(windowId, second);
88+
for (let part of parts) {
89+
await this._dispatchKeybinding(windowId, part);
9690
}
9791
}
9892

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,23 +40,23 @@ export class KeybindingResolver {
4040
this._keybindings = KeybindingResolver.combine(defaultKeybindings, overrides);
4141
for (let i = 0, len = this._keybindings.length; i < len; i++) {
4242
let k = this._keybindings[i];
43-
if (k.keypressFirstPart === null) {
43+
if (k.keypressParts.length === 0) {
4444
// unbound
4545
continue;
4646
}
4747

48-
this._addKeyPress(k.keypressFirstPart, k);
48+
this._addKeyPress(k.keypressParts[0], k);
4949
}
5050
}
5151

5252
private static _isTargetedForRemoval(defaultKb: ResolvedKeybindingItem, keypressFirstPart: string | null, keypressChordPart: string | null, command: string, when: ContextKeyExpr | null): boolean {
5353
if (defaultKb.command !== command) {
5454
return false;
5555
}
56-
if (keypressFirstPart && defaultKb.keypressFirstPart !== keypressFirstPart) {
56+
if (keypressFirstPart && defaultKb.keypressParts[0] !== keypressFirstPart) {
5757
return false;
5858
}
59-
if (keypressChordPart && defaultKb.keypressChordPart !== keypressChordPart) {
59+
if (keypressChordPart && defaultKb.keypressParts[1] !== keypressChordPart) {
6060
return false;
6161
}
6262
if (when) {
@@ -84,8 +84,8 @@ export class KeybindingResolver {
8484
}
8585

8686
const command = override.command.substr(1);
87-
const keypressFirstPart = override.keypressFirstPart;
88-
const keypressChordPart = override.keypressChordPart;
87+
const keypressFirstPart = override.keypressParts[0];
88+
const keypressChordPart = override.keypressParts[1];
8989
const when = override.when;
9090
for (let j = defaults.length - 1; j >= 0; j--) {
9191
if (this._isTargetedForRemoval(defaults[j], keypressFirstPart, keypressChordPart, command, when)) {
@@ -114,10 +114,10 @@ export class KeybindingResolver {
114114
continue;
115115
}
116116

117-
const conflictIsChord = (conflict.keypressChordPart !== null);
118-
const itemIsChord = (item.keypressChordPart !== null);
117+
const conflictIsChord = (conflict.keypressParts.length > 1);
118+
const itemIsChord = (item.keypressParts.length > 1);
119119

120-
if (conflictIsChord && itemIsChord && conflict.keypressChordPart !== item.keypressChordPart) {
120+
if (conflictIsChord && itemIsChord && conflict.keypressParts[1] !== item.keypressParts[1]) {
121121
// The conflict only shares the chord start with this command
122122
continue;
123123
}
@@ -247,7 +247,7 @@ export class KeybindingResolver {
247247
lookupMap = [];
248248
for (let i = 0, len = candidates.length; i < len; i++) {
249249
let candidate = candidates[i];
250-
if (candidate.keypressChordPart === keypress) {
250+
if (candidate.keypressParts[1] === keypress) {
251251
lookupMap.push(candidate);
252252
}
253253
}
@@ -266,7 +266,7 @@ export class KeybindingResolver {
266266
return null;
267267
}
268268

269-
if (currentChord === null && result.keypressChordPart !== null) {
269+
if (currentChord === null && result.keypressParts.length > 1 && result.keypressParts[1] !== null) {
270270
return {
271271
enterChord: true,
272272
commandId: null,

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

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
1010
export class ResolvedKeybindingItem {
1111
_resolvedKeybindingItemBrand: void;
1212

13+
public readonly keypressParts: string[];
1314
public readonly resolvedKeybinding: ResolvedKeybinding | null;
14-
public readonly keypressFirstPart: string | null;
15-
public readonly keypressChordPart: string | null;
1615
public readonly bubble: boolean;
1716
public readonly command: string | null;
1817
public readonly commandArgs: any;
@@ -22,21 +21,9 @@ export class ResolvedKeybindingItem {
2221
constructor(resolvedKeybinding: ResolvedKeybinding | null, command: string | null, commandArgs: any, when: ContextKeyExpr | null, isDefault: boolean) {
2322
this.resolvedKeybinding = resolvedKeybinding;
2423
if (resolvedKeybinding) {
25-
const dispatchParts = resolvedKeybinding.getDispatchParts();
26-
// TODO@chords: add support for dispatching N chords here
27-
if (dispatchParts.length >= 2) {
28-
this.keypressFirstPart = dispatchParts[0];
29-
this.keypressChordPart = dispatchParts[1];
30-
} else if (dispatchParts.length === 1) {
31-
this.keypressFirstPart = dispatchParts[0];
32-
this.keypressChordPart = null;
33-
} else {
34-
this.keypressFirstPart = null;
35-
this.keypressChordPart = null;
36-
}
24+
this.keypressParts = resolvedKeybinding.getDispatchParts();
3725
} else {
38-
this.keypressFirstPart = null;
39-
this.keypressChordPart = null;
26+
this.keypressParts = [];
4027
}
4128
this.bubble = (command ? command.charCodeAt(0) === CharCode.Caret : false);
4229
this.command = this.bubble ? command!.substr(1) : command;

src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -265,13 +265,19 @@ export class KeybindingEditorDecorationsRenderer extends Disposable {
265265
return true;
266266
}
267267

268-
const [parsedA1, parsedA2] = KeybindingParser.parseUserBinding(a);
269-
const [parsedB1, parsedB2] = KeybindingParser.parseUserBinding(b);
268+
const aParts = KeybindingParser.parseUserBinding(a);
269+
const bParts = KeybindingParser.parseUserBinding(b);
270270

271-
return (
272-
this._userBindingEquals(parsedA1, parsedB1)
273-
&& this._userBindingEquals(parsedA2, parsedB2)
274-
);
271+
if (aParts.length !== bParts.length) {
272+
return false;
273+
}
274+
275+
for (let i = 0, length = aParts.length; i < length; i++) {
276+
if (!this._userBindingEquals(aParts[i], bParts[i])) {
277+
return false;
278+
}
279+
}
280+
return true;
275281
}
276282

277283
private static _userBindingEquals(a: SimpleKeybinding | ScanCodeBinding, b: SimpleKeybinding | ScanCodeBinding): boolean {

src/vs/workbench/contrib/preferences/test/browser/keybindingsEditorContribution.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ suite('KeybindingsEditorContribution', () => {
1111
function assertUserSettingsFuzzyEquals(a: string, b: string, expected: boolean): void {
1212
const actual = KeybindingEditorDecorationsRenderer._userSettingsFuzzyEquals(a, b);
1313
const message = expected ? `${a} == ${b}` : `${a} != ${b}`;
14+
console.log(a);
15+
console.log(b);
1416
assert.equal(actual, expected, 'fuzzy: ' + message);
1517
}
1618

src/vs/workbench/services/keybinding/common/keybindingIO.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export class KeybindingIO {
4242
}
4343

4444
public static readUserKeybindingItem(input: IUserFriendlyKeybinding, OS: OperatingSystem): IUserKeybindingItem {
45+
// TODO yusuke: replace with keychords.
4546
const [firstPart, chordPart] = (typeof input.key === 'string' ? KeybindingParser.parseUserBinding(input.key) : [null, null]);
4647
const when = (typeof input.when === 'string' ? ContextKeyExpr.deserialize(input.when) : null);
4748
const command = (typeof input.command === 'string' ? input.command : null);

src/vs/workbench/services/keybinding/common/keyboardMapper.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export interface IKeyboardMapper {
1111
dumpDebugInfo(): string;
1212
resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[];
1313
resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding;
14-
resolveUserBinding(firstPart: SimpleKeybinding | ScanCodeBinding | null, chordPart: SimpleKeybinding | ScanCodeBinding | null): ResolvedKeybinding[];
14+
resolveUserBinding(firstPart: Array<SimpleKeybinding | ScanCodeBinding>): ResolvedKeybinding[];
1515
}
1616

1717
export class CachedKeyboardMapper implements IKeyboardMapper {
@@ -43,7 +43,7 @@ export class CachedKeyboardMapper implements IKeyboardMapper {
4343
return this._actual.resolveKeyboardEvent(keyboardEvent);
4444
}
4545

46-
public resolveUserBinding(firstPart: SimpleKeybinding | ScanCodeBinding, chordPart: SimpleKeybinding | ScanCodeBinding): ResolvedKeybinding[] {
47-
return this._actual.resolveUserBinding(firstPart, chordPart);
46+
public resolveUserBinding(parts: Array<SimpleKeybinding | ScanCodeBinding>): ResolvedKeybinding[] {
47+
return this._actual.resolveUserBinding(parts);
4848
}
4949
}

src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -117,16 +117,8 @@ export class MacLinuxFallbackKeyboardMapper implements IKeyboardMapper {
117117
return new SimpleKeybinding(binding.ctrlKey, binding.shiftKey, binding.altKey, binding.metaKey, keyCode);
118118
}
119119

120-
public resolveUserBinding(firstPart: SimpleKeybinding | ScanCodeBinding | null, chordPart: SimpleKeybinding | ScanCodeBinding | null): ResolvedKeybinding[] {
121-
const _firstPart = this._resolveSimpleUserBinding(firstPart);
122-
const _chordPart = this._resolveSimpleUserBinding(chordPart);
123-
let parts: SimpleKeybinding[] = [];
124-
if (_firstPart) {
125-
parts.push(_firstPart);
126-
}
127-
if (_chordPart) {
128-
parts.push(_chordPart);
129-
}
120+
public resolveUserBinding(input: Array<SimpleKeybinding | ScanCodeBinding>): ResolvedKeybinding[] {
121+
let parts: SimpleKeybinding[] = input.map(this._resolveSimpleUserBinding.bind(this));
130122
if (parts.length > 0) {
131123
return [new USLayoutResolvedKeybinding(new ChordKeybinding(parts), this._OS)];
132124
}

src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,14 +1053,8 @@ export class MacLinuxKeyboardMapper implements IKeyboardMapper {
10531053
return this.simpleKeybindingToScanCodeBinding(binding);
10541054
}
10551055

1056-
public resolveUserBinding(_firstPart: SimpleKeybinding | ScanCodeBinding | null, _chordPart: SimpleKeybinding | ScanCodeBinding | null): ResolvedKeybinding[] {
1057-
let parts: ScanCodeBinding[][] = [];
1058-
if (_firstPart) {
1059-
parts.push(this._resolveSimpleUserBinding(_firstPart));
1060-
}
1061-
if (_chordPart) {
1062-
parts.push(this._resolveSimpleUserBinding(_chordPart));
1063-
}
1056+
public resolveUserBinding(input: Array<SimpleKeybinding | ScanCodeBinding>): ResolvedKeybinding[] {
1057+
let parts: ScanCodeBinding[][] = input.map(this._resolveSimpleUserBinding.bind(this));
10641058
let result: NativeResolvedKeybinding[] = [];
10651059
this._generateResolvedKeybindings(parts, 0, [], result);
10661060
return result;

0 commit comments

Comments
 (0)