keybindingServiceImpl.ts 15.5 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 'vs/css!./keybindings';
8
import * as nls from 'vs/nls';
A
Alex Dima 已提交
9 10
import {IHTMLContentElement} from 'vs/base/common/htmlContent';
import {KeyCode, Keybinding} from 'vs/base/common/keyCodes';
J
Joao Moreno 已提交
11
import {IDisposable, dispose} from 'vs/base/common/lifecycle';
E
Erich Gamma 已提交
12
import Severity from 'vs/base/common/severity';
13
import {isFalsyOrEmpty} from 'vs/base/common/arrays';
A
Alex Dima 已提交
14 15
import * as dom from 'vs/base/browser/dom';
import {IKeyboardEvent, StandardKeyboardEvent} from 'vs/base/browser/keyboardEvent';
E
Erich Gamma 已提交
16
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
17
import {ICommandService, CommandsRegistry, ICommandHandler, ICommandHandlerDescription} from 'vs/platform/commands/common/commands';
18
import {KeybindingResolver} from 'vs/platform/keybinding/common/keybindingResolver';
19
import {IKeybindingContextKey, IKeybindingItem, IKeybindingScopeLocation, IKeybindingService, SET_CONTEXT_COMMAND_ID, KbExpr} from 'vs/platform/keybinding/common/keybinding';
A
Alex Dima 已提交
20
import {KeybindingsRegistry} from 'vs/platform/keybinding/common/keybindingsRegistry';
21
import {IStatusbarService} from 'vs/platform/statusbar/common/statusbar';
A
Alex Dima 已提交
22
import {IMessageService} from 'vs/platform/message/common/message';
23
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
24
import Event, {Emitter, debounceEvent} from 'vs/base/common/event';
E
Erich Gamma 已提交
25

26
let KEYBINDING_CONTEXT_ATTR = 'data-keybinding-context';
E
Erich Gamma 已提交
27 28

export class KeybindingContext {
29 30 31
	protected _parent: KeybindingContext;
	protected _value: any;
	protected _id: number;
E
Erich Gamma 已提交
32 33 34 35 36 37 38 39

	constructor(id: number, parent: KeybindingContext) {
		this._id = id;
		this._parent = parent;
		this._value = Object.create(null);
		this._value['_contextId'] = id;
	}

40
	public setValue(key: string, value: any): boolean {
41
		// console.log('SET ' + key + ' = ' + value + ' ON ' + this._id);
42 43 44 45
		if (this._value[key] !== value) {
			this._value[key] = value;
			return true;
		}
E
Erich Gamma 已提交
46 47
	}

48
	public removeValue(key: string): boolean {
49
		// console.log('REMOVE ' + key + ' FROM ' + this._id);
50
		return delete this._value[key];
E
Erich Gamma 已提交
51 52
	}

53 54 55 56 57 58 59 60
	public getValue<T>(key: string): T {
		const ret = this._value[key];
		if (typeof ret === 'undefined' && this._parent) {
			return this._parent.getValue<T>(key);
		}
		return ret;
	}

61 62 63 64
	public fillInContext(bucket: any): void {
		if (this._parent) {
			this._parent.fillInContext(bucket);
		}
65
		for (let key in this._value) {
66 67 68 69 70
			bucket[key] = this._value[key];
		}
	}
}

71
class ConfigAwareKeybindingContext extends KeybindingContext {
72

73
	private _emitter: Emitter<string>;
74 75
	private _subscription: IDisposable;

76 77 78 79
	constructor(id: number, configurationService: IConfigurationService, emitter:Emitter<string>) {
		super(id, null);

		this._emitter = emitter;
80
		this._subscription = configurationService.onDidUpdateConfiguration(e => this._updateConfigurationContext(e.config));
81
		this._updateConfigurationContext(configurationService.getConfiguration());
82 83 84 85 86 87 88
	}

	public dispose() {
		this._subscription.dispose();
	}

