Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modifying ChordKeybinding to support multiple chords #65826

Merged
merged 12 commits into from
Feb 15, 2019
Merged
74 changes: 45 additions & 29 deletions src/vs/base/common/keyCodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { OperatingSystem } from 'vs/base/common/platform';
import { illegalArgument } from 'vs/base/common/errors';

/**
* Virtual Key Codes, the value does not hold any inherent meaning.
Expand Down Expand Up @@ -417,12 +418,12 @@ export function createKeybinding(keybinding: number, OS: OperatingSystem): Keybi
const firstPart = (keybinding & 0x0000FFFF) >>> 0;
const chordPart = (keybinding & 0xFFFF0000) >>> 16;
if (chordPart !== 0) {
return new ChordKeybinding(
return new ChordKeybinding([
createSimpleKeybinding(firstPart, OS),
createSimpleKeybinding(chordPart, OS),
);
createSimpleKeybinding(chordPart, OS)
]);
}
return createSimpleKeybinding(firstPart, OS);
return new ChordKeybinding([createSimpleKeybinding(firstPart, OS)]);
}

export function createSimpleKeybinding(keybinding: number, OS: OperatingSystem): SimpleKeybinding {
Expand All @@ -439,14 +440,7 @@ export function createSimpleKeybinding(keybinding: number, OS: OperatingSystem):
return new SimpleKeybinding(ctrlKey, shiftKey, altKey, metaKey, keyCode);
}

export const enum KeybindingType {
Simple = 1,
Chord = 2
}

export class SimpleKeybinding {
public readonly type = KeybindingType.Simple;

public readonly ctrlKey: boolean;
public readonly shiftKey: boolean;
public readonly altKey: boolean;
Expand All @@ -461,10 +455,7 @@ export class SimpleKeybinding {
this.keyCode = keyCode;
}

public equals(other: Keybinding): boolean {
if (other.type !== KeybindingType.Simple) {
return false;
}
public equals(other: SimpleKeybinding): boolean {
return (
this.ctrlKey === other.ctrlKey
&& this.shiftKey === other.shiftKey
Expand Down Expand Up @@ -492,6 +483,10 @@ export class SimpleKeybinding {
);
}

public toChord(): ChordKeybinding {
return new ChordKeybinding([this]);
}

/**
* Does this keybinding refer to the key code of a modifier and it also has the modifier flag?
*/
Expand All @@ -506,22 +501,43 @@ export class SimpleKeybinding {
}

