diff --git a/src/vs/base/common/keybindingParser.ts b/src/vs/base/common/keybindingParser.ts index b49477c1b0ecaa9599a0ded5e76d11e525e713d3..68025d9131b129252c1178d36b1b02a639341abd 100644 --- a/src/vs/base/common/keybindingParser.ts +++ b/src/vs/base/common/keybindingParser.ts @@ -107,24 +107,17 @@ export class KeybindingParser { return [new SimpleKeybinding(mods.ctrl, mods.shift, mods.alt, mods.meta, keyCode), mods.remains]; } - static parseUserBinding(input: string): Array { - // TODO@chords: allow users to define N chords + static parseUserBinding(input: string): (SimpleKeybinding | ScanCodeBinding)[] { if (!input) { return []; } - let parts = []; - let remains = input; - while (remains.length > 0) { - let [part, nextRemains] = this.parseSimpleUserBinding(remains); + let parts: (SimpleKeybinding | ScanCodeBinding)[] = []; + let part: SimpleKeybinding | ScanCodeBinding; + + while (input.length > 0) { + [part, input] = this.parseSimpleUserBinding(input); parts.push(part); - // check equality to break out of a possible infinite loop. - // if nothing was consumed it implies that an empty keybinding - // was returned. - if (remains === nextRemains) { - break; - } - remains = nextRemains; } return parts; } diff --git a/src/vs/platform/keybinding/common/keybindingResolver.ts b/src/vs/platform/keybinding/common/keybindingResolver.ts index 6bee34f567f4227562828683b4003cd7e391b106..9b714a1150e1ad7f31f5fe517341b338d9d40ec4 100644 --- a/src/vs/platform/keybinding/common/keybindingResolver.ts +++ b/src/vs/platform/keybinding/common/keybindingResolver.ts @@ -45,6 +45,7 @@ export class KeybindingResolver { continue; } + // TODO@chords this._addKeyPress(k.keypressParts[0], k); } } @@ -53,9 +54,11 @@ export class KeybindingResolver { if (defaultKb.command !== command) { return false; } + // TODO@chords if (keypressFirstPart && defaultKb.keypressParts[0] !== keypressFirstPart) { return false; } + // TODO@chords if (keypressChordPart && defaultKb.keypressParts[1] !== keypressChordPart) { return false; } @@ -84,6 +87,7 @@ export class KeybindingResolver { } const command = override.command.substr(1); + // TODO@chords const keypressFirstPart = override.keypressParts[0]; const keypressChordPart = override.keypressParts[1]; const when = override.when; @@ -117,6 +121,7 @@ export class KeybindingResolver { const conflictIsChord = (conflict.keypressParts.length > 1); const itemIsChord = (item.keypressParts.length > 1); + // TODO@chords if (conflictIsChord && itemIsChord && conflict.keypressParts[1] !== item.keypressParts[1]) { // The conflict only shares the chord start with this command continue; @@ -247,6 +252,7 @@ export class KeybindingResolver { lookupMap = []; for (let i = 0, len = candidates.length; i < len; i++) { let candidate = candidates[i]; + // TODO@chords if (candidate.keypressParts[1] === keypress) { lookupMap.push(candidate); } @@ -266,6 +272,7 @@ export class KeybindingResolver { return null; } + // TODO@chords if (currentChord === null && result.keypressParts.length > 1 && result.keypressParts[1] !== null) { return { enterChord: true, diff --git a/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts b/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts index 3520f824b7d4d898bfc3b8def0b21dac37234fbc..7dfe09262d16d79deba08dc7a834cff84d854277 100644 --- a/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts +++ b/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts @@ -10,8 +10,8 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; export class ResolvedKeybindingItem { _resolvedKeybindingItemBrand: void; - public readonly keypressParts: string[]; public readonly resolvedKeybinding: ResolvedKeybinding | null; + public readonly keypressParts: string[]; public readonly bubble: boolean; public readonly command: string | null; public readonly commandArgs: any; @@ -20,11 +20,7 @@ export class ResolvedKeybindingItem { constructor(resolvedKeybinding: ResolvedKeybinding | null, command: string | null, commandArgs: any, when: ContextKeyExpr | null, isDefault: boolean) { this.resolvedKeybinding = resolvedKeybinding; - if (resolvedKeybinding) { - this.keypressParts = resolvedKeybinding.getDispatchParts(); - } else { - this.keypressParts = []; - } + this.keypressParts = resolvedKeybinding ? removeElementsAfterNulls(resolvedKeybinding.getDispatchParts()) : []; this.bubble = (command ? command.charCodeAt(0) === CharCode.Caret : false); this.command = this.bubble ? command!.substr(1) : command; this.commandArgs = commandArgs; @@ -32,3 +28,16 @@ export class ResolvedKeybindingItem { this.isDefault = isDefault; } } + +export function removeElementsAfterNulls(arr: (T | null)[]): T[] { + let result: T[] = []; + for (let i = 0, len = arr.length; i < len; i++) { + const element = arr[i]; + if (!element) { + // stop processing at first encountered null + return result; + } + result.push(element); + } + return result; +} diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts index cc50735056cad23801593a9d49efdda393f5283c..35f7916e4369180cea08c82d4ff6b6276c293a15 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts @@ -272,11 +272,12 @@ export class KeybindingEditorDecorationsRenderer extends Disposable { return false; } - for (let i = 0, length = aParts.length; i < length; i++) { + for (let i = 0, len = aParts.length; i < len; i++) { if (!this._userBindingEquals(aParts[i], bParts[i])) { return false; } } + return true; } diff --git a/src/vs/workbench/contrib/preferences/test/browser/keybindingsEditorContribution.test.ts b/src/vs/workbench/contrib/preferences/test/browser/keybindingsEditorContribution.test.ts index d5db6231785173bc223ecae863c56d094ea14262..53b1167b8e8c338048757dfa9f9fcc203bfb0b98 100644 --- a/src/vs/workbench/contrib/preferences/test/browser/keybindingsEditorContribution.test.ts +++ b/src/vs/workbench/contrib/preferences/test/browser/keybindingsEditorContribution.test.ts @@ -11,8 +11,6 @@ suite('KeybindingsEditorContribution', () => { function assertUserSettingsFuzzyEquals(a: string, b: string, expected: boolean): void { const actual = KeybindingEditorDecorationsRenderer._userSettingsFuzzyEquals(a, b); const message = expected ? `${a} == ${b}` : `${a} != ${b}`; - console.log(a); - console.log(b); assert.equal(actual, expected, 'fuzzy: ' + message); } diff --git a/src/vs/workbench/services/keybinding/common/keybindingIO.ts b/src/vs/workbench/services/keybinding/common/keybindingIO.ts index 236e14551c5c4373f32c954ae59a9518badf2f7e..4a32a43c9dce9a93f061393f0ad938e89d4759a9 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingIO.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingIO.ts @@ -5,15 +5,13 @@ import { SimpleKeybinding } from 'vs/base/common/keyCodes'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; -import { OperatingSystem } from 'vs/base/common/platform'; import { ScanCodeBinding } from 'vs/base/common/scanCode'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; export interface IUserKeybindingItem { - firstPart: SimpleKeybinding | ScanCodeBinding | null; - chordPart: SimpleKeybinding | ScanCodeBinding | null; + parts: (SimpleKeybinding | ScanCodeBinding)[]; command: string | null; commandArgs?: any; when: ContextKeyExpr | null; @@ -21,7 +19,7 @@ export interface IUserKeybindingItem { export class KeybindingIO { - public static writeKeybindingItem(out: OutputBuilder, item: ResolvedKeybindingItem, OS: OperatingSystem): void { + public static writeKeybindingItem(out: OutputBuilder, item: ResolvedKeybindingItem): void { if (!item.resolvedKeybinding) { return; } @@ -41,15 +39,13 @@ export class KeybindingIO { out.write('}'); } - public static readUserKeybindingItem(input: IUserFriendlyKeybinding, OS: OperatingSystem): IUserKeybindingItem { - // TODO yusuke: replace with keychords. - const [firstPart, chordPart] = (typeof input.key === 'string' ? KeybindingParser.parseUserBinding(input.key) : [null, null]); + public static readUserKeybindingItem(input: IUserFriendlyKeybinding): IUserKeybindingItem { + const parts = (typeof input.key === 'string' ? KeybindingParser.parseUserBinding(input.key) : []); const when = (typeof input.when === 'string' ? ContextKeyExpr.deserialize(input.when) : null); const command = (typeof input.command === 'string' ? input.command : null); const commandArgs = (typeof input.args !== 'undefined' ? input.args : undefined); return { - firstPart: firstPart, - chordPart: chordPart, + parts: parts, command: command, commandArgs: commandArgs, when: when diff --git a/src/vs/workbench/services/keybinding/common/keyboardMapper.ts b/src/vs/workbench/services/keybinding/common/keyboardMapper.ts index aceb842f3d360bdf85b7746ea9ad843b2146e8e6..373700f973fdcd8e59b8704b6725df742ef7356c 100644 --- a/src/vs/workbench/services/keybinding/common/keyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/keyboardMapper.ts @@ -11,7 +11,7 @@ export interface IKeyboardMapper { dumpDebugInfo(): string; resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[]; resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding; - resolveUserBinding(firstPart: Array): ResolvedKeybinding[]; + resolveUserBinding(firstPart: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[]; } export class CachedKeyboardMapper implements IKeyboardMapper { @@ -43,7 +43,7 @@ export class CachedKeyboardMapper implements IKeyboardMapper { return this._actual.resolveKeyboardEvent(keyboardEvent); } - public resolveUserBinding(parts: Array): ResolvedKeybinding[] { + public resolveUserBinding(parts: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[] { return this._actual.resolveUserBinding(parts); } } diff --git a/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts b/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts index 82961d0b52cddca0d54b8f83adb41e8325642b58..23e3521e20f3829711e3350833a3ce198b5d6dbe 100644 --- a/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts @@ -9,6 +9,7 @@ import { IMMUTABLE_CODE_TO_KEY_CODE, ScanCode, ScanCodeBinding } from 'vs/base/c import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; +import { removeElementsAfterNulls } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; /** * A keyboard mapper to be used when reading the keymap from the OS fails. @@ -117,8 +118,8 @@ export class MacLinuxFallbackKeyboardMapper implements IKeyboardMapper { return new SimpleKeybinding(binding.ctrlKey, binding.shiftKey, binding.altKey, binding.metaKey, keyCode); } - public resolveUserBinding(input: Array): ResolvedKeybinding[] { - let parts: SimpleKeybinding[] = input.map(this._resolveSimpleUserBinding.bind(this)); + public resolveUserBinding(input: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[] { + const parts: SimpleKeybinding[] = removeElementsAfterNulls(input.map(keybinding => this._resolveSimpleUserBinding(keybinding))); if (parts.length > 0) { return [new USLayoutResolvedKeybinding(new ChordKeybinding(parts), this._OS)]; } diff --git a/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts b/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts index 8b58a308145606dfc8b3a831435d2a8dabf970eb..16481ba5b63416b5efd5769d7b257f8fd64e5bc0 100644 --- a/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts @@ -1053,8 +1053,8 @@ export class MacLinuxKeyboardMapper implements IKeyboardMapper { return this.simpleKeybindingToScanCodeBinding(binding); } - public resolveUserBinding(input: Array): ResolvedKeybinding[] { - let parts: ScanCodeBinding[][] = input.map(this._resolveSimpleUserBinding.bind(this)); + public resolveUserBinding(input: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[] { + const parts: ScanCodeBinding[][] = input.map(keybinding => this._resolveSimpleUserBinding(keybinding)); let result: NativeResolvedKeybinding[] = []; this._generateResolvedKeybindings(parts, 0, [], result); return result; diff --git a/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts b/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts index 64f5472248a7b81ca287d71c89de74a1db86785e..ee848b09d0f9825984f0379bdf0942ef0e31d904 100644 --- a/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts @@ -11,6 +11,7 @@ import { IMMUTABLE_CODE_TO_KEY_CODE, ScanCode, ScanCodeBinding, ScanCodeUtils } import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; import { BaseResolvedKeybinding } from 'vs/platform/keybinding/common/baseResolvedKeybinding'; +import { removeElementsAfterNulls } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; export interface IWindowsKeyMapping { vkey: string; @@ -464,8 +465,8 @@ export class WindowsKeyboardMapper implements IKeyboardMapper { return new SimpleKeybinding(binding.ctrlKey, binding.shiftKey, binding.altKey, binding.metaKey, keyCode); } - public resolveUserBinding(input: Array): ResolvedKeybinding[] { - let parts: SimpleKeybinding[] = input.map(this._resolveSimpleUserBinding.bind(this)); + public resolveUserBinding(input: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[] { + const parts: SimpleKeybinding[] = removeElementsAfterNulls(input.map(keybinding => this._resolveSimpleUserBinding(keybinding))); if (parts.length > 0) { return [new WindowsNativeResolvedKeybinding(this, parts)]; } diff --git a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts index 15551e868f75d7a1ef80ce6a1073bf80d25c835f..176bb4e1a86674d31be071c5ef1b0db62fc4d21b 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts @@ -412,16 +412,11 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { let result: ResolvedKeybindingItem[] = [], resultLen = 0; for (const item of items) { const when = (item.when ? item.when.normalize() : null); - const firstPart = item.firstPart; - const chordPart = item.chordPart; - if (!firstPart) { + const parts = item.parts; + if (parts.length === 0) { // This might be a removal keybinding item in user settings => accept it result[resultLen++] = new ResolvedKeybindingItem(null, item.command, item.commandArgs, when, isDefault); } else { - let parts = [firstPart]; - if (chordPart !== null) { - parts.push(chordPart); - } const resolvedKeybindings = this._keyboardMapper.resolveUserBinding(parts); for (const resolvedKeybinding of resolvedKeybindings) { result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault); @@ -447,7 +442,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { }); } - return extraUserKeybindings.map((k) => KeybindingIO.readUserKeybindingItem(k, OS)); + return extraUserKeybindings.map((k) => KeybindingIO.readUserKeybindingItem(k)); } public resolveKeybinding(kb: Keybinding): ResolvedKeybinding[] { @@ -540,7 +535,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { let lastIndex = defaultKeybindings.length - 1; defaultKeybindings.forEach((k, index) => { - KeybindingIO.writeKeybindingItem(out, k, OS); + KeybindingIO.writeKeybindingItem(out, k); if (index !== lastIndex) { out.writeLine(','); } else { diff --git a/src/vs/workbench/services/keybinding/test/keybindingIO.test.ts b/src/vs/workbench/services/keybinding/test/keybindingIO.test.ts index 4a3ae32202834c6c9a03e6bbe58f0d6144ce85d2..99a2ef11a0858e5642988bf21dc65fe5176c5f34 100644 --- a/src/vs/workbench/services/keybinding/test/keybindingIO.test.ts +++ b/src/vs/workbench/services/keybinding/test/keybindingIO.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { KeyChord, KeyCode, KeyMod, SimpleKeybinding, createKeybinding } from 'vs/base/common/keyCodes'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; -import { OS, OperatingSystem } from 'vs/base/common/platform'; +import { OperatingSystem } from 'vs/base/common/platform'; import { ScanCode, ScanCodeBinding } from 'vs/base/common/scanCode'; import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; @@ -126,37 +126,35 @@ suite('keybindingIO', () => { test('issue #10452 - invalid command', () => { let strJSON = `[{ "key": "ctrl+k ctrl+f", "command": ["firstcommand", "seccondcommand"] }]`; let userKeybinding = JSON.parse(strJSON)[0]; - let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding, OS); + let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding); assert.equal(keybindingItem.command, null); }); test('issue #10452 - invalid when', () => { let strJSON = `[{ "key": "ctrl+k ctrl+f", "command": "firstcommand", "when": [] }]`; let userKeybinding = JSON.parse(strJSON)[0]; - let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding, OS); + let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding); assert.equal(keybindingItem.when, null); }); test('issue #10452 - invalid key', () => { let strJSON = `[{ "key": [], "command": "firstcommand" }]`; let userKeybinding = JSON.parse(strJSON)[0]; - let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding, OS); - assert.equal(keybindingItem.firstPart, null); - assert.equal(keybindingItem.chordPart, null); + let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding); + assert.deepEqual(keybindingItem.parts, []); }); test('issue #10452 - invalid key 2', () => { let strJSON = `[{ "key": "", "command": "firstcommand" }]`; let userKeybinding = JSON.parse(strJSON)[0]; - let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding, OS); - assert.equal(keybindingItem.firstPart, null); - assert.equal(keybindingItem.chordPart, null); + let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding); + assert.deepEqual(keybindingItem.parts, []); }); test('test commands args', () => { let strJSON = `[{ "key": "ctrl+k ctrl+f", "command": "firstcommand", "when": [], "args": { "text": "theText" } }]`; let userKeybinding = JSON.parse(strJSON)[0]; - let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding, OS); + let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding); assert.equal(keybindingItem.commandArgs.text, 'theText'); }); }); diff --git a/src/vs/workbench/services/keybinding/test/keyboardMapperTestUtils.ts b/src/vs/workbench/services/keybinding/test/keyboardMapperTestUtils.ts index ee574285582faf32d6219cb82b4ff6b674b74054..8c8b209a0024ed12af1ed404b03fb541de077462 100644 --- a/src/vs/workbench/services/keybinding/test/keyboardMapperTestUtils.ts +++ b/src/vs/workbench/services/keybinding/test/keyboardMapperTestUtils.ts @@ -44,7 +44,7 @@ export function assertResolveKeyboardEvent(mapper: IKeyboardMapper, keyboardEven assert.deepEqual(actual, expected); } -export function assertResolveUserBinding(mapper: IKeyboardMapper, parts: Array, expected: IResolvedKeybinding[]): void { +export function assertResolveUserBinding(mapper: IKeyboardMapper, parts: (SimpleKeybinding | ScanCodeBinding)[], expected: IResolvedKeybinding[]): void { let actual: IResolvedKeybinding[] = mapper.resolveUserBinding(parts).map(toIResolvedKeybinding); assert.deepEqual(actual, expected); }