	private _updateConfigurationContext(config: any) {
89 90 91 92 93 94 95 96 97

		// remove old config.xyz values
		for (let key in this._value) {
			if (key.indexOf('config.') === 0) {
				delete this._value[key];
			}
		}

		// add new value from config
98 99 100 101 102 103
		const walk = (obj: any, keys: string[]) => {
			for (let key in obj) {
				if (Object.prototype.hasOwnProperty.call(obj, key)) {
					keys.push(key);
					let value = obj[key];
					if (typeof value === 'boolean') {
104 105 106
						const configKey = keys.join('.');
						this._value[configKey] = value;
						this._emitter.fire(configKey);
107 108 109 110 111 112 113 114 115
					} else if (typeof value === 'object') {
						walk(value, keys);
					}
					keys.pop();
				}
			}
		};
		walk(config, ['config']);
	}
E
Erich Gamma 已提交
116 117 118 119 120 121 122 123 124 125 126 127
}

class KeybindingContextKey<T> implements IKeybindingContextKey<T> {

	private _parent: AbstractKeybindingService;
	private _key: string;
	private _defaultValue: T;

	constructor(parent: AbstractKeybindingService, key: string, defaultValue: T) {
		this._parent = parent;
		this._key = key;
		this._defaultValue = defaultValue;
128
		this.reset();
E
Erich Gamma 已提交
129 130 131 132 133 134 135 136 137 138 139 140 141 142
	}

	public set(value: T): void {
		this._parent.setContext(this._key, value);
	}

	public reset(): void {
		if (typeof this._defaultValue === 'undefined') {
			this._parent.removeContext(this._key);
		} else {
			this._parent.setContext(this._key, this._defaultValue);
		}
	}

A
Alex Dima 已提交
143 144 145 146
	public get(): T {
		return this._parent.getContextValue<T>(this._key);
	}

E
Erich Gamma 已提交
147 148
}

149
export abstract class AbstractKeybindingService {
150
	public _serviceBrand: any;
151 152 153

	protected _onDidChangeContext: Event<string[]>;
	protected _onDidChangeContextKey: Emitter<string>;
E
Erich Gamma 已提交
154 155 156 157 158
	protected _myContextId: number;
	protected _instantiationService: IInstantiationService;

	constructor(myContextId: number) {
		this._myContextId = myContextId;
159
		this._onDidChangeContextKey = new Emitter<string>();
E
Erich Gamma 已提交
160 161 162 163 164 165 166
		this._instantiationService = null;
	}

	public createKey<T>(key: string, defaultValue: T): IKeybindingContextKey<T> {
		return new KeybindingContextKey(this, key, defaultValue);
	}

167 168 169 170 171 172 173 174 175 176 177 178 179 180
	public get onDidChangeContext(): Event<string[]> {
		if (!this._onDidChangeContext) {
			this._onDidChangeContext = debounceEvent(this._onDidChangeContextKey.event, (prev: string[], cur) => {
				if (!prev) {
					prev = [cur];
				} else if (prev.indexOf(cur) < 0) {
					prev.push(cur);
				}
				return prev;
			}, 25);
		}
		return this._onDidChangeContext;
	}

E
Erich Gamma 已提交
181 182 183 184 185
	public setInstantiationService(instantiationService: IInstantiationService): void {
		this._instantiationService = instantiationService;
	}

	public createScoped(domNode: IKeybindingScopeLocation): IKeybindingService {
186
		return new ScopedKeybindingService(this, this._onDidChangeContextKey, domNode);
E
Erich Gamma 已提交
187 188
	}

189 190 191
	public contextMatchesRules(rules: KbExpr): boolean {
		const ctx = Object.create(null);
		this.getContext(this._myContextId).fillInContext(ctx);
192 193 194 195 196
		const result = KeybindingResolver.contextMatchesRules(ctx, rules);
		// console.group(rules.serialize() + ' -> ' + result);
		// rules.keys().forEach(key => { console.log(key, ctx[key]); });
		// console.groupEnd();
		return result;
197 198 199 200 201 202
	}

	public getContextValue<T>(key: string): T {
		return this.getContext(this._myContextId).getValue<T>(key);
	}

E
Erich Gamma 已提交
203
	public setContext(key: string, value: any): void {
204 205 206
		if(this.getContext(this._myContextId).setValue(key, value)) {
			this._onDidChangeContextKey.fire(key);
		}
E
Erich Gamma 已提交
207 208 209
	}