export class ChordKeybinding {
public readonly type = KeybindingType.Chord;

public readonly firstPart: SimpleKeybinding;
public readonly chordPart: SimpleKeybinding;
public readonly parts: SimpleKeybinding[];

constructor(firstPart: SimpleKeybinding, chordPart: SimpleKeybinding) {
this.firstPart = firstPart;
this.chordPart = chordPart;
constructor(parts: SimpleKeybinding[]) {
if (parts.length === 0) {
throw illegalArgument(`parts`);
}
this.parts = parts;
}

public getHashCode(): string {
return `${this.firstPart.getHashCode()};${this.chordPart.getHashCode()}`;
let result = '';
for (let i = 0, len = this.parts.length; i < len; i++) {
if (i !== 0) {
result += ';';
}
result += this.parts[i].getHashCode();
}
return result;
}

public equals(other: ChordKeybinding | null): boolean {
if (other === null) {
return false;
}
if (this.parts.length !== other.parts.length) {
return false;
}
for (let i = 0; i < this.parts.length; i++) {
if (!this.parts[i].equals(other.parts[i])) {
return false;
}
}
return true;
}
}

export type Keybinding = SimpleKeybinding | ChordKeybinding;
export type Keybinding = ChordKeybinding;

export class ResolvedKeybindingPart {
readonly ctrlKey: boolean;
Expand Down Expand Up @@ -574,12 +590,12 @@ export abstract class ResolvedKeybinding {
public abstract isChord(): boolean;

/**
* Returns the firstPart, chordPart that should be used for dispatching.
* Returns the parts that should be used for dispatching.
*/
public abstract getDispatchParts(): [string | null, string | null];
public abstract getDispatchParts(): (string | null)[];
/**
* Returns the firstPart, chordPart of the keybinding.
* For simple keybindings, the second element will be null.
* Returns the parts that comprise of the keybinding.
* Simple keybindings return one element.
*/
public abstract getParts(): [ResolvedKeybindingPart, ResolvedKeybindingPart | null];
public abstract getParts(): ResolvedKeybindingPart[];
}
32 changes: 18 additions & 14 deletions src/vs/base/common/keybindingLabels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export interface Modifiers {
readonly metaKey: boolean;
}

export interface KeyLabelProvider<T extends Modifiers> {
(keybinding: T): string | null;
}

export class ModifierLabelProvider {

public readonly modifierLabels: ModifierLabels[];
Expand All @@ -32,11 +36,22 @@ export class ModifierLabelProvider {
this.modifierLabels[OperatingSystem.Linux] = linux;
}

public toLabel(firstPartMod: Modifiers | null, firstPartKey: string | null, chordPartMod: Modifiers | null, chordPartKey: string | null, OS: OperatingSystem): string | null {
if (firstPartMod === null || firstPartKey === null) {
public toLabel<T extends Modifiers>(OS: OperatingSystem, parts: T[], keyLabelProvider: KeyLabelProvider<T>): string | null {
if (parts.length === 0) {
return null;
}
return _asString(firstPartMod, firstPartKey, chordPartMod, chordPartKey, this.modifierLabels[OS]);

let result: string[] = [];
for (let i = 0, len = parts.length; i < len; i++) {
const part = parts[i];
const keyLabel = keyLabelProvider(part);
if (keyLabel === null) {
// this keybinding cannot be expressed...
return null;
}
result[i] = _simpleAsString(part, keyLabel, this.modifierLabels[OS]);
}
return result.join(' ');
}
}

Expand Down Expand Up @@ -171,14 +186,3 @@ function _simpleAsString(modifiers: Modifiers, key: string, labels: ModifierLabe

return result.join(labels.separator);
}

function _asString(firstPartMod: Modifiers, firstPartKey: string, chordPartMod: Modifiers | null, chordPartKey: string | null, labels: ModifierLabels): string {
let result = _simpleAsString(firstPartMod, firstPartKey, labels);

if (chordPartMod !== null && chordPartKey !== null) {
result += ' ';
result += _simpleAsString(chordPartMod, chordPartKey, labels);
}

return result;
}
17 changes: 8 additions & 9 deletions src/vs/base/common/keybindingParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,14 @@ export class KeybindingParser {
return null;
}

let [firstPart, remains] = this.parseSimpleKeybinding(input);
let chordPart: SimpleKeybinding | null = null;
if (remains.length > 0) {
[chordPart] = this.parseSimpleKeybinding(remains);
}
let parts: SimpleKeybinding[] = [];
let part: SimpleKeybinding;

if (chordPart) {
return new ChordKeybinding(firstPart, chordPart);
}
return firstPart;
do {
[part, input] = this.parseSimpleKeybinding(input);
parts.push(part);
} while (input.length > 0);
return new ChordKeybinding(parts);
}

private static parseSimpleUserBinding(input: string): [SimpleKeybinding | ScanCodeBinding, string] {
Expand All @@ -110,6 +108,7 @@ export class KeybindingParser {
}

static parseUserBinding(input: string): [SimpleKeybinding | ScanCodeBinding | null, SimpleKeybinding | ScanCodeBinding | null] {
// TODO@chords: allow users to define N chords
if (!input) {
return [null, null];
}
Expand Down
18 changes: 10 additions & 8 deletions src/vs/base/parts/tree/browser/treeDefaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import * as mouse from 'vs/base/browser/mouseEvent';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import * as _ from 'vs/base/parts/tree/browser/tree';
import { IDragAndDropData } from 'vs/base/browser/dnd';
import { KeyCode, KeyMod, Keybinding, SimpleKeybinding, createSimpleKeybinding } from 'vs/base/common/keyCodes';
import { KeyCode, KeyMod, Keybinding, SimpleKeybinding, createKeybinding } from 'vs/base/common/keyCodes';

export interface IKeyBindingCallback {
(tree: _.ITree, event: IKeyboardEvent): void;
Expand Down Expand Up @@ -49,7 +49,7 @@ export interface IControllerOptions {
}

interface IKeybindingDispatcherItem {
keybinding: Keybinding;
keybinding: Keybinding | null;
callback: IKeyBindingCallback;
}

Expand All @@ -62,18 +62,20 @@ export class KeybindingDispatcher {
}

public has(keybinding: KeyCode): boolean {
let target = createSimpleKeybinding(keybinding, platform.OS);
for (const a of this._arr) {
if (target.equals(a.keybinding)) {
return true;
let target = createKeybinding(keybinding, platform.OS);
if (target !== null) {
for (const a of this._arr) {
if (target.equals(a.keybinding)) {
return true;
}
}
}
return false;
}

public set(keybinding: number, callback: IKeyBindingCallback) {
this._arr.push({
keybinding: createSimpleKeybinding(keybinding, platform.OS),
keybinding: createKeybinding(keybinding, platform.OS),
callback: callback
});
}
Expand All @@ -82,7 +84,7 @@ export class KeybindingDispatcher {
// Loop from the last to the first to handle overwrites
for (let i = this._arr.length - 1; i >= 0; i--) {
let item = this._arr[i];
if (keybinding.equals(item.keybinding)) {
if (keybinding.toChord().equals(item.keybinding)) {
return item.callback;
}
}
Expand Down
80 changes: 40 additions & 40 deletions src/vs/base/test/common/keyCodes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,35 @@ suite('keyCodes', () => {
}

test(null, 0);
test(new SimpleKeybinding(false, false, false, false, KeyCode.Enter), KeyCode.Enter);
test(new SimpleKeybinding(true, false, false, false, KeyCode.Enter), KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, false, true, false, KeyCode.Enter), KeyMod.Alt | KeyCode.Enter);
test(new SimpleKeybinding(true, false, true, false, KeyCode.Enter), KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, true, false, false, KeyCode.Enter), KeyMod.Shift | KeyCode.Enter);
test(new SimpleKeybinding(true, true, false, false, KeyCode.Enter), KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, true, true, false, KeyCode.Enter), KeyMod.Shift | KeyMod.Alt | KeyCode.Enter);
test(new SimpleKeybinding(true, true, true, false, KeyCode.Enter), KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, false, false, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyCode.Enter);
test(new SimpleKeybinding(true, false, false, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, false, true, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter);
test(new SimpleKeybinding(true, false, true, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, true, false, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter);
test(new SimpleKeybinding(true, true, false, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, true, true, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.Enter);
test(new SimpleKeybinding(true, true, true, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, false, false, false, KeyCode.Enter).toChord(), KeyCode.Enter);
test(new SimpleKeybinding(true, false, false, false, KeyCode.Enter).toChord(), KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, false, true, false, KeyCode.Enter).toChord(), KeyMod.Alt | KeyCode.Enter);
test(new SimpleKeybinding(true, false, true, false, KeyCode.Enter).toChord(), KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, true, false, false, KeyCode.Enter).toChord(), KeyMod.Shift | KeyCode.Enter);
test(new SimpleKeybinding(true, true, false, false, KeyCode.Enter).toChord(), KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, true, true, false, KeyCode.Enter).toChord(), KeyMod.Shift | KeyMod.Alt | KeyCode.Enter);
test(new SimpleKeybinding(true, true, true, false, KeyCode.Enter).toChord(), KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, false, false, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyCode.Enter);
test(new SimpleKeybinding(true, false, false, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, false, true, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter);
test(new SimpleKeybinding(true, false, true, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, true, false, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter);
test(new SimpleKeybinding(true, true, false, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, true, true, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.Enter);
test(new SimpleKeybinding(true, true, true, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter);

test(
new ChordKeybinding(
new ChordKeybinding([
new SimpleKeybinding(false, false, false, false, KeyCode.Enter),
new SimpleKeybinding(false, false, false, false, KeyCode.Tab)
),
]),
KeyChord(KeyCode.Enter, KeyCode.Tab)
);
test(
new ChordKeybinding(
new ChordKeybinding([
new SimpleKeybinding(false, false, false, true, KeyCode.KEY_Y),
new SimpleKeybinding(false, false, false, false, KeyCode.KEY_Z)
),
]),
KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_Y, KeyCode.KEY_Z)
);
});
Expand All @@ -62,35 +62,35 @@ suite('keyCodes', () => {
}

test(null, 0);
test(new SimpleKeybinding(false, false, false, false, KeyCode.Enter), KeyCode.Enter);
test(new SimpleKeybinding(false, false, false, true, KeyCode.Enter), KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, false, true, false, KeyCode.Enter), KeyMod.Alt | KeyCode.Enter);
test(new SimpleKeybinding(false, false, true, true, KeyCode.Enter), KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, true, false, false, KeyCode.Enter), KeyMod.Shift | KeyCode.Enter);
test(new SimpleKeybinding(false, true, false, true, KeyCode.Enter), KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, true, true, false, KeyCode.Enter), KeyMod.Shift | KeyMod.Alt | KeyCode.Enter);
test(new SimpleKeybinding(false, true, true, true, KeyCode.Enter), KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(true, false, false, false, KeyCode.Enter), KeyMod.CtrlCmd | KeyCode.Enter);
test(new SimpleKeybinding(true, false, false, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(true, false, true, false, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter);
test(new SimpleKeybinding(true, false, true, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(true, true, false, false, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter);
test(new SimpleKeybinding(true, true, false, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(true, true, true, false, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.Enter);
test(new SimpleKeybinding(true, true, true, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, false, false, false, KeyCode.Enter).toChord(), KeyCode.Enter);
test(new SimpleKeybinding(false, false, false, true, KeyCode.Enter).toChord(), KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, false, true, false, KeyCode.Enter).toChord(), KeyMod.Alt | KeyCode.Enter);
test(new SimpleKeybinding(false, false, true, true, KeyCode.Enter).toChord(), KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, true, false, false, KeyCode.Enter).toChord(), KeyMod.Shift | KeyCode.Enter);
test(new SimpleKeybinding(false, true, false, true, KeyCode.Enter).toChord(), KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(false, true, true, false, KeyCode.Enter).toChord(), KeyMod.Shift | KeyMod.Alt | KeyCode.Enter);
test(new SimpleKeybinding(false, true, true, true, KeyCode.Enter).toChord(), KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(true, false, false, false, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyCode.Enter);
test(new SimpleKeybinding(true, false, false, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(true, false, true, false, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter);
test(new SimpleKeybinding(true, false, true, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(true, true, false, false, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter);
test(new SimpleKeybinding(true, true, false, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter);
test(new SimpleKeybinding(true, true, true, false, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.Enter);
test(new SimpleKeybinding(true, true, true, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter);

test(
new ChordKeybinding(
new ChordKeybinding([
new SimpleKeybinding(false, false, false, false, KeyCode.Enter),
new SimpleKeybinding(false, false, false, false, KeyCode.Tab)
),
]),
KeyChord(KeyCode.Enter, KeyCode.Tab)
);
test(
new ChordKeybinding(
new ChordKeybinding([
new SimpleKeybinding(true, false, false, false, KeyCode.KEY_Y),
new SimpleKeybinding(false, false, false, false, KeyCode.KEY_Z)
),
]),
KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_Y, KeyCode.KEY_Z)
);

Expand Down
2 changes: 1 addition & 1 deletion src/vs/editor/standalone/browser/simpleServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ export class StandaloneKeybindingService extends AbstractKeybindingService {
keyboardEvent.altKey,
keyboardEvent.metaKey,
keyboardEvent.keyCode
);
).toChord();
return new USLayoutResolvedKeybinding(keybinding, OS);
}

Expand Down
Loading