SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

View File

@@ -0,0 +1,168 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import { ResolvedKeybinding, Keybinding } from 'vs/base/common/keyCodes';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import Severity from 'vs/base/common/severity';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { KeybindingResolver, IResolveResult } from 'vs/platform/keybinding/common/keybindingResolver';
import { IKeybindingEvent, IKeybindingService, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey';
import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
import { IMessageService } from 'vs/platform/message/common/message';
import Event, { Emitter } from 'vs/base/common/event';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
interface CurrentChord {
keypress: string;
label: string;
}
export abstract class AbstractKeybindingService implements IKeybindingService {
public _serviceBrand: any;
protected toDispose: IDisposable[] = [];
private _currentChord: CurrentChord;
private _currentChordStatusMessage: IDisposable;
protected _onDidUpdateKeybindings: Emitter<IKeybindingEvent>;
private _contextKeyService: IContextKeyService;
protected _commandService: ICommandService;
private _statusService: IStatusbarService;
private _messageService: IMessageService;
constructor(
contextKeyService: IContextKeyService,
commandService: ICommandService,
messageService: IMessageService,
statusService?: IStatusbarService
) {
this._contextKeyService = contextKeyService;
this._commandService = commandService;
this._statusService = statusService;
this._messageService = messageService;
this._currentChord = null;
this._currentChordStatusMessage = null;
this._onDidUpdateKeybindings = new Emitter<IKeybindingEvent>();
this.toDispose.push(this._onDidUpdateKeybindings);
}
public dispose(): void {
this.toDispose = dispose(this.toDispose);
}
get onDidUpdateKeybindings(): Event<IKeybindingEvent> {
return this._onDidUpdateKeybindings ? this._onDidUpdateKeybindings.event : Event.None; // Sinon stubbing walks properties on prototype
}
protected abstract _getResolver(): KeybindingResolver;
public abstract resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[];
public abstract resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding;
public abstract resolveUserBinding(userBinding: string): ResolvedKeybinding[];
public getDefaultKeybindingsContent(): string {
return '';
}
public getDefaultKeybindings(): ResolvedKeybindingItem[] {
return this._getResolver().getDefaultKeybindings();
}
public getKeybindings(): ResolvedKeybindingItem[] {
return this._getResolver().getKeybindings();
}
public customKeybindingsCount(): number {
return 0;
}
public lookupKeybindings(commandId: string): ResolvedKeybinding[] {
return this._getResolver().lookupKeybindings(commandId).map(item => item.resolvedKeybinding);
}
public lookupKeybinding(commandId: string): ResolvedKeybinding {
let result = this._getResolver().lookupPrimaryKeybinding(commandId);
if (!result) {
return null;
}
return result.resolvedKeybinding;
}
public softDispatch(e: IKeyboardEvent, target: IContextKeyServiceTarget): IResolveResult {
const keybinding = this.resolveKeyboardEvent(e);
if (keybinding.isChord()) {
console.warn('Unexpected keyboard event mapped to a chord');
return null;
}
const [firstPart,] = keybinding.getDispatchParts();
if (firstPart === null) {
// cannot be dispatched, probably only modifier keys
return null;
}
const contextValue = this._contextKeyService.getContext(target);
const currentChord = this._currentChord ? this._currentChord.keypress : null;
return this._getResolver().resolve(contextValue, currentChord, firstPart);
}
protected _dispatch(e: IKeyboardEvent, target: IContextKeyServiceTarget): boolean {
let shouldPreventDefault = false;
const keybinding = this.resolveKeyboardEvent(e);
if (keybinding.isChord()) {
console.warn('Unexpected keyboard event mapped to a chord');
return null;
}
const [firstPart,] = keybinding.getDispatchParts();
if (firstPart === null) {
// cannot be dispatched, probably only modifier keys
return shouldPreventDefault;
}
const contextValue = this._contextKeyService.getContext(target);
const currentChord = this._currentChord ? this._currentChord.keypress : null;
const keypressLabel = keybinding.getLabel();
const resolveResult = this._getResolver().resolve(contextValue, currentChord, firstPart);
if (resolveResult && resolveResult.enterChord) {
shouldPreventDefault = true;
this._currentChord = {
keypress: firstPart,
label: keypressLabel
};
if (this._statusService) {
this._currentChordStatusMessage = this._statusService.setStatusMessage(nls.localize('first.chord', "({0}) was pressed. Waiting for second key of chord...", keypressLabel));
}
return shouldPreventDefault;
}
if (this._statusService && this._currentChord) {
if (!resolveResult || !resolveResult.commandId) {
this._statusService.setStatusMessage(nls.localize('missing.chord', "The key combination ({0}, {1}) is not a command.", this._currentChord.label, keypressLabel), 10 * 1000 /* 10s */);
shouldPreventDefault = true;
}
}
if (this._currentChordStatusMessage) {
this._currentChordStatusMessage.dispose();
this._currentChordStatusMessage = null;
}
this._currentChord = null;
if (resolveResult && resolveResult.commandId) {
if (!resolveResult.bubble) {
shouldPreventDefault = true;
}
this._commandService.executeCommand(resolveResult.commandId, resolveResult.commandArgs || {}).done(undefined, err => {
this._messageService.show(Severity.Warning, err);
});
}
return shouldPreventDefault;
}
}

View File

@@ -0,0 +1,81 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ResolvedKeybinding, Keybinding, KeyCode } from 'vs/base/common/keyCodes';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey';
import { IResolveResult } from 'vs/platform/keybinding/common/keybindingResolver';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
import Event from 'vs/base/common/event';
export interface IUserFriendlyKeybinding {
key: string;
command: string;
args?: any;
when?: string;
}
export enum KeybindingSource {
Default = 1,
User
}
export interface IKeybindingEvent {
source: KeybindingSource;
keybindings?: IUserFriendlyKeybinding[];
}
export interface IKeyboardEvent {
readonly ctrlKey: boolean;
readonly shiftKey: boolean;
readonly altKey: boolean;
readonly metaKey: boolean;
readonly keyCode: KeyCode;
readonly code: string;
}
export const IKeybindingService = createDecorator<IKeybindingService>('keybindingService');
export interface IKeybindingService {
_serviceBrand: any;
onDidUpdateKeybindings: Event<IKeybindingEvent>;
/**
* Returns none, one or many (depending on keyboard layout)!
*/
resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[];
resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding;
resolveUserBinding(userBinding: string): ResolvedKeybinding[];
/**
* Resolve and dispatch `keyboardEvent`, but do not invoke the command or change inner state.
*/
softDispatch(keyboardEvent: IKeyboardEvent, target: IContextKeyServiceTarget): IResolveResult;
/**
* Look up keybindings for a command.
* Use `lookupKeybinding` if you are interested in the preferred keybinding.
*/
lookupKeybindings(commandId: string): ResolvedKeybinding[];
/**
* Look up the preferred (last defined) keybinding for a command.
* @returns The preferred keybinding or null if the command is not bound.
*/
lookupKeybinding(commandId: string): ResolvedKeybinding;
getDefaultKeybindingsContent(): string;
getDefaultKeybindings(): ResolvedKeybindingItem[];
getKeybindings(): ResolvedKeybindingItem[];
customKeybindingsCount(): number;
}

View File

