keybindingService.ts 22.2 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

7
import * as nls from 'vs/nls';
J
Johannes Rieken 已提交
8
import { IJSONSchema } from 'vs/base/common/jsonSchema';
9
import { ResolvedKeybinding, Keybinding } from 'vs/base/common/keyCodes';
A
Alex Dima 已提交
10
import { OS, OperatingSystem } from 'vs/base/common/platform';
11
import { ExtensionMessageCollector, ExtensionsRegistry } from 'vs/platform/extensions/common/extensionsRegistry';
J
Johannes Rieken 已提交
12
import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
13
import { AbstractKeybindingService } from 'vs/platform/keybinding/common/abstractKeybindingService';
J
Johannes Rieken 已提交
14
import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
A
Alex Dima 已提交
15
import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver';
S
Sandeep Somavarapu 已提交
16
import { ICommandService } from 'vs/platform/commands/common/commands';
17
import { IKeybindingEvent, IUserFriendlyKeybinding, KeybindingSource, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding';
18
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
19
import { IKeybindingItem, KeybindingsRegistry, IKeybindingRule2 } from 'vs/platform/keybinding/common/keybindingsRegistry';
20
import { Registry } from 'vs/platform/registry/common/platform';
21 22
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { keybindingsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
J
Johannes Rieken 已提交
23 24 25
import { IMessageService } from 'vs/platform/message/common/message';
import { ConfigWatcher } from 'vs/base/node/config';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
26 27
import * as dom from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
28
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
29
import { KeybindingIO, OutputBuilder, IUserKeybindingItem } from 'vs/workbench/services/keybinding/common/keybindingIO';
30 31
import * as nativeKeymap from 'native-keymap';
import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper';
A
Alex Dima 已提交
32 33
import { WindowsKeyboardMapper, IWindowsKeyboardMapping, windowsKeyboardMappingEquals } from 'vs/workbench/services/keybinding/common/windowsKeyboardMapper';
import { IMacLinuxKeyboardMapping, MacLinuxKeyboardMapper, macLinuxKeyboardMappingEquals } from 'vs/workbench/services/keybinding/common/macLinuxKeyboardMapper';
34
import { MacLinuxFallbackKeyboardMapper } from 'vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper';
A
Alex Dima 已提交
35
import Event, { Emitter } from 'vs/base/common/event';
36 37
import { Extensions as ConfigExtensions, IConfigurationRegistry, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
B
Benjamin Pasero 已提交
38
import { onUnexpectedError } from 'vs/base/common/errors';
39

A
Alex Dima 已提交
40 41 42 43 44 45
export class KeyboardMapperFactory {
	public static INSTANCE = new KeyboardMapperFactory();

	private _layoutInfo: nativeKeymap.IKeyboardLayoutInfo;
	private _rawMapping: nativeKeymap.IKeyboardMapping;
	private _keyboardMapper: IKeyboardMapper;
A
Alex Dima 已提交
46
	private _initialized: boolean;
A
Alex Dima 已提交
47 48 49 50 51 52 53 54

	private _onDidChangeKeyboardMapper: Emitter<void> = new Emitter<void>();
	public onDidChangeKeyboardMapper: Event<void> = this._onDidChangeKeyboardMapper.event;

	private constructor() {
		this._layoutInfo = null;
		this._rawMapping = null;
		this._keyboardMapper = null;
A
Alex Dima 已提交
55
		this._initialized = false;
56
	}
A
Alex Dima 已提交
57

58
	public _onKeyboardLayoutChanged(): void {
A
Alex Dima 已提交
59
		if (this._initialized) {
60
			this._setKeyboardData(nativeKeymap.getCurrentKeyboardLayout(), nativeKeymap.getKeyMap());
A
Alex Dima 已提交
61 62 63
		}
	}

64
	public getKeyboardMapper(dispatchConfig: DispatchConfig): IKeyboardMapper {
A
Alex Dima 已提交
65
		if (!this._initialized) {
66
			this._setKeyboardData(nativeKeymap.getCurrentKeyboardLayout(), nativeKeymap.getKeyMap());
A
Alex Dima 已提交
67
		}
68 69 70 71
		if (dispatchConfig === DispatchConfig.KeyCode) {
			// Forcefully set to use keyCode
			return new MacLinuxFallbackKeyboardMapper(OS);
		}
A
Alex Dima 已提交
72 73 74
		return this._keyboardMapper;
	}

A
Alex Dima 已提交
75 76
	public getCurrentKeyboardLayout(): nativeKeymap.IKeyboardLayoutInfo {
		if (!this._initialized) {
77
			this._setKeyboardData(nativeKeymap.getCurrentKeyboardLayout(), nativeKeymap.getKeyMap());
A
Alex Dima 已提交
78 79 80 81
		}
		return this._layoutInfo;
	}

82
	private static _isUSStandard(_kbInfo: nativeKeymap.IKeyboardLayoutInfo): boolean {
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
		if (OS === OperatingSystem.Linux) {
			const kbInfo = <nativeKeymap.ILinuxKeyboardLayoutInfo>_kbInfo;
			return (kbInfo && kbInfo.layout === 'us');
		}

		if (OS === OperatingSystem.Macintosh) {
			const kbInfo = <nativeKeymap.IMacKeyboardLayoutInfo>_kbInfo;
			return (kbInfo && kbInfo.id === 'com.apple.keylayout.US');
		}

		if (OS === OperatingSystem.Windows) {
			const kbInfo = <nativeKeymap.IWindowsKeyboardLayoutInfo>_kbInfo;
			return (kbInfo && kbInfo.name === '00000409');
		}

		return false;
	}

A
Alex Dima 已提交
101 102
	public getRawKeyboardMapping(): nativeKeymap.IKeyboardMapping {
		if (!this._initialized) {
103
			this._setKeyboardData(nativeKeymap.getCurrentKeyboardLayout(), nativeKeymap.getKeyMap());
A
Alex Dima 已提交
104 105 106 107
		}
		return this._rawMapping;
	}

108
	private _setKeyboardData(layoutInfo: nativeKeymap.IKeyboardLayoutInfo, rawMapping: nativeKeymap.IKeyboardMapping): void {
A
Alex Dima 已提交
109 110
		this._layoutInfo = layoutInfo;

111
		if (this._initialized && KeyboardMapperFactory._equals(this._rawMapping, rawMapping)) {
A
Alex Dima 已提交
112 113 114 115
			// nothing to do...
			return;
		}

A
Alex Dima 已提交
116
		this._initialized = true;
A
Alex Dima 已提交
117
		this._rawMapping = rawMapping;
118
		this._keyboardMapper = KeyboardMapperFactory._createKeyboardMapper(this._layoutInfo, this._rawMapping);
A
Alex Dima 已提交
119 120 121
		this._onDidChangeKeyboardMapper.fire();
	}

122
	private static _createKeyboardMapper(layoutInfo: nativeKeymap.IKeyboardLayoutInfo, rawMapping: nativeKeymap.IKeyboardMapping): IKeyboardMapper {
123
		const isUSStandard = KeyboardMapperFactory._isUSStandard(layoutInfo);
A
Alex Dima 已提交
124
		if (OS === OperatingSystem.Windows) {
125
			return new WindowsKeyboardMapper(isUSStandard, <IWindowsKeyboardMapping>rawMapping);
A
Alex Dima 已提交
126 127 128 129
		}

		if (Object.keys(rawMapping).length === 0) {
			// Looks like reading the mappings failed (most likely Mac + Japanese/Chinese keyboard layouts)
130
			return new MacLinuxFallbackKeyboardMapper(OS);
A
Alex Dima 已提交
131 132
		}

133 134 135 136 137 138 139 140
		if (OS === OperatingSystem.Macintosh) {
			const kbInfo = <nativeKeymap.IMacKeyboardLayoutInfo>layoutInfo;
			if (kbInfo.id === 'com.apple.keylayout.DVORAK-QWERTYCMD') {
				// Use keyCode based dispatching for DVORAK - QWERTY ⌘
				return new MacLinuxFallbackKeyboardMapper(OS);
			}
		}

141
		return new MacLinuxKeyboardMapper(isUSStandard, <IMacLinuxKeyboardMapping>rawMapping, OS);
A
Alex Dima 已提交
142 143 144 145 146 147 148 149
	}

	private static _equals(a: nativeKeymap.IKeyboardMapping, b: nativeKeymap.IKeyboardMapping): boolean {
		if (OS === OperatingSystem.Windows) {
			return windowsKeyboardMappingEquals(<IWindowsKeyboardMapping>a, <IWindowsKeyboardMapping>b);
		}

		return macLinuxKeyboardMappingEquals(<IMacLinuxKeyboardMapping>a, <IMacLinuxKeyboardMapping>b);
150
	}
151
}
E
Erich Gamma 已提交
152 153 154 155 156 157 158 159 160 161

interface ContributedKeyBinding {
	command: string;
	key: string;
	when?: string;
	mac?: string;
	linux?: string;
	win?: string;
}

162
function isContributedKeyBindingsArray(thing: ContributedKeyBinding | ContributedKeyBinding[]): thing is ContributedKeyBinding[] {
E
Erich Gamma 已提交
163 164 165 166 167 168 169 170 171
	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') {
B
Benjamin Pasero 已提交
172
		rejects.push(nls.localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'command'));
E
Erich Gamma 已提交
173 174 175
		return false;
	}
	if (typeof keyBinding.key !== 'string') {
B
Benjamin Pasero 已提交
176
		rejects.push(nls.localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'key'));
E
Erich Gamma 已提交
177 178 179
		return false;
	}
	if (keyBinding.when && typeof keyBinding.when !== 'string') {
B
Benjamin Pasero 已提交
180
		rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when'));
E
Erich Gamma 已提交
181 182 183
		return false;
	}
	if (keyBinding.mac && typeof keyBinding.mac !== 'string') {
B
Benjamin Pasero 已提交
184
		rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'mac'));