	public removeContext(key: string): void {
210 211 212
		if(this.getContext(this._myContextId).removeValue(key)) {
			this._onDidChangeContextKey.fire(key);
		}
E
Erich Gamma 已提交
213 214
	}

B
Benjamin Pasero 已提交
215 216
	public abstract getLabelFor(keybinding: Keybinding): string;
	public abstract getHTMLLabelFor(keybinding: Keybinding): IHTMLContentElement[];
217
	public abstract getAriaLabelFor(keybinding: Keybinding): string;
B
Benjamin Pasero 已提交
218
	public abstract getElectronAcceleratorFor(keybinding: Keybinding): string;
219 220 221 222 223 224
	public abstract customKeybindingsCount(): number;
	public abstract getContext(contextId: number): KeybindingContext;
	public abstract createChildContext(parentContextId?: number): number;
	public abstract disposeContext(contextId: number): void;
	public abstract getDefaultKeybindings(): string;
	public abstract lookupKeybindings(commandId: string): Keybinding[];
225

E
Erich Gamma 已提交
226 227
}

228
export abstract class KeybindingService extends AbstractKeybindingService implements IKeybindingService {
E
Erich Gamma 已提交
229 230 231

	private _lastContextId: number;
	private _contexts: {
B
Benjamin Pasero 已提交
232
		[contextId: string]: KeybindingContext;
E
Erich Gamma 已提交
233 234
	};

235
	private _toDispose: IDisposable[] = [];
236 237
	private _cachedResolver: KeybindingResolver;
	private _firstTimeComputingResolver: boolean;
E
Erich Gamma 已提交
238
	private _currentChord: number;
239
	private _currentChordStatusMessage: IDisposable;
240
	private _commandService: ICommandService;
241
	private _statusService: IStatusbarService;
242
	private _messageService: IMessageService;
E
Erich Gamma 已提交
243

244
	constructor(commandService: ICommandService, configurationService: IConfigurationService, messageService: IMessageService, statusService?: IStatusbarService) {
245 246
		super(0);
		this._lastContextId = 0;
E
Erich Gamma 已提交
247
		this._contexts = Object.create(null);
248 249 250 251 252

		const myContext = new ConfigAwareKeybindingContext(this._myContextId, configurationService, this._onDidChangeContextKey);
		this._contexts[String(this._myContextId)] = myContext;
		this._toDispose.push(myContext);

253 254 255 256
		this._cachedResolver = null;
		this._firstTimeComputingResolver = true;
		this._currentChord = 0;
		this._currentChordStatusMessage = null;
257
		this._commandService = commandService;
258
		this._statusService = statusService;
259
		this._messageService = messageService;
260
	}
261

262
	protected _beginListening(domNode: HTMLElement): void {
263
		this._toDispose.push(dom.addDisposableListener(domNode, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
264
			let keyEvent = new StandardKeyboardEvent(e);
E
Erich Gamma 已提交
265
			this._dispatch(keyEvent);
266
		}));
267
	}
E
Erich Gamma 已提交
268

269 270 271 272 273 274
	private _getResolver(): KeybindingResolver {
		if (!this._cachedResolver) {
			this._cachedResolver = new KeybindingResolver(KeybindingsRegistry.getDefaultKeybindings(), this._getExtraKeybindings(this._firstTimeComputingResolver));
			this._firstTimeComputingResolver = false;
		}
		return this._cachedResolver;
E
Erich Gamma 已提交
275 276 277
	}

	public dispose(): void {
J
Joao Moreno 已提交
278
		this._toDispose = dispose(this._toDispose);
E
Erich Gamma 已提交
279 280
	}

B
Benjamin Pasero 已提交
281
	public getLabelFor(keybinding: Keybinding): string {
282 283 284
		return keybinding._toUSLabel();
	}

B
Benjamin Pasero 已提交
285
	public getHTMLLabelFor(keybinding: Keybinding): IHTMLContentElement[] {
286 287 288
		return keybinding._toUSHTMLLabel();
	}

289 290 291 292
	public getAriaLabelFor(keybinding: Keybinding): string {
		return keybinding._toUSAriaLabel();
	}