@@ -0,0 +1,323 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { ContextKeyExpr, IContext } from 'vs/platform/contextkey/common/contextkey';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
import { CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
export interface IResolveResult {
enterChord: boolean;
commandId: string;
commandArgs: any;
bubble: boolean;
}
export class KeybindingResolver {
private readonly _defaultKeybindings: ResolvedKeybindingItem[];
private readonly _keybindings: ResolvedKeybindingItem[];
private readonly _defaultBoundCommands: Map<string, boolean>;
private readonly _map: Map<string, ResolvedKeybindingItem[]>;
private readonly _lookupMap: Map<string, ResolvedKeybindingItem[]>;
constructor(defaultKeybindings: ResolvedKeybindingItem[], overrides: ResolvedKeybindingItem[]) {
this._defaultKeybindings = defaultKeybindings;
this._defaultBoundCommands = new Map<string, boolean>();
for (let i = 0, len = defaultKeybindings.length; i < len; i++) {
const command = defaultKeybindings[i].command;
this._defaultBoundCommands.set(command, true);
}
this._map = new Map<string, ResolvedKeybindingItem[]>();
this._lookupMap = new Map<string, ResolvedKeybindingItem[]>();
this._keybindings = KeybindingResolver.combine(defaultKeybindings, overrides);
for (let i = 0, len = this._keybindings.length; i < len; i++) {
let k = this._keybindings[i];
if (k.keypressFirstPart === null) {
// unbound
continue;
}
this._addKeyPress(k.keypressFirstPart, k);
}
}
private static _isTargetedForRemoval(defaultKb: ResolvedKeybindingItem, keypressFirstPart: string, keypressChordPart: string, command: string, when: ContextKeyExpr): boolean {
if (defaultKb.command !== command) {
return false;
}
if (keypressFirstPart && defaultKb.keypressFirstPart !== keypressFirstPart) {
return false;
}
if (keypressChordPart && defaultKb.keypressChordPart !== keypressChordPart) {
return false;
}
if (when) {
if (!defaultKb.when) {
return false;
}
if (!when.equals(defaultKb.when)) {
return false;
}
}
return true;
}
/**
* Looks for rules containing -command in `overrides` and removes them directly from `defaults`.
*/
public static combine(defaults: ResolvedKeybindingItem[], rawOverrides: ResolvedKeybindingItem[]): ResolvedKeybindingItem[] {
defaults = defaults.slice(0);
let overrides: ResolvedKeybindingItem[] = [];
for (let i = 0, len = rawOverrides.length; i < len; i++) {
const override = rawOverrides[i];
if (!override.command || override.command.length === 0 || override.command.charAt(0) !== '-') {
overrides.push(override);
continue;
}
const command = override.command.substr(1);
const keypressFirstPart = override.keypressFirstPart;
const keypressChordPart = override.keypressChordPart;
const when = override.when;
for (let j = defaults.length - 1; j >= 0; j--) {
if (this._isTargetedForRemoval(defaults[j], keypressFirstPart, keypressChordPart, command, when)) {
defaults.splice(j, 1);
}
}
}
return defaults.concat(overrides);
}
private _addKeyPress(keypress: string, item: ResolvedKeybindingItem): void {
const conflicts = this._map.get(keypress);
if (typeof conflicts === 'undefined') {
// There is no conflict so far
this._map.set(keypress, [item]);
this._addToLookupMap(item);
return;
}
for (let i = conflicts.length - 1; i >= 0; i--) {
let conflict = conflicts[i];
if (conflict.command === item.command) {
continue;
}
const conflictIsChord = (conflict.keypressChordPart !== null);
const itemIsChord = (item.keypressChordPart !== null);
if (conflictIsChord && itemIsChord && conflict.keypressChordPart !== item.keypressChordPart) {
// The conflict only shares the chord start with this command
continue;
}
if (KeybindingResolver.whenIsEntirelyIncluded(true, conflict.when, item.when)) {
// `item` completely overwrites `conflict`
// Remove conflict from the lookupMap
this._removeFromLookupMap(conflict);
}
}
conflicts.push(item);
this._addToLookupMap(item);
}
private _addToLookupMap(item: ResolvedKeybindingItem): void {
if (!item.command) {
return;
}
let arr = this._lookupMap.get(item.command);
if (typeof arr === 'undefined') {
arr = [item];
this._lookupMap.set(item.command, arr);
} else {
arr.push(item);
}
}
private _removeFromLookupMap(item: ResolvedKeybindingItem): void {
let arr = this._lookupMap.get(item.command);
if (typeof arr === 'undefined') {
return;
}
for (let i = 0, len = arr.length; i < len; i++) {
if (arr[i] === item) {
arr.splice(i, 1);
return;
}
}
}
/**
* 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).
*/
public static whenIsEntirelyIncluded(inNormalizedForm: boolean, a: ContextKeyExpr, b: ContextKeyExpr): boolean {
if (!inNormalizedForm) {
a = a ? a.normalize() : null;
b = b ? b.normalize() : null;
}
if (!b) {
return true;
}
if (!a) {
return false;
}
let aRulesArr = a.serialize().split(' && ');
let bRulesArr = b.serialize().split(' && ');
let aRules: { [rule: string]: boolean; } = Object.create(null);
for (let i = 0, len = aRulesArr.length; i < len; i++) {
aRules[aRulesArr[i]] = true;
}
for (let i = 0, len = bRulesArr.length; i < len; i++) {
if (!aRules[bRulesArr[i]]) {
return false;
}
}
return true;
}
public getDefaultBoundCommands(): Map<string, boolean> {
return this._defaultBoundCommands;
}
public getDefaultKeybindings(): ResolvedKeybindingItem[] {
return this._defaultKeybindings;
}
public getKeybindings(): ResolvedKeybindingItem[] {
return this._keybindings;
}
public lookupKeybindings(commandId: string): ResolvedKeybindingItem[] {
let items = this._lookupMap.get(commandId);
if (typeof items === 'undefined' || items.length === 0) {
return [];
}
// Reverse to get the most specific item first
let result: ResolvedKeybindingItem[] = [], resultLen = 0;
for (let i = items.length - 1; i >= 0; i--) {
result[resultLen++] = items[i];
}
return result;
}
public lookupPrimaryKeybinding(commandId: string): ResolvedKeybindingItem {
let items = this._lookupMap.get(commandId);
if (typeof items === 'undefined' || items.length === 0) {
return null;
}
return items[items.length - 1];
}
public resolve(context: IContext, currentChord: string, keypress: string): IResolveResult {
let lookupMap: ResolvedKeybindingItem[] = null;
if (currentChord !== null) {
// Fetch all chord bindings for `currentChord`
const candidates = this._map.get(currentChord);
if (typeof candidates === 'undefined') {
// No chords starting with `currentChord`
return null;
}
lookupMap = [];
for (let i = 0, len = candidates.length; i < len; i++) {
let candidate = candidates[i];
if (candidate.keypressChordPart === keypress) {
lookupMap.push(candidate);
}
}
} else {
const candidates = this._map.get(keypress);
if (typeof candidates === 'undefined') {
// No bindings with `keypress`
return null;
}
lookupMap = candidates;
}
let result = this._findCommand(context, lookupMap);
if (!result) {
return null;
}
if (currentChord === null && result.keypressChordPart !== null) {
return {
enterChord: true,
commandId: null,
commandArgs: null,
bubble: false
};
}
return {
enterChord: false,
commandId: result.command,
commandArgs: result.commandArgs,
bubble: result.bubble
};
}
private _findCommand(context: IContext, matches: ResolvedKeybindingItem[]): ResolvedKeybindingItem {
for (let i = matches.length - 1; i >= 0; i--) {
let k = matches[i];
if (!KeybindingResolver.contextMatchesRules(context, k.when)) {
continue;
}
return k;
}
return null;
}
public static contextMatchesRules(context: IContext, rules: ContextKeyExpr): boolean {
if (!rules) {
return true;
}
return rules.evaluate(context);
}
public static getAllUnboundCommands(boundCommands: Map<string, boolean>): string[] {
const commands = CommandsRegistry.getCommands();
const unboundCommands: string[] = [];
for (let id in commands) {
if (id[0] === '_' || id.indexOf('vscode.') === 0) { // private command
continue;
}
if (typeof commands[id].description === 'object'
&& !isFalsyOrEmpty((<ICommandHandlerDescription>commands[id].description).args)) { // command with args
continue;
}
if (boundCommands.get(id) === true) {
continue;
}
unboundCommands.push(id);
}
return unboundCommands;
}
}

View File

@@ -0,0 +1,245 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { SimpleKeybinding, KeyCode, KeybindingType, createKeybinding, Keybinding } from 'vs/base/common/keyCodes';
import { OS, OperatingSystem } from 'vs/base/common/platform';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { CommandsRegistry, ICommandHandler, ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
import { Registry } from 'vs/platform/registry/common/platform';
export interface IKeybindingItem {
keybinding: Keybinding;
command: string;
commandArgs?: any;
when: ContextKeyExpr;
weight1: number;
weight2: number;
}
export interface IKeybindings {
primary: number;
secondary?: number[];
win?: {
primary: number;
secondary?: number[];
};
linux?: {
primary: number;
secondary?: number[];
};
mac?: {
primary: number;
secondary?: number[];
};
}
export interface IKeybindingRule extends IKeybindings {
id: string;
weight: number;
when: ContextKeyExpr;
}
export interface IKeybindingRule2 {
primary: Keybinding;
win?: { primary: Keybinding; };
linux?: { primary: Keybinding; };
mac?: { primary: Keybinding; };
id: string;
weight: number;
when: ContextKeyExpr;
}
export interface ICommandAndKeybindingRule extends IKeybindingRule {
handler: ICommandHandler;
description?: ICommandHandlerDescription;
}
export interface IKeybindingsRegistry {
registerKeybindingRule(rule: IKeybindingRule): void;
registerKeybindingRule2(rule: IKeybindingRule2): void;
registerCommandAndKeybindingRule(desc: ICommandAndKeybindingRule): void;
getDefaultKeybindings(): IKeybindingItem[];
WEIGHT: {
editorCore(importance?: number): number;
editorContrib(importance?: number): number;
workbenchContrib(importance?: number): number;
builtinExtension(importance?: number): number;
externalExtension(importance?: number): number;
};
}
class KeybindingsRegistryImpl implements IKeybindingsRegistry {
private _keybindings: IKeybindingItem[];
public WEIGHT = {
editorCore: (importance: number = 0): number => {
return 0 + importance;
},
editorContrib: (importance: number = 0): number => {
return 100 + importance;
},
workbenchContrib: (importance: number = 0): number => {
return 200 + importance;
},
builtinExtension: (importance: number = 0): number => {
return 300 + importance;
},
externalExtension: (importance: number = 0): number => {
return 400 + importance;
}
};
constructor() {
this._keybindings = [];
}
/**
* Take current platform into account and reduce to primary & secondary.
*/
private static bindToCurrentPlatform(kb: IKeybindings): { primary?: number; secondary?: number[]; } {
if (OS === OperatingSystem.Windows) {
if (kb && kb.win) {
return kb.win;
}
} else if (OS === OperatingSystem.Macintosh) {
if (kb && kb.mac) {
return kb.mac;
}
} else {
if (kb && kb.linux) {
return kb.linux;
}
}
return kb;
}
/**
* Take current platform into account and reduce to primary & secondary.
*/
private static bindToCurrentPlatform2(kb: IKeybindingRule2): { primary?: Keybinding; } {
if (OS === OperatingSystem.Windows) {
if (kb && kb.win) {
return kb.win;
}
} else if (OS === OperatingSystem.Macintosh) {
if (kb && kb.mac) {
return kb.mac;
}
} else {
if (kb && kb.linux) {
return kb.linux;
}
}
return kb;
}
public registerKeybindingRule(rule: IKeybindingRule): void {
let actualKb = KeybindingsRegistryImpl.bindToCurrentPlatform(rule);
if (actualKb && actualKb.primary) {
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));
}
}
public registerKeybindingRule2(rule: IKeybindingRule2): void {
let actualKb = KeybindingsRegistryImpl.bindToCurrentPlatform2(rule);
if (actualKb && actualKb.primary) {
this.registerDefaultKeybinding(actualKb.primary, rule.id, rule.weight, 0, rule.when);
}
}
public registerCommandAndKeybindingRule(desc: ICommandAndKeybindingRule): void {
this.registerKeybindingRule(desc);
CommandsRegistry.registerCommand(desc.id, desc);
}
private static _mightProduceChar(keyCode: KeyCode): boolean {
if (keyCode >= KeyCode.KEY_0 && keyCode <= KeyCode.KEY_9) {
return true;
}
if (keyCode >= KeyCode.KEY_A && keyCode <= KeyCode.KEY_Z) {
return true;
}
return (
keyCode === KeyCode.US_SEMICOLON
|| keyCode === KeyCode.US_EQUAL
|| keyCode === KeyCode.US_COMMA
|| keyCode === KeyCode.US_MINUS
|| keyCode === KeyCode.US_DOT
|| keyCode === KeyCode.US_SLASH
|| keyCode === KeyCode.US_BACKTICK
|| keyCode === KeyCode.ABNT_C1
|| keyCode === KeyCode.ABNT_C2
|| keyCode === KeyCode.US_OPEN_SQUARE_BRACKET
|| keyCode === KeyCode.US_BACKSLASH
|| keyCode === KeyCode.US_CLOSE_SQUARE_BRACKET
|| keyCode === KeyCode.US_QUOTE
|| keyCode === KeyCode.OEM_8
|| keyCode === KeyCode.OEM_102
);
}
private _assertNoCtrlAlt(keybinding: SimpleKeybinding, commandId: string): void {
if (keybinding.ctrlKey && keybinding.altKey && !keybinding.metaKey) {
if (KeybindingsRegistryImpl._mightProduceChar(keybinding.keyCode)) {
console.warn('Ctrl+Alt+ keybindings should not be used by default under Windows. Offender: ', keybinding, ' for ', commandId);
}
}
}
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);
} else {
this._assertNoCtrlAlt(keybinding, commandId);
}
}
this._keybindings.push({
keybinding: keybinding,
command: commandId,
commandArgs: null,
when: when,
weight1: weight1,
weight2: weight2
});
}
public getDefaultKeybindings(): IKeybindingItem[] {
let result = this._keybindings.slice(0);
result.sort(sorter);
return result;
}
}
export const KeybindingsRegistry: IKeybindingsRegistry = new KeybindingsRegistryImpl();
// Define extension point ids
export const Extensions = {
EditorModes: 'platform.keybindingsRegistry'
};
Registry.add(Extensions.EditorModes, KeybindingsRegistry);
function sorter(a: IKeybindingItem, b: IKeybindingItem): number {
if (a.weight1 !== b.weight1) {
return a.weight1 - b.weight1;
}
if (a.command < b.command) {
return -1;
}
if (a.command > b.command) {
return 1;
}
return a.weight2 - b.weight2;
}