E
Erich Gamma 已提交
185 186 187
		return false;
	}
	if (keyBinding.linux && typeof keyBinding.linux !== 'string') {
B
Benjamin Pasero 已提交
188
		rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'linux'));
E
Erich Gamma 已提交
189 190 191
		return false;
	}
	if (keyBinding.win && typeof keyBinding.win !== 'string') {
B
Benjamin Pasero 已提交
192
		rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'win'));
E
Erich Gamma 已提交
193 194 195 196 197
		return false;
	}
	return true;
}

198
let keybindingType: IJSONSchema = {
E
Erich Gamma 已提交
199 200 201 202 203 204 205 206
	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: {
A
Alex Dima 已提交
207
			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).'),
E
Erich Gamma 已提交
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
			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'
		}
	}
};

A
Alex Dima 已提交
229
let keybindingsExtPoint = ExtensionsRegistry.registerExtensionPoint<ContributedKeyBinding | ContributedKeyBinding[]>('keybindings', [], {
E
Erich Gamma 已提交
230 231 232 233 234 235 236 237 238 239
	description: nls.localize('vscode.extension.contributes.keybindings', "Contributes keybindings."),
	oneOf: [
		keybindingType,
		{
			type: 'array',
			items: keybindingType
		}
	]
});

240 241 242 243 244 245 246 247 248 249 250
export const enum DispatchConfig {
	Code,
	KeyCode
}

