Files
vscode/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts
Benjamin Pasero 1de7a23ccc code clean up
2016-08-23 09:19:04 +02:00

295 lines
11 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import {IHTMLContentElement} from 'vs/base/common/htmlContent';
import {IJSONSchema} from 'vs/base/common/jsonSchema';
import {Keybinding} from 'vs/base/common/keyCodes';
import * as platform from 'vs/base/common/platform';
import {toDisposable} from 'vs/base/common/lifecycle';
import {IEventService} from 'vs/platform/event/common/event';
import {IExtensionMessageCollector, ExtensionsRegistry} from 'vs/platform/extensions/common/extensionsRegistry';
import {Extensions, IJSONContributionRegistry} from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import {KeybindingService} from 'vs/platform/keybinding/browser/keybindingServiceImpl';
import {IStatusbarService} from 'vs/platform/statusbar/common/statusbar';
import {IOSupport} from 'vs/platform/keybinding/common/keybindingResolver';
import {ICommandService} from 'vs/platform/commands/common/commands';
import {IKeybindingItem, IUserFriendlyKeybinding} from 'vs/platform/keybinding/common/keybinding';
import {IContextKeyService} from 'vs/platform/contextkey/common/contextkey';
import {IKeybindingRule, KeybindingsRegistry} from 'vs/platform/keybinding/common/keybindingsRegistry';
import {Registry} from 'vs/platform/platform';
import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
import {getNativeLabelProvider, getNativeAriaLabelProvider} from 'vs/workbench/services/keybinding/electron-browser/nativeKeymap';
import {IMessageService} from 'vs/platform/message/common/message';
import {ConfigWatcher} from 'vs/base/node/config';
import {IEnvironmentService} from 'vs/platform/environment/common/environment';
interface ContributedKeyBinding {
command: string;
key: string;
when?: string;
mac?: string;
linux?: string;
win?: string;
}
function isContributedKeyBindingsArray(thing: ContributedKeyBinding | ContributedKeyBinding[]): thing is ContributedKeyBinding[] {
return Array.isArray(thing);
}
function isValidContributedKeyBinding(keyBinding: ContributedKeyBinding, rejects: string[]): boolean {
if (!keyBinding) {
rejects.push(nls.localize('nonempty', "expected non-empty value."));
return false;
}
if (typeof keyBinding.command !== 'string') {
rejects.push(nls.localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'command'));
return false;
}
if (typeof keyBinding.key !== 'string') {
rejects.push(nls.localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'key'));
return false;
}
if (keyBinding.when && typeof keyBinding.when !== 'string') {
rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when'));
return false;
}
if (keyBinding.mac && typeof keyBinding.mac !== 'string') {
rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'mac'));
return false;
}
if (keyBinding.linux && typeof keyBinding.linux !== 'string') {
rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'linux'));
return false;
}
if (keyBinding.win && typeof keyBinding.win !== 'string') {
rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'win'));
return false;
}
return true;
}
let keybindingType: IJSONSchema = {
type: 'object',
default: { command: '', key: '' },
properties: {
command: {
description: nls.localize('vscode.extension.contributes.keybindings.command', 'Identifier of the command to run when keybinding is triggered.'),
type: 'string'
},
key: {
description: nls.localize('vscode.extension.contributes.keybindings.key', 'Key or key sequence (separate keys with plus-sign and sequences with space, e.g Ctrl+O and Ctrl+L L for a chord'),
type: 'string'
},
mac: {
description: nls.localize('vscode.extension.contributes.keybindings.mac', 'Mac specific key or key sequence.'),
type: 'string'
},
linux: {
description: nls.localize('vscode.extension.contributes.keybindings.linux', 'Linux specific key or key sequence.'),
type: 'string'
},
win: {
description: nls.localize('vscode.extension.contributes.keybindings.win', 'Windows specific key or key sequence.'),
type: 'string'
},
when: {
description: nls.localize('vscode.extension.contributes.keybindings.when', 'Condition when the key is active.'),
type: 'string'
}
}
};
let keybindingsExtPoint = ExtensionsRegistry.registerExtensionPoint<ContributedKeyBinding | ContributedKeyBinding[]>('keybindings', {
description: nls.localize('vscode.extension.contributes.keybindings', "Contributes keybindings."),
oneOf: [
keybindingType,
{
type: 'array',
items: keybindingType
}
]
});
export class WorkbenchKeybindingService extends KeybindingService {
private userKeybindings: ConfigWatcher<IUserFriendlyKeybinding[]>;
constructor(
domNode: HTMLElement,
@IContextKeyService contextKeyService: IContextKeyService,
@ICommandService commandService: ICommandService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IEventService private eventService: IEventService,
@ITelemetryService private telemetryService: ITelemetryService,
@IMessageService messageService: IMessageService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IStatusbarService statusBarService: IStatusbarService
) {
super(contextKeyService, commandService, messageService, statusBarService);
this.userKeybindings = new ConfigWatcher(environmentService.appKeybindingsPath, { defaultConfig: [] });
this.toDispose.push(toDisposable(() => this.userKeybindings.dispose()));
keybindingsExtPoint.setHandler((extensions) => {
let commandAdded = false;
for (let extension of extensions) {
commandAdded = this._handleKeybindingsExtensionPointUser(extension.description.isBuiltin, extension.value, extension.collector) || commandAdded;
}
if (commandAdded) {
this.updateResolver();
}
});
this.toDispose.push(this.userKeybindings.onDidUpdateConfiguration(() => this.updateResolver()));
this._beginListening(domNode);
}
public customKeybindingsCount(): number {
let userKeybindings = this.userKeybindings.getConfig();
return userKeybindings.length;
}
protected _getExtraKeybindings(isFirstTime: boolean): IKeybindingItem[] {
let extraUserKeybindings: IUserFriendlyKeybinding[] = this.userKeybindings.getConfig();
if (!isFirstTime) {
let cnt = extraUserKeybindings.length;
this.telemetryService.publicLog('customKeybindingsChanged', {
keyCount: cnt
});
}
return extraUserKeybindings.map((k, i) => IOSupport.readKeybindingItem(k, i));
}
public getLabelFor(keybinding: Keybinding): string {
return keybinding.toCustomLabel(getNativeLabelProvider());
}
public getHTMLLabelFor(keybinding: Keybinding): IHTMLContentElement[] {
return keybinding.toCustomHTMLLabel(getNativeLabelProvider());
}
public getAriaLabelFor(keybinding: Keybinding): string {
return keybinding.toCustomLabel(getNativeAriaLabelProvider());
}
public getElectronAcceleratorFor(keybinding: Keybinding): string {
if (platform.isWindows) {
// electron menus always do the correct rendering on Windows
return super.getElectronAcceleratorFor(keybinding);
}
let usLabel = keybinding._toUSLabel();
let label = this.getLabelFor(keybinding);
if (usLabel !== label) {
// electron menus are incorrect in rendering (linux) and in rendering and interpreting (mac)
// for non US standard keyboard layouts
return null;
}
return super.getElectronAcceleratorFor(keybinding);
}
private _handleKeybindingsExtensionPointUser(isBuiltin: boolean, keybindings: ContributedKeyBinding | ContributedKeyBinding[], collector: IExtensionMessageCollector): boolean {
if (isContributedKeyBindingsArray(keybindings)) {
let commandAdded = false;
for (let i = 0, len = keybindings.length; i < len; i++) {
commandAdded = this._handleKeybinding(isBuiltin, i + 1, keybindings[i], collector) || commandAdded;
}
return commandAdded;
} else {
return this._handleKeybinding(isBuiltin, 1, keybindings, collector);
}
}
private _handleKeybinding(isBuiltin: boolean, idx: number, keybindings: ContributedKeyBinding, collector: IExtensionMessageCollector): boolean {
let rejects: string[] = [];
let commandAdded = false;
if (isValidContributedKeyBinding(keybindings, rejects)) {
let rule = this._asCommandRule(isBuiltin, idx++, keybindings);
if (rule) {
KeybindingsRegistry.registerKeybindingRule(rule);
commandAdded = true;
}
}
if (rejects.length > 0) {
collector.error(nls.localize(
'invalid.keybindings',
"Invalid `contributes.{0}`: {1}",
keybindingsExtPoint.name,
rejects.join('\n')
));
}
return commandAdded;
}
private _asCommandRule(isBuiltin: boolean, idx: number, binding: ContributedKeyBinding): IKeybindingRule {
let {command, when, key, mac, linux, win} = binding;
let weight: number;
if (isBuiltin) {
weight = KeybindingsRegistry.WEIGHT.builtinExtension(idx);
} else {
weight = KeybindingsRegistry.WEIGHT.externalExtension(idx);
}
let desc = {
id: command,
when: IOSupport.readKeybindingWhen(when),
weight: weight,
primary: IOSupport.readKeybinding(key),
mac: mac && { primary: IOSupport.readKeybinding(mac) },
linux: linux && { primary: IOSupport.readKeybinding(linux) },
win: win && { primary: IOSupport.readKeybinding(win) }
};
if (!desc.primary && !desc.mac && !desc.linux && !desc.win) {
return;
}
return desc;
}
}
let schemaId = 'vscode://schemas/keybindings';
let schema: IJSONSchema = {
'id': schemaId,
'type': 'array',
'title': nls.localize('keybindings.json.title', "Keybindings configuration"),
'items': {
'required': ['key'],
'type': 'object',
'defaultSnippets': [{ 'body': { 'key': '{{_}}', 'command': '{{_}}', 'when': '{{_}}' } }],
'properties': {
'key': {
'type': 'string',
'description': nls.localize('keybindings.json.key', 'Key or key sequence (separated by space)'),
},
'command': {
'description': nls.localize('keybindings.json.command', 'Name of the command to execute'),
},
'when': {
'type': 'string',
'description': nls.localize('keybindings.json.when', 'Condition when the key is active.')
}
}
}
};
let schemaRegistry = <IJSONContributionRegistry>Registry.as(Extensions.JSONContribution);
schemaRegistry.registerSchema(schemaId, schema);