View File

@@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { CharCode } from 'vs/base/common/charCode';
export class ResolvedKeybindingItem {
_resolvedKeybindingItemBrand: void;
public readonly resolvedKeybinding: ResolvedKeybinding;
public readonly keypressFirstPart: string;
public readonly keypressChordPart: string;
public readonly bubble: boolean;
public readonly command: string;
public readonly commandArgs: any;
public readonly when: ContextKeyExpr;
public readonly isDefault: boolean;
constructor(resolvedKeybinding: ResolvedKeybinding, command: string, commandArgs: any, when: ContextKeyExpr, isDefault: boolean) {
this.resolvedKeybinding = resolvedKeybinding;
if (resolvedKeybinding) {
let [keypressFirstPart, keypressChordPart] = resolvedKeybinding.getDispatchParts();
this.keypressFirstPart = keypressFirstPart;
this.keypressChordPart = keypressChordPart;
} else {
this.keypressFirstPart = null;
this.keypressChordPart = null;
}
this.bubble = (command ? command.charCodeAt(0) === CharCode.Caret : false);
this.command = this.bubble ? command.substr(1) : command;
this.commandArgs = commandArgs;
this.when = when;
this.isDefault = isDefault;
}
}

View File

@@ -0,0 +1,198 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ResolvedKeybinding, ResolvedKeybindingPart, KeyCode, KeyCodeUtils, Keybinding, KeybindingType, SimpleKeybinding } from 'vs/base/common/keyCodes';
import { UILabelProvider, AriaLabelProvider, ElectronAcceleratorLabelProvider, UserSettingsLabelProvider } from 'vs/base/common/keybindingLabels';
import { OperatingSystem } from 'vs/base/common/platform';
/**
* Do not instantiate. Use KeybindingService to get a ResolvedKeybinding seeded with information about the current kb layout.
*/
export class USLayoutResolvedKeybinding extends ResolvedKeybinding {
private readonly _os: OperatingSystem;
private readonly _firstPart: SimpleKeybinding;
private readonly _chordPart: SimpleKeybinding;
constructor(actual: Keybinding, OS: OperatingSystem) {
super();
this._os = OS;
if (actual === null) {
this._firstPart = null;
this._chordPart = null;
} else if (actual.type === KeybindingType.Chord) {
this._firstPart = actual.firstPart;
this._chordPart = actual.chordPart;
} else {
this._firstPart = actual;
this._chordPart = null;
}
}
private _keyCodeToUILabel(keyCode: KeyCode): string {
if (this._os === OperatingSystem.Macintosh) {
switch (keyCode) {
case KeyCode.LeftArrow:
return '←';
case KeyCode.UpArrow:
return '↑';
case KeyCode.RightArrow:
return '→';
case KeyCode.DownArrow:
return '↓';
}
}
return KeyCodeUtils.toString(keyCode);
}
private _getUILabelForKeybinding(keybinding: SimpleKeybinding): string {
if (!keybinding) {
return null;
}
if (keybinding.isDuplicateModifierCase()) {
return '';
}
return this._keyCodeToUILabel(keybinding.keyCode);
}
public getLabel(): string {
let firstPart = this._getUILabelForKeybinding(this._firstPart);
let chordPart = this._getUILabelForKeybinding(this._chordPart);
return UILabelProvider.toLabel(this._firstPart, firstPart, this._chordPart, chordPart, this._os);
}
private _getAriaLabelForKeybinding(keybinding: SimpleKeybinding): string {
if (!keybinding) {
return null;
}
if (keybinding.isDuplicateModifierCase()) {
return '';
}
return KeyCodeUtils.toString(keybinding.keyCode);
}
public getAriaLabel(): string {
let firstPart = this._getAriaLabelForKeybinding(this._firstPart);
let chordPart = this._getAriaLabelForKeybinding(this._chordPart);
return AriaLabelProvider.toLabel(this._firstPart, firstPart, this._chordPart, chordPart, this._os);
}
private _keyCodeToElectronAccelerator(keyCode: KeyCode): string {
if (keyCode >= KeyCode.NUMPAD_0 && keyCode <= KeyCode.NUMPAD_DIVIDE) {
// Electron cannot handle numpad keys
return null;
}
switch (keyCode) {
case KeyCode.UpArrow:
return 'Up';
case KeyCode.DownArrow:
return 'Down';
case KeyCode.LeftArrow:
return 'Left';
case KeyCode.RightArrow:
return 'Right';
}
return KeyCodeUtils.toString(keyCode);
}
private _getElectronAcceleratorLabelForKeybinding(keybinding: SimpleKeybinding): string {
if (!keybinding) {
return null;
}
if (keybinding.isDuplicateModifierCase()) {
return null;
}
return this._keyCodeToElectronAccelerator(keybinding.keyCode);
}
public getElectronAccelerator(): string {
if (this._chordPart !== null) {
// Electron cannot handle chords
return null;
}
let firstPart = this._getElectronAcceleratorLabelForKeybinding(this._firstPart);
return ElectronAcceleratorLabelProvider.toLabel(this._firstPart, firstPart, null, null, this._os);
}
private _getUserSettingsLabelForKeybinding(keybinding: SimpleKeybinding): string {
if (!keybinding) {
return null;
}
if (keybinding.isDuplicateModifierCase()) {
return '';
}
return KeyCodeUtils.toUserSettingsUS(keybinding.keyCode);
}
public getUserSettingsLabel(): string {
let firstPart = this._getUserSettingsLabelForKeybinding(this._firstPart);
let chordPart = this._getUserSettingsLabelForKeybinding(this._chordPart);
let result = UserSettingsLabelProvider.toLabel(this._firstPart, firstPart, this._chordPart, chordPart, this._os);
return (result ? result.toLowerCase() : result);
}
public isWYSIWYG(): boolean {
return true;
}
public isChord(): boolean {
return (this._chordPart ? true : false);
}
public getParts(): [ResolvedKeybindingPart, ResolvedKeybindingPart] {
return [
this._toResolvedKeybindingPart(this._firstPart),
this._toResolvedKeybindingPart(this._chordPart)
];
}
private _toResolvedKeybindingPart(keybinding: SimpleKeybinding): ResolvedKeybindingPart {
if (!keybinding) {
return null;
}
return new ResolvedKeybindingPart(
keybinding.ctrlKey,
keybinding.shiftKey,
keybinding.altKey,
keybinding.metaKey,
this._getUILabelForKeybinding(keybinding),
this._getAriaLabelForKeybinding(keybinding)
);
}
public getDispatchParts(): [string, string] {
let firstPart = this._firstPart ? USLayoutResolvedKeybinding.getDispatchStr(this._firstPart) : null;
let chordPart = this._chordPart ? USLayoutResolvedKeybinding.getDispatchStr(this._chordPart) : null;
return [firstPart, chordPart];
}
public static getDispatchStr(keybinding: SimpleKeybinding): string {
if (keybinding.isModifierKey()) {
return null;
}
let result = '';
if (keybinding.ctrlKey) {
result += 'ctrl+';
}
if (keybinding.shiftKey) {
result += 'shift+';
}
if (keybinding.altKey) {
result += 'alt+';
}
if (keybinding.metaKey) {
result += 'meta+';
}
result += KeyCodeUtils.toString(keybinding.keyCode);
return result;
}
}