function getDispatchConfig(configurationService: IConfigurationService): DispatchConfig {
	const keyboard = configurationService.getConfiguration('keyboard');
	const r = (keyboard ? (<any>keyboard).dispatch : null);
	return (r === 'keyCode' ? DispatchConfig.KeyCode : DispatchConfig.Code);
}

251 252
export class WorkbenchKeybindingService extends AbstractKeybindingService {

253
	private _keyboardMapper: IKeyboardMapper;
254 255
	private _cachedResolver: KeybindingResolver;
	private _firstTimeComputingResolver: boolean;
256
	private userKeybindings: ConfigWatcher<IUserFriendlyKeybinding[]>;
E
Erich Gamma 已提交
257

258
	constructor(
B
Benjamin Pasero 已提交
259
		windowElement: Window,
260
		@IContextKeyService contextKeyService: IContextKeyService,
261
		@ICommandService commandService: ICommandService,
262
		@ITelemetryService telemetryService: ITelemetryService,
263
		@IMessageService messageService: IMessageService,
264
		@IEnvironmentService environmentService: IEnvironmentService,
265
		@IStatusbarService statusBarService: IStatusbarService,
266
		@IConfigurationService configurationService: IConfigurationService
267
	) {
268
		super(contextKeyService, commandService, telemetryService, messageService, statusBarService);
269

270
		let dispatchConfig = getDispatchConfig(configurationService);
271
		configurationService.onDidChangeConfiguration((e) => {
272 273 274 275 276 277 278 279 280 281 282
			let newDispatchConfig = getDispatchConfig(configurationService);
			if (dispatchConfig === newDispatchConfig) {
				return;
			}

			dispatchConfig = newDispatchConfig;
			this._keyboardMapper = KeyboardMapperFactory.INSTANCE.getKeyboardMapper(dispatchConfig);
			this.updateResolver({ source: KeybindingSource.Default });
		});

		this._keyboardMapper = KeyboardMapperFactory.INSTANCE.getKeyboardMapper(dispatchConfig);
A
Alex Dima 已提交
283
		KeyboardMapperFactory.INSTANCE.onDidChangeKeyboardMapper(() => {
284
			this._keyboardMapper = KeyboardMapperFactory.INSTANCE.getKeyboardMapper(dispatchConfig);
A
Alex Dima 已提交
285 286
			this.updateResolver({ source: KeybindingSource.Default });
		});
287

288 289 290
		this._cachedResolver = null;
		this._firstTimeComputingResolver = true;

291
		this.userKeybindings = new ConfigWatcher(environmentService.appKeybindingsPath, { defaultConfig: [], onError: error => onUnexpectedError(error) });
B
Benjamin Pasero 已提交
292
		this.toDispose.push(this.userKeybindings);
293

E
Erich Gamma 已提交
294 295 296 297 298 299 300 301
		keybindingsExtPoint.setHandler((extensions) => {
			let commandAdded = false;

			for (let extension of extensions) {
				commandAdded = this._handleKeybindingsExtensionPointUser(extension.description.isBuiltin, extension.value, extension.collector) || commandAdded;
			}

			if (commandAdded) {
C
Christof Marti 已提交
302
				this.updateResolver({ source: KeybindingSource.Default });
E
Erich Gamma 已提交
303 304
			}
		});
305

C
Christof Marti 已提交
306 307 308 309
		this.toDispose.push(this.userKeybindings.onDidUpdateConfiguration(event => this.updateResolver({
			source: KeybindingSource.User,
			keybindings: event.config
		})));
310

B
Benjamin Pasero 已提交
311
		this.toDispose.push(dom.addDisposableListener(windowElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
312
			let keyEvent = new StandardKeyboardEvent(e);
313
			let shouldPreventDefault = this._dispatch(keyEvent, keyEvent.target);
314 315 316 317 318
			if (shouldPreventDefault) {
				keyEvent.preventDefault();
			}
		}));

C
Christof Marti 已提交
319
		keybindingsTelemetry(telemetryService, this);
A
Alex Dima 已提交
320
		let data = KeyboardMapperFactory.INSTANCE.getCurrentKeyboardLayout();
K
kieferrm 已提交
321
		/* __GDPR__
K
kieferrm 已提交
322 323 324 325
			"keyboardLayout" : {
				"currentKeyboardLayout": { "${inline}": [ "${IKeyboardLayoutInfo}" ] }
			}
		*/
A
Alex Dima 已提交
326 327 328
		telemetryService.publicLog('keyboardLayout', {
			currentKeyboardLayout: data
		});
E
Erich Gamma 已提交
329 330
	}

