mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-03 01:25:38 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
168
src/vs/platform/keybinding/common/abstractKeybindingService.ts
Normal file
168
src/vs/platform/keybinding/common/abstractKeybindingService.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
81
src/vs/platform/keybinding/common/keybinding.ts
Normal file
81
src/vs/platform/keybinding/common/keybinding.ts
Normal 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;
|
||||
}
|
||||
|
||||
323
src/vs/platform/keybinding/common/keybindingResolver.ts
Normal file
323
src/vs/platform/keybinding/common/keybindingResolver.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
245
src/vs/platform/keybinding/common/keybindingsRegistry.ts
Normal file
245
src/vs/platform/keybinding/common/keybindingsRegistry.ts
Normal 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;
|
||||
}
|
||||
39
src/vs/platform/keybinding/common/resolvedKeybindingItem.ts
Normal file
39
src/vs/platform/keybinding/common/resolvedKeybindingItem.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
198
src/vs/platform/keybinding/common/usLayoutResolvedKeybinding.ts
Normal file
198
src/vs/platform/keybinding/common/usLayoutResolvedKeybinding.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user