View File

@@ -0,0 +1,421 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import { ResolvedKeybinding, KeyCode, KeyMod, KeyChord, Keybinding, createKeybinding, createSimpleKeybinding, SimpleKeybinding } from 'vs/base/common/keyCodes';
import { AbstractKeybindingService } from 'vs/platform/keybinding/common/abstractKeybindingService';
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding';
import { IDisposable } from 'vs/base/common/lifecycle';
import Severity from 'vs/base/common/severity';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver';
import { IContext, ContextKeyExpr, IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey';
import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
import { IMessageService } from 'vs/platform/message/common/message';
import { TPromise } from 'vs/base/common/winjs.base';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
import { OS } from 'vs/base/common/platform';
import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding';
function createContext(ctx: any) {
return {
getValue: (key: string) => {
return ctx[key];
}
};
}
suite('AbstractKeybindingService', () => {
class TestKeybindingService extends AbstractKeybindingService {
private _resolver: KeybindingResolver;
constructor(
resolver: KeybindingResolver,
contextKeyService: IContextKeyService,
commandService: ICommandService,
messageService: IMessageService,
statusService?: IStatusbarService
) {
super(contextKeyService, commandService, messageService, statusService);
this._resolver = resolver;
}
protected _getResolver(): KeybindingResolver {
return this._resolver;
}
public resolveKeybinding(kb: Keybinding): ResolvedKeybinding[] {
return [new USLayoutResolvedKeybinding(kb, OS)];
}
public resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding {
let keybinding = new SimpleKeybinding(
keyboardEvent.ctrlKey,
keyboardEvent.shiftKey,
keyboardEvent.altKey,
keyboardEvent.metaKey,
keyboardEvent.keyCode
);
return this.resolveKeybinding(keybinding)[0];
}
public resolveUserBinding(userBinding: string): ResolvedKeybinding[] {
return [];
}
public testDispatch(kb: number): boolean {
const keybinding = createSimpleKeybinding(kb, OS);
return this._dispatch({
ctrlKey: keybinding.ctrlKey,
shiftKey: keybinding.shiftKey,
altKey: keybinding.altKey,
metaKey: keybinding.metaKey,
keyCode: keybinding.keyCode,
code: null
}, null);
}
}
let createTestKeybindingService: (items: ResolvedKeybindingItem[], contextValue?: any) => TestKeybindingService = null;
let currentContextValue: IContext = null;
let executeCommandCalls: { commandId: string; args: any[]; }[] = null;
let showMessageCalls: { sev: Severity, message: any; }[] = null;
let statusMessageCalls: string[] = null;
let statusMessageCallsDisposed: string[] = null;
setup(() => {
executeCommandCalls = [];
showMessageCalls = [];
statusMessageCalls = [];
statusMessageCallsDisposed = [];
createTestKeybindingService = (items: ResolvedKeybindingItem[]): TestKeybindingService => {
let contextKeyService: IContextKeyService = {
_serviceBrand: undefined,
dispose: undefined,
onDidChangeContext: undefined,
createKey: undefined,
contextMatchesRules: undefined,
getContextKeyValue: undefined,
createScoped: undefined,
getContext: (target: IContextKeyServiceTarget): any => {
return currentContextValue;
}
};
let commandService: ICommandService = {
_serviceBrand: undefined,
onWillExecuteCommand: () => ({ dispose: () => { } }),
executeCommand: (commandId: string, ...args: any[]): TPromise<any> => {
executeCommandCalls.push({
commandId: commandId,
args: args
});
return TPromise.as(void 0);
}
};
let messageService: IMessageService = {
_serviceBrand: undefined,
hideAll: undefined,
confirm: undefined,
show: (sev: Severity, message: any): () => void => {
showMessageCalls.push({
sev: sev,
message: message
});
return null;
}
};
let statusbarService: IStatusbarService = {
_serviceBrand: undefined,
addEntry: undefined,
setStatusMessage: (message: string, autoDisposeAfter?: number, delayBy?: number): IDisposable => {
statusMessageCalls.push(message);
return {
dispose: () => {
statusMessageCallsDisposed.push(message);
}
};
}
};
let resolver = new KeybindingResolver(items, []);
return new TestKeybindingService(resolver, contextKeyService, commandService, messageService, statusbarService);
};
});
teardown(() => {
currentContextValue = null;
executeCommandCalls = null;
showMessageCalls = null;
createTestKeybindingService = null;
statusMessageCalls = null;
statusMessageCallsDisposed = null;
});
function kbItem(keybinding: number, command: string, when: ContextKeyExpr = null): ResolvedKeybindingItem {
const resolvedKeybinding = (keybinding !== 0 ? new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS), OS) : null);
return new ResolvedKeybindingItem(
resolvedKeybinding,
command,
null,
when,
true
);
}
function toUsLabel(keybinding: number): string {
const usResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS), OS);
return usResolvedKeybinding.getLabel();
}
test('issue #16498: chord mode is quit for invalid chords', () => {
let kbService = createTestKeybindingService([
kbItem(KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_X), 'chordCommand'),
kbItem(KeyCode.Backspace, 'simpleCommand'),
]);
// send Ctrl/Cmd + K
let shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KEY_K);
assert.equal(shouldPreventDefault, true);
assert.deepEqual(executeCommandCalls, []);
assert.deepEqual(showMessageCalls, []);
assert.deepEqual(statusMessageCalls, [
`(${toUsLabel(KeyMod.CtrlCmd | KeyCode.KEY_K)}) was pressed. Waiting for second key of chord...`
]);
assert.deepEqual(statusMessageCallsDisposed, []);
executeCommandCalls = [];
showMessageCalls = [];
statusMessageCalls = [];
statusMessageCallsDisposed = [];
// send backspace
shouldPreventDefault = kbService.testDispatch(KeyCode.Backspace);
assert.equal(shouldPreventDefault, true);
assert.deepEqual(executeCommandCalls, []);
assert.deepEqual(showMessageCalls, []);
assert.deepEqual(statusMessageCalls, [
`The key combination (${toUsLabel(KeyMod.CtrlCmd | KeyCode.KEY_K)}, ${toUsLabel(KeyCode.Backspace)}) is not a command.`
]);
assert.deepEqual(statusMessageCallsDisposed, [
`(${toUsLabel(KeyMod.CtrlCmd | KeyCode.KEY_K)}) was pressed. Waiting for second key of chord...`
]);
executeCommandCalls = [];
showMessageCalls = [];
statusMessageCalls = [];
statusMessageCallsDisposed = [];
// send backspace
shouldPreventDefault = kbService.testDispatch(KeyCode.Backspace);
assert.equal(shouldPreventDefault, true);
assert.deepEqual(executeCommandCalls, [{
commandId: 'simpleCommand',
args: [{}]
}]);
assert.deepEqual(showMessageCalls, []);
assert.deepEqual(statusMessageCalls, []);
assert.deepEqual(statusMessageCallsDisposed, []);
executeCommandCalls = [];
showMessageCalls = [];
statusMessageCalls = [];
statusMessageCallsDisposed = [];
kbService.dispose();
});
test('issue #16833: Keybinding service should not testDispatch on modifier keys', () => {
let kbService = createTestKeybindingService([
kbItem(KeyCode.Ctrl, 'nope'),
kbItem(KeyCode.Meta, 'nope'),
kbItem(KeyCode.Alt, 'nope'),
kbItem(KeyCode.Shift, 'nope'),
kbItem(KeyMod.CtrlCmd, 'nope'),
kbItem(KeyMod.WinCtrl, 'nope'),
kbItem(KeyMod.Alt, 'nope'),
kbItem(KeyMod.Shift, 'nope'),
]);
function assertIsIgnored(keybinding: number): void {
let shouldPreventDefault = kbService.testDispatch(keybinding);
assert.equal(shouldPreventDefault, false);
assert.deepEqual(executeCommandCalls, []);
assert.deepEqual(showMessageCalls, []);
assert.deepEqual(statusMessageCalls, []);
assert.deepEqual(statusMessageCallsDisposed, []);
executeCommandCalls = [];
showMessageCalls = [];
statusMessageCalls = [];
statusMessageCallsDisposed = [];
}
assertIsIgnored(KeyCode.Ctrl);
assertIsIgnored(KeyCode.Meta);
assertIsIgnored(KeyCode.Alt);
assertIsIgnored(KeyCode.Shift);
assertIsIgnored(KeyMod.CtrlCmd);
assertIsIgnored(KeyMod.WinCtrl);
assertIsIgnored(KeyMod.Alt);
assertIsIgnored(KeyMod.Shift);
kbService.dispose();
});
test('can trigger command that is sharing keybinding with chord', () => {
let kbService = createTestKeybindingService([
kbItem(KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_X), 'chordCommand'),
kbItem(KeyMod.CtrlCmd | KeyCode.KEY_K, 'simpleCommand', ContextKeyExpr.has('key1')),
]);
// send Ctrl/Cmd + K
currentContextValue = createContext({
key1: true
});
let shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KEY_K);
assert.equal(shouldPreventDefault, true);
assert.deepEqual(executeCommandCalls, [{
commandId: 'simpleCommand',
args: [{}]
}]);
assert.deepEqual(showMessageCalls, []);
assert.deepEqual(statusMessageCalls, []);
assert.deepEqual(statusMessageCallsDisposed, []);
executeCommandCalls = [];
showMessageCalls = [];
statusMessageCalls = [];
statusMessageCallsDisposed = [];
// send Ctrl/Cmd + K
currentContextValue = createContext({});
shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KEY_K);
assert.equal(shouldPreventDefault, true);
assert.deepEqual(executeCommandCalls, []);
assert.deepEqual(showMessageCalls, []);
assert.deepEqual(statusMessageCalls, [
`(${toUsLabel(KeyMod.CtrlCmd | KeyCode.KEY_K)}) was pressed. Waiting for second key of chord...`
]);
assert.deepEqual(statusMessageCallsDisposed, []);
executeCommandCalls = [];
showMessageCalls = [];
statusMessageCalls = [];
statusMessageCallsDisposed = [];
// send Ctrl/Cmd + X
currentContextValue = createContext({});
shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KEY_X);
assert.equal(shouldPreventDefault, true);
assert.deepEqual(executeCommandCalls, [{
commandId: 'chordCommand',
args: [{}]
}]);
assert.deepEqual(showMessageCalls, []);
assert.deepEqual(statusMessageCalls, []);
assert.deepEqual(statusMessageCallsDisposed, [
`(${toUsLabel(KeyMod.CtrlCmd | KeyCode.KEY_K)}) was pressed. Waiting for second key of chord...`
]);
executeCommandCalls = [];
showMessageCalls = [];
statusMessageCalls = [];
statusMessageCallsDisposed = [];
kbService.dispose();
});
test('cannot trigger chord if command is overwriting', () => {
let kbService = createTestKeybindingService([
kbItem(KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_X), 'chordCommand', ContextKeyExpr.has('key1')),
kbItem(KeyMod.CtrlCmd | KeyCode.KEY_K, 'simpleCommand'),
]);
// send Ctrl/Cmd + K
currentContextValue = createContext({});
let shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KEY_K);
assert.equal(shouldPreventDefault, true);
assert.deepEqual(executeCommandCalls, [{
commandId: 'simpleCommand',
args: [{}]
}]);
assert.deepEqual(showMessageCalls, []);
assert.deepEqual(statusMessageCalls, []);
assert.deepEqual(statusMessageCallsDisposed, []);
executeCommandCalls = [];
showMessageCalls = [];
statusMessageCalls = [];
statusMessageCallsDisposed = [];
// send Ctrl/Cmd + K
currentContextValue = createContext({
key1: true
});
shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KEY_K);
assert.equal(shouldPreventDefault, true);
assert.deepEqual(executeCommandCalls, [{
commandId: 'simpleCommand',
args: [{}]
}]);
assert.deepEqual(showMessageCalls, []);
assert.deepEqual(statusMessageCalls, []);
assert.deepEqual(statusMessageCallsDisposed, []);
executeCommandCalls = [];
showMessageCalls = [];
statusMessageCalls = [];
statusMessageCallsDisposed = [];
// send Ctrl/Cmd + X
currentContextValue = createContext({
key1: true
});
shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KEY_X);
assert.equal(shouldPreventDefault, false);
assert.deepEqual(executeCommandCalls, []);
assert.deepEqual(showMessageCalls, []);
assert.deepEqual(statusMessageCalls, []);
assert.deepEqual(statusMessageCallsDisposed, []);
executeCommandCalls = [];
showMessageCalls = [];
statusMessageCalls = [];
statusMessageCallsDisposed = [];
kbService.dispose();
});
test('can have spying command', () => {
let kbService = createTestKeybindingService([
kbItem(KeyMod.CtrlCmd | KeyCode.KEY_K, '^simpleCommand'),
]);
// send Ctrl/Cmd + K
currentContextValue = createContext({});
let shouldPreventDefault = kbService.testDispatch(KeyMod.CtrlCmd | KeyCode.KEY_K);
assert.equal(shouldPreventDefault, false);
assert.deepEqual(executeCommandCalls, [{
commandId: 'simpleCommand',
args: [{}]
}]);
assert.deepEqual(showMessageCalls, []);
assert.deepEqual(statusMessageCalls, []);
assert.deepEqual(statusMessageCallsDisposed, []);
executeCommandCalls = [];
showMessageCalls = [];
statusMessageCalls = [];
statusMessageCallsDisposed = [];
kbService.dispose();
});
});