A
Alex Dima 已提交
331
	public dumpDebugInfo(): string {
A
Alex Dima 已提交
332 333 334 335
		const layoutInfo = JSON.stringify(KeyboardMapperFactory.INSTANCE.getCurrentKeyboardLayout(), null, '\t');
		const mapperInfo = this._keyboardMapper.dumpDebugInfo();
		const rawMapping = JSON.stringify(KeyboardMapperFactory.INSTANCE.getRawKeyboardMapping(), null, '\t');
		return `Layout info:\n${layoutInfo}\n${mapperInfo}\n\nRaw mapping:\n${rawMapping}`;
A
Alex Dima 已提交
336 337
	}

338 339 340 341 342 343 344 345
	private _safeGetConfig(): IUserFriendlyKeybinding[] {
		let rawConfig = this.userKeybindings.getConfig();
		if (Array.isArray(rawConfig)) {
			return rawConfig;
		}
		return [];
	}

346
	public customKeybindingsCount(): number {
347
		let userKeybindings = this._safeGetConfig();
348 349

		return userKeybindings.length;
350 351
	}

352 353 354 355 356 357 358
	private updateResolver(event: IKeybindingEvent): void {
		this._cachedResolver = null;
		this._onDidUpdateKeybindings.fire(event);
	}

	protected _getResolver(): KeybindingResolver {
		if (!this._cachedResolver) {
359 360
			const defaults = this._resolveKeybindingItems(KeybindingsRegistry.getDefaultKeybindings(), true);
			const overrides = this._resolveUserKeybindingItems(this._getExtraKeybindings(this._firstTimeComputingResolver), false);
361
			this._cachedResolver = new KeybindingResolver(defaults, overrides);
362 363 364
			this._firstTimeComputingResolver = false;
		}
		return this._cachedResolver;
365 366
	}