B
Benjamin Pasero 已提交
293
	public getElectronAcceleratorFor(keybinding: Keybinding): string {
294 295 296
		return keybinding._toElectronAccelerator();
	}

E
Erich Gamma 已提交
297
	protected updateResolver(): void {
298
		this._cachedResolver = null;
E
Erich Gamma 已提交
299 300
	}

B
Benjamin Pasero 已提交
301
	protected _getExtraKeybindings(isFirstTime: boolean): IKeybindingItem[] {
E
Erich Gamma 已提交
302 303 304 305
		return [];
	}

	public getDefaultKeybindings(): string {
306
		return this._getResolver().getDefaultKeybindings() + '\n\n' + this._getAllCommandsAsComment();
E
Erich Gamma 已提交
307 308 309 310 311 312 313
	}

	public customKeybindingsCount(): number {
		return 0;
	}

	public lookupKeybindings(commandId: string): Keybinding[] {
314
		return this._getResolver().lookupKeybinding(commandId);
E
Erich Gamma 已提交
315 316 317
	}

	private _getAllCommandsAsComment(): string {
318
		const commands = CommandsRegistry.getCommands();
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
		const unboundCommands: string[] = [];
		const boundCommands = this._getResolver().getDefaultBoundCommands();

		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[id]) {
				continue;
			}
			unboundCommands.push(id);
		}

		let pretty = unboundCommands.sort().join('\n// - ');
E
Erich Gamma 已提交
337 338 339 340

		return '// ' + nls.localize('unboundCommands', "Here are other available commands: ") + '\n// - ' + pretty;
	}

B
Benjamin Pasero 已提交
341
	protected _getCommandHandler(commandId: string): ICommandHandler {
342
		return CommandsRegistry.getCommand(commandId).handler;
E
Erich Gamma 已提交
343 344
	}

A
Cleanup  
Alex Dima 已提交
345
	private _dispatch(e: IKeyboardEvent): void {
346
		let isModifierKey = (e.keyCode === KeyCode.Ctrl || e.keyCode === KeyCode.Shift || e.keyCode === KeyCode.Alt || e.keyCode === KeyCode.Meta);
E
Erich Gamma 已提交
347 348 349 350
		if (isModifierKey) {
			return;
		}

351 352 353
		let contextValue = Object.create(null);
		this.getContext(this._findContextAttr(e.target)).fillInContext(contextValue);
		// console.log(JSON.stringify(contextValue, null, '\t'));
E
Erich Gamma 已提交
354

355
		let resolveResult = this._getResolver().resolve(contextValue, this._currentChord, e.asKeybinding());
E
Erich Gamma 已提交
356 357 358 359

		if (resolveResult && resolveResult.enterChord) {
			e.preventDefault();
			this._currentChord = resolveResult.enterChord;
360
			if (this._statusService) {
361
				let firstPartLabel = this.getLabelFor(new Keybinding(this._currentChord));
362
				this._currentChordStatusMessage = this._statusService.setStatusMessage(nls.localize('first.chord', "({0}) was pressed. Waiting for second key of chord...", firstPartLabel));
E
Erich Gamma 已提交
363 364 365 366
			}
			return;
		}

367
		if (this._statusService && this._currentChord) {
E
Erich Gamma 已提交
368
			if (!resolveResult || !resolveResult.commandId) {
369 370
				let firstPartLabel = this.getLabelFor(new Keybinding(this._currentChord));
				let chordPartLabel = this.getLabelFor(new Keybinding(e.asKeybinding()));
371
				this._statusService.setStatusMessage(nls.localize('missing.chord', "The key combination ({0}, {1}) is not a command.", firstPartLabel, chordPartLabel), 10 * 1000 /* 10s */);
E
Erich Gamma 已提交
372 373 374 375 376 377 378 379 380 381 382 383 384
				e.preventDefault();
			}
		}
		if (this._currentChordStatusMessage) {
			this._currentChordStatusMessage.dispose();
			this._currentChordStatusMessage = null;
		}
		this._currentChord = 0;

		if (resolveResult && resolveResult.commandId) {
			if (!/^\^/.test(resolveResult.commandId)) {
				e.preventDefault();
			}
385
			let commandId = resolveResult.commandId.replace(/^\^/, '');
386
			this._commandService.executeCommand(commandId, {}).done(undefined, err => {
E
Erich Gamma 已提交
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
				this._messageService.show(Severity.Warning, err);
			});
		}
	}

	private _findContextAttr(domNode: HTMLElement): number {
		while (domNode) {
			if (domNode.hasAttribute(KEYBINDING_CONTEXT_ATTR)) {
				return parseInt(domNode.getAttribute(KEYBINDING_CONTEXT_ATTR), 10);
			}
			domNode = domNode.parentElement;
		}
		return this._myContextId;
	}

	public getContext(contextId: number): KeybindingContext {
		return this._contexts[String(contextId)];
	}

	public createChildContext(parentContextId: number = this._myContextId): number {
407
		let id = (++this._lastContextId);
E
Erich Gamma 已提交
408 409 410 411
		this._contexts[String(id)] = new KeybindingContext(id, this.getContext(parentContextId));
		return id;
	}