View File

@@ -0,0 +1,172 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import { KeyCode, KeyMod, KeyChord, createKeybinding } from 'vs/base/common/keyCodes';
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding';
import { OperatingSystem } from 'vs/base/common/platform';
suite('KeybindingLabels', () => {
function assertUSLabel(OS: OperatingSystem, keybinding: number, expected: string): void {
const usResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS), OS);
assert.equal(usResolvedKeybinding.getLabel(), expected);
}
test('Windows US label', () => {
// no modifier
assertUSLabel(OperatingSystem.Windows, KeyCode.KEY_A, 'A');
// one modifier
assertUSLabel(OperatingSystem.Windows, KeyMod.CtrlCmd | KeyCode.KEY_A, 'Ctrl+A');
assertUSLabel(OperatingSystem.Windows, KeyMod.Shift | KeyCode.KEY_A, 'Shift+A');
assertUSLabel(OperatingSystem.Windows, KeyMod.Alt | KeyCode.KEY_A, 'Alt+A');
assertUSLabel(OperatingSystem.Windows, KeyMod.WinCtrl | KeyCode.KEY_A, 'Windows+A');
// two modifiers
assertUSLabel(OperatingSystem.Windows, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_A, 'Ctrl+Shift+A');
assertUSLabel(OperatingSystem.Windows, KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_A, 'Ctrl+Alt+A');
assertUSLabel(OperatingSystem.Windows, KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Windows+A');
assertUSLabel(OperatingSystem.Windows, KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_A, 'Shift+Alt+A');
assertUSLabel(OperatingSystem.Windows, KeyMod.Shift | KeyMod.WinCtrl | KeyCode.KEY_A, 'Shift+Windows+A');
assertUSLabel(OperatingSystem.Windows, KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Alt+Windows+A');
// three modifiers
assertUSLabel(OperatingSystem.Windows, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_A, 'Ctrl+Shift+Alt+A');
assertUSLabel(OperatingSystem.Windows, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Shift+Windows+A');
assertUSLabel(OperatingSystem.Windows, KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Alt+Windows+A');
assertUSLabel(OperatingSystem.Windows, KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Shift+Alt+Windows+A');
// four modifiers
assertUSLabel(OperatingSystem.Windows, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Shift+Alt+Windows+A');
// chord
assertUSLabel(OperatingSystem.Windows, KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_A, KeyMod.CtrlCmd | KeyCode.KEY_B), 'Ctrl+A Ctrl+B');
});
test('Linux US label', () => {
// no modifier
assertUSLabel(OperatingSystem.Linux, KeyCode.KEY_A, 'A');
// one modifier
assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyCode.KEY_A, 'Ctrl+A');
assertUSLabel(OperatingSystem.Linux, KeyMod.Shift | KeyCode.KEY_A, 'Shift+A');
assertUSLabel(OperatingSystem.Linux, KeyMod.Alt | KeyCode.KEY_A, 'Alt+A');
assertUSLabel(OperatingSystem.Linux, KeyMod.WinCtrl | KeyCode.KEY_A, 'Windows+A');
// two modifiers
assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_A, 'Ctrl+Shift+A');
assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_A, 'Ctrl+Alt+A');
assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Windows+A');
assertUSLabel(OperatingSystem.Linux, KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_A, 'Shift+Alt+A');
assertUSLabel(OperatingSystem.Linux, KeyMod.Shift | KeyMod.WinCtrl | KeyCode.KEY_A, 'Shift+Windows+A');
assertUSLabel(OperatingSystem.Linux, KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Alt+Windows+A');
// three modifiers
assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_A, 'Ctrl+Shift+Alt+A');
assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Shift+Windows+A');
assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Alt+Windows+A');
assertUSLabel(OperatingSystem.Linux, KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Shift+Alt+Windows+A');
// four modifiers
assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Shift+Alt+Windows+A');
// chord
assertUSLabel(OperatingSystem.Linux, KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_A, KeyMod.CtrlCmd | KeyCode.KEY_B), 'Ctrl+A Ctrl+B');
});
test('Mac US label', () => {
// no modifier
assertUSLabel(OperatingSystem.Macintosh, KeyCode.KEY_A, 'A');
// one modifier
assertUSLabel(OperatingSystem.Macintosh, KeyMod.CtrlCmd | KeyCode.KEY_A, '⌘A');
assertUSLabel(OperatingSystem.Macintosh, KeyMod.Shift | KeyCode.KEY_A, '⇧A');
assertUSLabel(OperatingSystem.Macintosh, KeyMod.Alt | KeyCode.KEY_A, '⌥A');
assertUSLabel(OperatingSystem.Macintosh, KeyMod.WinCtrl | KeyCode.KEY_A, '⌃A');
// two modifiers
assertUSLabel(OperatingSystem.Macintosh, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_A, '⇧⌘A');
assertUSLabel(OperatingSystem.Macintosh, KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_A, '⌥⌘A');
assertUSLabel(OperatingSystem.Macintosh, KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_A, '⌃⌘A');
assertUSLabel(OperatingSystem.Macintosh, KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_A, '⇧⌥A');
assertUSLabel(OperatingSystem.Macintosh, KeyMod.Shift | KeyMod.WinCtrl | KeyCode.KEY_A, '⌃⇧A');
assertUSLabel(OperatingSystem.Macintosh, KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, '⌃⌥A');
// three modifiers
assertUSLabel(OperatingSystem.Macintosh, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_A, '⇧⌥⌘A');
assertUSLabel(OperatingSystem.Macintosh, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.WinCtrl | KeyCode.KEY_A, '⌃⇧⌘A');
assertUSLabel(OperatingSystem.Macintosh, KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, '⌃⌥⌘A');
assertUSLabel(OperatingSystem.Macintosh, KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, '⌃⇧⌥A');
// four modifiers
assertUSLabel(OperatingSystem.Macintosh, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, '⌃⇧⌥⌘A');
// chord
assertUSLabel(OperatingSystem.Macintosh, KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_A, KeyMod.CtrlCmd | KeyCode.KEY_B), '⌘A ⌘B');
// special keys
assertUSLabel(OperatingSystem.Macintosh, KeyCode.LeftArrow, '←');
assertUSLabel(OperatingSystem.Macintosh, KeyCode.UpArrow, '↑');
assertUSLabel(OperatingSystem.Macintosh, KeyCode.RightArrow, '→');
assertUSLabel(OperatingSystem.Macintosh, KeyCode.DownArrow, '↓');
});
test('Aria label', () => {
function assertAriaLabel(OS: OperatingSystem, keybinding: number, expected: string): void {
const usResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS), OS);
assert.equal(usResolvedKeybinding.getAriaLabel(), expected);
}
assertAriaLabel(OperatingSystem.Windows, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Control+Shift+Alt+Windows+A');
assertAriaLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Control+Shift+Alt+Windows+A');
assertAriaLabel(OperatingSystem.Macintosh, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Control+Shift+Alt+Command+A');
});
test('Electron Accelerator label', () => {
function assertElectronAcceleratorLabel(OS: OperatingSystem, keybinding: number, expected: string): void {
const usResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS), OS);
assert.equal(usResolvedKeybinding.getElectronAccelerator(), expected);
}
assertElectronAcceleratorLabel(OperatingSystem.Windows, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Shift+Alt+Super+A');
assertElectronAcceleratorLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Shift+Alt+Super+A');
assertElectronAcceleratorLabel(OperatingSystem.Macintosh, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Shift+Alt+Cmd+A');
// electron cannot handle chords
assertElectronAcceleratorLabel(OperatingSystem.Windows, KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_A, KeyMod.CtrlCmd | KeyCode.KEY_B), null);
assertElectronAcceleratorLabel(OperatingSystem.Linux, KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_A, KeyMod.CtrlCmd | KeyCode.KEY_B), null);
assertElectronAcceleratorLabel(OperatingSystem.Macintosh, KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_A, KeyMod.CtrlCmd | KeyCode.KEY_B), null);
// electron cannot handle numpad keys
assertElectronAcceleratorLabel(OperatingSystem.Windows, KeyCode.NUMPAD_1, null);
assertElectronAcceleratorLabel(OperatingSystem.Linux, KeyCode.NUMPAD_1, null);
assertElectronAcceleratorLabel(OperatingSystem.Macintosh, KeyCode.NUMPAD_1, null);
// special
assertElectronAcceleratorLabel(OperatingSystem.Macintosh, KeyCode.LeftArrow, 'Left');
assertElectronAcceleratorLabel(OperatingSystem.Macintosh, KeyCode.UpArrow, 'Up');
assertElectronAcceleratorLabel(OperatingSystem.Macintosh, KeyCode.RightArrow, 'Right');
assertElectronAcceleratorLabel(OperatingSystem.Macintosh, KeyCode.DownArrow, 'Down');
});
test('User Settings label', () => {
function assertElectronAcceleratorLabel(OS: OperatingSystem, keybinding: number, expected: string): void {
const usResolvedKeybinding = new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS), OS);
assert.equal(usResolvedKeybinding.getUserSettingsLabel(), expected);
}
assertElectronAcceleratorLabel(OperatingSystem.Windows, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'ctrl+shift+alt+win+a');
assertElectronAcceleratorLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'ctrl+shift+alt+meta+a');
assertElectronAcceleratorLabel(OperatingSystem.Macintosh, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'ctrl+shift+alt+cmd+a');
// electron cannot handle chords
assertElectronAcceleratorLabel(OperatingSystem.Windows, KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_A, KeyMod.CtrlCmd | KeyCode.KEY_B), 'ctrl+a ctrl+b');
assertElectronAcceleratorLabel(OperatingSystem.Linux, KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_A, KeyMod.CtrlCmd | KeyCode.KEY_B), 'ctrl+a ctrl+b');
assertElectronAcceleratorLabel(OperatingSystem.Macintosh, KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_A, KeyMod.CtrlCmd | KeyCode.KEY_B), 'cmd+a cmd+b');
});
});