367
	private _resolveKeybindingItems(items: IKeybindingItem[], isDefault: boolean): ResolvedKeybindingItem[] {
368
		let result: ResolvedKeybindingItem[] = [], resultLen = 0;
369 370 371
		for (let i = 0, len = items.length; i < len; i++) {
			const item = items[i];
			const when = (item.when ? item.when.normalize() : null);
A
Alex Dima 已提交
372
			const keybinding = item.keybinding;
373 374 375 376 377 378 379 380 381
			if (!keybinding) {
				// This might be a removal keybinding item in user settings => accept it
				result[resultLen++] = new ResolvedKeybindingItem(null, item.command, item.commandArgs, when, isDefault);
			} else {
				const resolvedKeybindings = this.resolveKeybinding(keybinding);
				for (let j = 0; j < resolvedKeybindings.length; j++) {
					result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybindings[j], item.command, item.commandArgs, when, isDefault);
				}
			}
382 383 384
		}

		return result;
385 386
	}

387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
	private _resolveUserKeybindingItems(items: IUserKeybindingItem[], isDefault: boolean): ResolvedKeybindingItem[] {
		let result: ResolvedKeybindingItem[] = [], resultLen = 0;
		for (let i = 0, len = items.length; i < len; i++) {
			const item = items[i];
			const when = (item.when ? item.when.normalize() : null);
			const firstPart = item.firstPart;
			const chordPart = item.chordPart;
			if (!firstPart) {
				// This might be a removal keybinding item in user settings => accept it
				result[resultLen++] = new ResolvedKeybindingItem(null, item.command, item.commandArgs, when, isDefault);
			} else {
				const resolvedKeybindings = this._keyboardMapper.resolveUserBinding(firstPart, chordPart);
				for (let j = 0; j < resolvedKeybindings.length; j++) {
					result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybindings[j], item.command, item.commandArgs, when, isDefault);
				}
			}
		}

		return result;
	}

	private _getExtraKeybindings(isFirstTime: boolean): IUserKeybindingItem[] {
409
		let extraUserKeybindings: IUserFriendlyKeybinding[] = this._safeGetConfig();
410 411
		if (!isFirstTime) {
			let cnt = extraUserKeybindings.length;
412

K
kieferrm 已提交
413
			/* __GDPR__
K
kieferrm 已提交
414 415 416 417
				"customKeybindingsChanged" : {
					"keyCount" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
				}
			*/
418
			this._telemetryService.publicLog('customKeybindingsChanged', {
419 420
				keyCount: cnt
			});
421
		}
422

423
		return extraUserKeybindings.map((k) => KeybindingIO.readUserKeybindingItem(k, OS));
424 425
	}