B
Benjamin Pasero 已提交
412
	public disposeContext(contextId: number): void {
E
Erich Gamma 已提交
413 414 415 416
		delete this._contexts[String(contextId)];
	}
}

417 418
CommandsRegistry.registerCommand(SET_CONTEXT_COMMAND_ID, function (accessor, contextKey: any, contextValue: any) {
	accessor.get(IKeybindingService).createKey(String(contextKey), contextValue);
419 420
});

E
Erich Gamma 已提交
421 422 423 424 425
class ScopedKeybindingService extends AbstractKeybindingService {

	private _parent: AbstractKeybindingService;
	private _domNode: IKeybindingScopeLocation;

426
	constructor(parent: AbstractKeybindingService, emitter: Emitter<string>, domNode: IKeybindingScopeLocation) {
427
		super(parent.createChildContext());
E
Erich Gamma 已提交
428
		this._parent = parent;
429
		this._onDidChangeContextKey = emitter;
E
Erich Gamma 已提交
430 431 432 433 434 435 436 437 438
		this._domNode = domNode;
		this._domNode.setAttribute(KEYBINDING_CONTEXT_ATTR, String(this._myContextId));
	}

	public dispose(): void {
		this._parent.disposeContext(this._myContextId);
		this._domNode.removeAttribute(KEYBINDING_CONTEXT_ATTR);
	}

439 440 441 442
	public get onDidChangeContext(): Event<string[]> {
		return this._parent.onDidChangeContext;
	}

B
Benjamin Pasero 已提交
443
	public getLabelFor(keybinding: Keybinding): string {
444 445 446
		return this._parent.getLabelFor(keybinding);
	}

B
Benjamin Pasero 已提交
447
	public getHTMLLabelFor(keybinding: Keybinding): IHTMLContentElement[] {
448 449 450
		return this._parent.getHTMLLabelFor(keybinding);
	}

451 452 453 454
	public getAriaLabelFor(keybinding: Keybinding): string {
		return this._parent.getAriaLabelFor(keybinding);
	}

B
Benjamin Pasero 已提交
455
	public getElectronAcceleratorFor(keybinding: Keybinding): string {
456 457 458
		return this._parent.getElectronAcceleratorFor(keybinding);
	}

E
Erich Gamma 已提交
459 460 461 462 463 464 465 466
	public getDefaultKeybindings(): string {
		return this._parent.getDefaultKeybindings();
	}

	public customKeybindingsCount(): number {
		return this._parent.customKeybindingsCount();
	}

B
Benjamin Pasero 已提交
467
	public lookupKeybindings(commandId: string): Keybinding[] {
E
Erich Gamma 已提交
468 469 470 471 472 473 474 475 476 477 478
		return this._parent.lookupKeybindings(commandId);
	}

	public getContext(contextId: number): KeybindingContext {
		return this._parent.getContext(contextId);
	}

	public createChildContext(parentContextId: number = this._myContextId): number {
		return this._parent.createChildContext(parentContextId);
	}

B
Benjamin Pasero 已提交
479
	public disposeContext(contextId: number): void {
E
Erich Gamma 已提交
480 481 482
		this._parent.disposeContext(contextId);
	}
}