View File

@@ -0,0 +1,398 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import { KeyCode, KeyMod, KeyChord, createKeybinding, KeybindingType, SimpleKeybinding } from 'vs/base/common/keyCodes';
import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver';
import { ContextKeyAndExpr, ContextKeyExpr, IContext } from 'vs/platform/contextkey/common/contextkey';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding';
import { OS } from 'vs/base/common/platform';
function createContext(ctx: any) {
return {
getValue: (key: string) => {
return ctx[key];
}
};
}
suite('KeybindingResolver', () => {
function kbItem(keybinding: number, command: string, commandArgs: any, when: ContextKeyExpr, isDefault: boolean): ResolvedKeybindingItem {
const resolvedKeybinding = (keybinding !== 0 ? new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS), OS) : null);
return new ResolvedKeybindingItem(
resolvedKeybinding,
command,
commandArgs,
when,
isDefault
);
}
function getDispatchStr(runtimeKb: SimpleKeybinding): string {
return USLayoutResolvedKeybinding.getDispatchStr(runtimeKb);
}
test('resolve key', function () {
let keybinding = KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z;
let runtimeKeybinding = createKeybinding(keybinding, OS);
let contextRules = ContextKeyExpr.equals('bar', 'baz');
let keybindingItem = kbItem(keybinding, 'yes', null, contextRules, true);
assert.equal(KeybindingResolver.contextMatchesRules(createContext({ bar: 'baz' }), contextRules), true);
assert.equal(KeybindingResolver.contextMatchesRules(createContext({ bar: 'bz' }), contextRules), false);
let resolver = new KeybindingResolver([keybindingItem], []);
assert.equal(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(<SimpleKeybinding>runtimeKeybinding)).commandId, 'yes');
assert.equal(resolver.resolve(createContext({ bar: 'bz' }), null, getDispatchStr(<SimpleKeybinding>runtimeKeybinding)), null);
});
test('resolve key with arguments', function () {
let commandArgs = { text: 'no' };
let keybinding = KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z;
let runtimeKeybinding = createKeybinding(keybinding, OS);
let contextRules = ContextKeyExpr.equals('bar', 'baz');
let keybindingItem = kbItem(keybinding, 'yes', commandArgs, contextRules, true);
let resolver = new KeybindingResolver([keybindingItem], []);
assert.equal(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(<SimpleKeybinding>runtimeKeybinding)).commandArgs, commandArgs);
});
test('KeybindingResolver.combine simple 1', function () {
let defaults = [
kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true)
];
let overrides = [
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), false)
];
let actual = KeybindingResolver.combine(defaults, overrides);
assert.deepEqual(actual, [
kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), false),
]);
});
test('KeybindingResolver.combine simple 2', function () {
let defaults = [
kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
];
let overrides = [
kbItem(KeyCode.KEY_C, 'yes3', null, ContextKeyExpr.equals('3', 'c'), false)
];
let actual = KeybindingResolver.combine(defaults, overrides);
assert.deepEqual(actual, [
kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true),
kbItem(KeyCode.KEY_C, 'yes3', null, ContextKeyExpr.equals('3', 'c'), false),
]);
});
test('KeybindingResolver.combine removal with not matching when', function () {
let defaults = [
kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
];
let overrides = [
kbItem(KeyCode.KEY_A, '-yes1', null, ContextKeyExpr.equals('1', 'b'), false)
];
let actual = KeybindingResolver.combine(defaults, overrides);
assert.deepEqual(actual, [
kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
]);
});
test('KeybindingResolver.combine removal with not matching keybinding', function () {
let defaults = [
kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
];
let overrides = [
kbItem(KeyCode.KEY_B, '-yes1', null, ContextKeyExpr.equals('1', 'a'), false)
];
let actual = KeybindingResolver.combine(defaults, overrides);
assert.deepEqual(actual, [
kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
]);
});
test('KeybindingResolver.combine removal with matching keybinding and when', function () {
let defaults = [
kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
];
let overrides = [
kbItem(KeyCode.KEY_A, '-yes1', null, ContextKeyExpr.equals('1', 'a'), false)
];
let actual = KeybindingResolver.combine(defaults, overrides);
assert.deepEqual(actual, [
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
]);
});
test('KeybindingResolver.combine removal with unspecified keybinding', function () {
let defaults = [
kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
];
let overrides = [
kbItem(0, '-yes1', null, ContextKeyExpr.equals('1', 'a'), false)
];
let actual = KeybindingResolver.combine(defaults, overrides);
assert.deepEqual(actual, [
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
]);
});
test('KeybindingResolver.combine removal with unspecified when', function () {
let defaults = [
kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
];
let overrides = [
kbItem(KeyCode.KEY_A, '-yes1', null, null, false)
];
let actual = KeybindingResolver.combine(defaults, overrides);
assert.deepEqual(actual, [
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
]);
});
test('KeybindingResolver.combine removal with unspecified when and unspecified keybinding', function () {
let defaults = [
kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
];
let overrides = [
kbItem(0, '-yes1', null, null, false)
];
let actual = KeybindingResolver.combine(defaults, overrides);
assert.deepEqual(actual, [
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
]);
});
test('issue #612#issuecomment-222109084 cannot remove keybindings for commands with ^', function () {
let defaults = [
kbItem(KeyCode.KEY_A, '^yes1', null, ContextKeyExpr.equals('1', 'a'), true),
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
];
let overrides = [
kbItem(KeyCode.KEY_A, '-yes1', null, null, false)
];
let actual = KeybindingResolver.combine(defaults, overrides);
assert.deepEqual(actual, [
kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
]);
});
test('contextIsEntirelyIncluded', function () {
let assertIsIncluded = (a: ContextKeyExpr[], b: ContextKeyExpr[]) => {
assert.equal(KeybindingResolver.whenIsEntirelyIncluded(false, new ContextKeyAndExpr(a), new ContextKeyAndExpr(b)), true);
};
let assertIsNotIncluded = (a: ContextKeyExpr[], b: ContextKeyExpr[]) => {
assert.equal(KeybindingResolver.whenIsEntirelyIncluded(false, new ContextKeyAndExpr(a), new ContextKeyAndExpr(b)), false);
};
let key1IsTrue = ContextKeyExpr.equals('key1', true);
let key1IsNotFalse = ContextKeyExpr.notEquals('key1', false);
let key1IsFalse = ContextKeyExpr.equals('key1', false);
let key1IsNotTrue = ContextKeyExpr.notEquals('key1', true);
let key2IsTrue = ContextKeyExpr.equals('key2', true);
let key2IsNotFalse = ContextKeyExpr.notEquals('key2', false);
let key3IsTrue = ContextKeyExpr.equals('key3', true);
let key4IsTrue = ContextKeyExpr.equals('key4', true);
assertIsIncluded([key1IsTrue], null);
assertIsIncluded([key1IsTrue], []);
assertIsIncluded([key1IsTrue], [key1IsTrue]);
assertIsIncluded([key1IsTrue], [key1IsNotFalse]);
assertIsIncluded([key1IsFalse], []);
assertIsIncluded([key1IsFalse], [key1IsFalse]);
assertIsIncluded([key1IsFalse], [key1IsNotTrue]);
assertIsIncluded([key2IsNotFalse], []);
assertIsIncluded([key2IsNotFalse], [key2IsNotFalse]);
assertIsIncluded([key2IsNotFalse], [key2IsTrue]);
assertIsIncluded([key1IsTrue, key2IsNotFalse], [key2IsTrue]);
assertIsIncluded([key1IsTrue, key2IsNotFalse], [key2IsNotFalse]);
assertIsIncluded([key1IsTrue, key2IsNotFalse], [key1IsTrue]);
assertIsIncluded([key1IsTrue, key2IsNotFalse], [key1IsNotFalse]);
assertIsIncluded([key1IsTrue, key2IsNotFalse], []);
assertIsNotIncluded([key1IsTrue], [key1IsFalse]);
assertIsNotIncluded([key1IsTrue], [key1IsNotTrue]);
assertIsNotIncluded([key1IsNotFalse], [key1IsFalse]);
assertIsNotIncluded([key1IsNotFalse], [key1IsNotTrue]);
assertIsNotIncluded([key1IsFalse], [key1IsTrue]);
assertIsNotIncluded([key1IsFalse], [key1IsNotFalse]);
assertIsNotIncluded([key1IsNotTrue], [key1IsTrue]);
assertIsNotIncluded([key1IsNotTrue], [key1IsNotFalse]);
assertIsNotIncluded([key1IsTrue, key2IsNotFalse], [key3IsTrue]);
assertIsNotIncluded([key1IsTrue, key2IsNotFalse], [key4IsTrue]);
assertIsNotIncluded([key1IsTrue], [key2IsTrue]);
assertIsNotIncluded([], [key2IsTrue]);
assertIsNotIncluded(null, [key2IsTrue]);
});
test('resolve command', function () {
function _kbItem(keybinding: number, command: string, when: ContextKeyExpr): ResolvedKeybindingItem {
return kbItem(keybinding, command, null, when, true);
}
let items = [
// This one will never match because its "when" is always overwritten by another one
_kbItem(
KeyCode.KEY_X,
'first',
ContextKeyExpr.and(
ContextKeyExpr.equals('key1', true),
ContextKeyExpr.notEquals('key2', false)
)
),
// This one always overwrites first
_kbItem(
KeyCode.KEY_X,
'second',
ContextKeyExpr.equals('key2', true)
),
// This one is a secondary mapping for `second`
_kbItem(
KeyCode.KEY_Z,
'second',
null
),
// This one sometimes overwrites first
_kbItem(
KeyCode.KEY_X,
'third',
ContextKeyExpr.equals('key3', true)
),
// This one is always overwritten by another one
_kbItem(
KeyMod.CtrlCmd | KeyCode.KEY_Y,
'fourth',
ContextKeyExpr.equals('key4', true)
),
// This one overwrites with a chord the previous one
_kbItem(
KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_Y, KeyCode.KEY_Z),
'fifth',
null
),
// This one has no keybinding
_kbItem(
0,
'sixth',
null
),
_kbItem(
KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_U),
'seventh',
null
),
_kbItem(
KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_K),
'seventh',
null
),
_kbItem(
KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_U),
'uncomment lines',
null
),
_kbItem(
KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_C),
'comment lines',
null
),
_kbItem(
KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_G, KeyMod.CtrlCmd | KeyCode.KEY_C),
'unreachablechord',
null
),
_kbItem(
KeyMod.CtrlCmd | KeyCode.KEY_G,
'eleven',
null
)
];
let resolver = new KeybindingResolver(items, []);
let testKey = (commandId: string, expectedKeys: number[]) => {
// Test lookup
let lookupResult = resolver.lookupKeybindings(commandId);
assert.equal(lookupResult.length, expectedKeys.length, 'Length mismatch @ commandId ' + commandId + '; GOT: ' + JSON.stringify(lookupResult, null, '\t'));
for (let i = 0, len = lookupResult.length; i < len; i++) {
const expected = new USLayoutResolvedKeybinding(createKeybinding(expectedKeys[i], OS), OS);
assert.equal(lookupResult[i].resolvedKeybinding.getUserSettingsLabel(), expected.getUserSettingsLabel(), 'value mismatch @ commandId ' + commandId);
}
};
let testResolve = (ctx: IContext, _expectedKey: number, commandId: string) => {
const expectedKey = createKeybinding(_expectedKey, OS);
if (expectedKey.type === KeybindingType.Chord) {
let firstPart = getDispatchStr(expectedKey.firstPart);
let chordPart = getDispatchStr(expectedKey.chordPart);
let result = resolver.resolve(ctx, null, firstPart);
assert.ok(result !== null, 'Enters chord for ' + commandId);
assert.equal(result.commandId, null, 'Enters chord for ' + commandId);
assert.equal(result.enterChord, true, 'Enters chord for ' + commandId);
result = resolver.resolve(ctx, firstPart, chordPart);
assert.ok(result !== null, 'Enters chord for ' + commandId);
assert.equal(result.commandId, commandId, 'Finds chorded command ' + commandId);
assert.equal(result.enterChord, false, 'Finds chorded command ' + commandId);
} else {
let result = resolver.resolve(ctx, null, getDispatchStr(expectedKey));
assert.ok(result !== null, 'Finds command ' + commandId);
assert.equal(result.commandId, commandId, 'Finds command ' + commandId);
assert.equal(result.enterChord, false, 'Finds command ' + commandId);
}
};
testKey('first', []);
testKey('second', [KeyCode.KEY_Z, KeyCode.KEY_X]);
testResolve(createContext({ key2: true }), KeyCode.KEY_X, 'second');
testResolve(createContext({}), KeyCode.KEY_Z, 'second');
testKey('third', [KeyCode.KEY_X]);
testResolve(createContext({ key3: true }), KeyCode.KEY_X, 'third');
testKey('fourth', []);
testKey('fifth', [KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_Y, KeyCode.KEY_Z)]);
testResolve(createContext({}), KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_Y, KeyCode.KEY_Z), 'fifth');
testKey('seventh', [KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_K)]);
testResolve(createContext({}), KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_K), 'seventh');
testKey('uncomment lines', [KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_U)]);
testResolve(createContext({}), KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_U), 'uncomment lines');
testKey('comment lines', [KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_C)]);
testResolve(createContext({}), KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_C), 'comment lines');
testKey('unreachablechord', []);
testKey('eleven', [KeyMod.CtrlCmd | KeyCode.KEY_G]);
testResolve(createContext({}), KeyMod.CtrlCmd | KeyCode.KEY_G, 'eleven');
testKey('sixth', []);
});
});