426
	public resolveKeybinding(kb: Keybinding): ResolvedKeybinding[] {
427
		return this._keyboardMapper.resolveKeybinding(kb);
A
Alex Dima 已提交
428 429
	}

430
	public resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding {
431
		return this._keyboardMapper.resolveKeyboardEvent(keyboardEvent);
432 433
	}

434 435 436 437 438
	public resolveUserBinding(userBinding: string): ResolvedKeybinding[] {
		const [firstPart, chordPart] = KeybindingIO._readUserBinding(userBinding);
		return this._keyboardMapper.resolveUserBinding(firstPart, chordPart);
	}

439
	private _handleKeybindingsExtensionPointUser(isBuiltin: boolean, keybindings: ContributedKeyBinding | ContributedKeyBinding[], collector: ExtensionMessageCollector): boolean {
E
Erich Gamma 已提交
440 441 442 443 444 445 446 447 448 449 450
		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);
		}
	}

451
	private _handleKeybinding(isBuiltin: boolean, idx: number, keybindings: ContributedKeyBinding, collector: ExtensionMessageCollector): boolean {
E
Erich Gamma 已提交
452 453 454 455 456 457 458

		let rejects: string[] = [];
		let commandAdded = false;

		if (isValidContributedKeyBinding(keybindings, rejects)) {
			let rule = this._asCommandRule(isBuiltin, idx++, keybindings);
			if (rule) {
459
				KeybindingsRegistry.registerKeybindingRule2(rule);
E
Erich Gamma 已提交
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
				commandAdded = true;
			}
		}

		if (rejects.length > 0) {
			collector.error(nls.localize(
				'invalid.keybindings',
				"Invalid `contributes.{0}`: {1}",
				keybindingsExtPoint.name,
				rejects.join('\n')
			));
		}

		return commandAdded;
	}

476
	private _asCommandRule(isBuiltin: boolean, idx: number, binding: ContributedKeyBinding): IKeybindingRule2 {
E
Erich Gamma 已提交
477

478
		let { command, when, key, mac, linux, win } = binding;
E
Erich Gamma 已提交
479 480 481 482 483 484 485 486 487 488

		let weight: number;
		if (isBuiltin) {
			weight = KeybindingsRegistry.WEIGHT.builtinExtension(idx);
		} else {
			weight = KeybindingsRegistry.WEIGHT.externalExtension(idx);
		}

		let desc = {
			id: command,
489
			when: ContextKeyExpr.deserialize(when),
E
Erich Gamma 已提交
490
			weight: weight,
A
Alex Dima 已提交
491 492 493 494
			primary: KeybindingIO.readKeybinding(key, OS),
			mac: mac && { primary: KeybindingIO.readKeybinding(mac, OS) },
			linux: linux && { primary: KeybindingIO.readKeybinding(linux, OS) },
			win: win && { primary: KeybindingIO.readKeybinding(win, OS) }
B
Benjamin Pasero 已提交
495
		};
E
Erich Gamma 已提交
496 497

		if (!desc.primary && !desc.mac && !desc.linux && !desc.win) {
498
			return undefined;
E
Erich Gamma 已提交
499 500 501 502
		}

		return desc;
	}
A
Alex Dima 已提交
503

S
Sandeep Somavarapu 已提交
504
	public getDefaultKeybindingsContent(): string {
A
Alex Dima 已提交
505 506 507 508 509 510 511 512 513 514
		const resolver = this._getResolver();
		const defaultKeybindings = resolver.getDefaultKeybindings();
		const boundCommands = resolver.getDefaultBoundCommands();
		return (
			WorkbenchKeybindingService._getDefaultKeybindings(defaultKeybindings)
			+ '\n\n'
			+ WorkbenchKeybindingService._getAllCommandsAsComment(boundCommands)
		);
	}

515
	private static _getDefaultKeybindings(defaultKeybindings: ResolvedKeybindingItem[]): string {
A
Alex Dima 已提交
516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532
		let out = new OutputBuilder();
		out.writeLine('[');

		let lastIndex = defaultKeybindings.length - 1;
		defaultKeybindings.forEach((k, index) => {
			KeybindingIO.writeKeybindingItem(out, k, OS);
			if (index !== lastIndex) {
				out.writeLine(',');
			} else {
				out.writeLine();
			}
		});
		out.writeLine(']');
		return out.toString();
	}

	private static _getAllCommandsAsComment(boundCommands: Map<string, boolean>): string {
S
Sandeep Somavarapu 已提交
533
		const unboundCommands = KeybindingResolver.getAllUnboundCommands(boundCommands);
A
Alex Dima 已提交
534 535 536
		let pretty = unboundCommands.sort().join('\n// - ');
		return '// ' + nls.localize('unboundCommands', "Here are other available commands: ") + '\n// - ' + pretty;
	}
537
}
538

539
let schemaId = 'vscode://schemas/keybindings';
540
let schema: IJSONSchema = {
541 542 543 544 545 546
	'id': schemaId,
	'type': 'array',
	'title': nls.localize('keybindings.json.title', "Keybindings configuration"),
	'items': {
		'required': ['key'],
		'type': 'object',
547
		'defaultSnippets': [{ 'body': { 'key': '$1', 'command': '$2', 'when': '$3' } }],
548 549 550
		'properties': {
			'key': {
				'type': 'string',
551
				'description': nls.localize('keybindings.json.key', "Key or key sequence (separated by space)"),
552 553
			},
			'command': {
554
				'description': nls.localize('keybindings.json.command', "Name of the command to execute"),
555 556 557
			},
			'when': {
				'type': 'string',
558 559 560 561
				'description': nls.localize('keybindings.json.when', "Condition when the key is active.")
			},
			'args': {
				'description': nls.localize('keybindings.json.args', "Arguments to pass to the command to execute.")
562 563 564 565 566
			}
		}
	}
};

567
let schemaRegistry = <IJSONContributionRegistry>Registry.as(Extensions.JSONContribution);
568
schemaRegistry.registerSchema(schemaId, schema);
569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591

if (OS === OperatingSystem.Macintosh || OS === OperatingSystem.Linux) {

	const configurationRegistry = <IConfigurationRegistry>Registry.as(ConfigExtensions.Configuration);
	const keyboardConfiguration: IConfigurationNode = {
		'id': 'keyboard',
		'order': 15,
		'type': 'object',
		'title': nls.localize('keyboardConfigurationTitle', "Keyboard"),
		'overridable': true,
		'properties': {
			'keyboard.dispatch': {
				'type': 'string',
				'enum': ['code', 'keyCode'],
				'default': 'code',
				'description': nls.localize('dispatch', "Controls the dispatching logic for key presses to use either `keydown.code` (recommended) or `keydown.keyCode`.")
			}
		}
	};

	configurationRegistry.registerConfiguration(keyboardConfiguration);

}