View File

@@ -0,0 +1,125 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ResolvedKeybinding, Keybinding, SimpleKeybinding } from 'vs/base/common/keyCodes';
import Event from 'vs/base/common/event';
import { IKeybindingService, IKeybindingEvent, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding';
import { IContextKey, IContextKeyService, IContextKeyServiceTarget, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IResolveResult } from 'vs/platform/keybinding/common/keybindingResolver';
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding';
import { OS } from 'vs/base/common/platform';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
class MockKeybindingContextKey<T> implements IContextKey<T> {
private _key: string;
private _defaultValue: T;
private _value: T;
constructor(key: string, defaultValue: T) {
this._key = key;
this._defaultValue = defaultValue;
this._value = this._defaultValue;
}
public set(value: T): void {
this._value = value;
}
public reset(): void {
this._value = this._defaultValue;
}
public get(): T {
return this._value;
}
}
export class MockContextKeyService implements IContextKeyService {
public _serviceBrand: any;
private _keys = new Map<string, IContextKey<any>>();
public dispose(): void {
//
}
public createKey<T>(key: string, defaultValue: T): IContextKey<T> {
let ret = new MockKeybindingContextKey(key, defaultValue);
this._keys.set(key, ret);
return ret;
}
public contextMatchesRules(rules: ContextKeyExpr): boolean {
return false;
}
public get onDidChangeContext(): Event<string[]> {
return Event.None;
}
public getContextKeyValue(key: string) {
if (this._keys.has(key)) {
return this._keys.get(key).get();
}
}
public getContext(domNode: HTMLElement): any {
return null;
}
public createScoped(domNode: HTMLElement): IContextKeyService {
return this;
}
}
export class MockKeybindingService implements IKeybindingService {
public _serviceBrand: any;
public get onDidUpdateKeybindings(): Event<IKeybindingEvent> {
return Event.None;
}
public getDefaultKeybindingsContent(): string {
return null;
}
public getDefaultKeybindings(): ResolvedKeybindingItem[] {
return [];
}
public getKeybindings(): ResolvedKeybindingItem[] {
return [];
}
public resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[] {
return [new USLayoutResolvedKeybinding(keybinding, OS)];
}
public resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding {
let keybinding = new SimpleKeybinding(
keyboardEvent.ctrlKey,
keyboardEvent.shiftKey,
keyboardEvent.altKey,
keyboardEvent.metaKey,
keyboardEvent.keyCode
);
return this.resolveKeybinding(keybinding)[0];
}
public resolveUserBinding(userBinding: string): ResolvedKeybinding[] {
return [];
}
public lookupKeybindings(commandId: string): ResolvedKeybinding[] {
return [];
}
public lookupKeybinding(commandId: string): ResolvedKeybinding {
return null;
}
public customKeybindingsCount(): number {
return 0;
}
public softDispatch(keybinding: IKeyboardEvent, target: IContextKeyServiceTarget): IResolveResult {
return null;
}
}