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

6
import * as nls from 'vs/nls';
A
Alex Dima 已提交
7
import * as arrays from 'vs/base/common/arrays';
A
Alex Dima 已提交
8 9 10 11
import { IntervalTimer } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { KeyCode, Keybinding, ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
A
Alex Dima 已提交
12
import { ICommandService } from 'vs/platform/commands/common/commands';
D
Daniel Imms 已提交
13
import { IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey';
A
Alex Dima 已提交
14 15
import { IKeybindingEvent, IKeybindingService, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding';
import { IResolveResult, KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver';
16
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
17
import { INotificationService } from 'vs/platform/notification/common/notification';
A
Alex Dima 已提交
18
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
19
import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';
A
Alex Dima 已提交
20

21 22
interface CurrentChord {
	keypress: string;
A
Alex Dima 已提交
23
	label: string | null;
24 25
}

26
export abstract class AbstractKeybindingService extends Disposable implements IKeybindingService {
A
Alex Dima 已提交
27 28
	public _serviceBrand: any;

29 30 31 32 33
	protected readonly _onDidUpdateKeybindings: Emitter<IKeybindingEvent> = this._register(new Emitter<IKeybindingEvent>());
	get onDidUpdateKeybindings(): Event<IKeybindingEvent> {
		return this._onDidUpdateKeybindings ? this._onDidUpdateKeybindings.event : Event.None; // Sinon stubbing walks properties on prototype
	}

A
Alex Dima 已提交
34
	private _currentChord: CurrentChord | null;
35
	private _currentChordChecker: IntervalTimer;
A
Alex Dima 已提交
36
	private _currentChordStatusMessage: IDisposable | null;
E
Erich Gamma 已提交
37

A
Alex Dima 已提交
38
	constructor(
39 40 41 42
		private _contextKeyService: IContextKeyService,
		protected _commandService: ICommandService,
		protected _telemetryService: ITelemetryService,
		private _notificationService: INotificationService,
A
Alex Dima 已提交
43
	) {
44
		super();
45

A
Alex Dima 已提交
46
		this._currentChord = null;
47
		this._currentChordChecker = new IntervalTimer();
48
		this._currentChordStatusMessage = null;
A
Alex Dima 已提交
49
	}
50

A
Alex Dima 已提交
51
	public dispose(): void {
52
		super.dispose();
53
	}
54

55
	protected abstract _getResolver(): KeybindingResolver;
56
	protected abstract _documentHasFocus(): boolean;
57
	public abstract resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[];
58
	public abstract resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding;
59
	public abstract resolveUserBinding(userBinding: string): ResolvedKeybinding[];
60
	public abstract _dumpDebugInfo(): string;
P
Peng Lyu 已提交
61
	public abstract _dumpDebugInfoJSON(): string;
62

S
Sandeep Somavarapu 已提交
63
	public getDefaultKeybindingsContent(): string {
A
Alex Dima 已提交
64
		return '';
E
Erich Gamma 已提交
65 66
	}

S
Sandeep Somavarapu 已提交
67 68 69 70
	public getDefaultKeybindings(): ResolvedKeybindingItem[] {
		return this._getResolver().getDefaultKeybindings();
	}

71 72
	public getKeybindings(): ResolvedKeybindingItem[] {
		return this._getResolver().getKeybindings();
73 74
	}

75 76 77 78
	public customKeybindingsCount(): number {
		return 0;
	}

79
	public lookupKeybindings(commandId: string): ResolvedKeybinding[] {
A
Alex Dima 已提交
80 81 82
		return arrays.coalesce(
			this._getResolver().lookupKeybindings(commandId).map(item => item.resolvedKeybinding)
		);
83 84
	}

M
Matt Bierner 已提交
85
	public lookupKeybinding(commandId: string): ResolvedKeybinding | undefined {
86
		const result = this._getResolver().lookupPrimaryKeybinding(commandId);
87
		if (!result) {
M
Matt Bierner 已提交
88
			return undefined;
89
		}
90
		return result.resolvedKeybinding;
91 92
	}

93 94 95 96
	public dispatchEvent(e: IKeyboardEvent, target: IContextKeyServiceTarget): boolean {
		return this._dispatch(e, target);
	}

A
Alex Dima 已提交
97
	public softDispatch(e: IKeyboardEvent, target: IContextKeyServiceTarget): IResolveResult | null {
98 99 100 101 102 103 104 105
		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
106
			return null;
E
Erich Gamma 已提交
107 108
		}

J
Joao Moreno 已提交
109
		const contextValue = this._contextKeyService.getContext(target);
110
		const currentChord = this._currentChord ? this._currentChord.keypress : null;
111
		return this._getResolver().resolve(contextValue, currentChord, firstPart);
D
Daniel Imms 已提交
112 113
	}

A
Alex Dima 已提交
114
	private _enterChordMode(firstPart: string, keypressLabel: string | null): void {
115 116 117 118
		this._currentChord = {
			keypress: firstPart,
			label: keypressLabel
		};
119
		this._currentChordStatusMessage = this._notificationService.status(nls.localize('first.chord', "({0}) was pressed. Waiting for second key of chord...", keypressLabel));
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
		const chordEnterTime = Date.now();
		this._currentChordChecker.cancelAndSet(() => {

			if (!this._documentHasFocus()) {
				// Focus has been lost => leave chord mode
				this._leaveChordMode();
				return;
			}

			if (Date.now() - chordEnterTime > 5000) {
				// 5 seconds elapsed => leave chord mode
				this._leaveChordMode();
			}

		}, 500);
	}

	private _leaveChordMode(): void {
		if (this._currentChordStatusMessage) {
			this._currentChordStatusMessage.dispose();
			this._currentChordStatusMessage = null;
		}
		this._currentChordChecker.cancel();
		this._currentChord = null;
	}

146 147
	public dispatchByUserSettingsLabel(userSettingsLabel: string, target: IContextKeyServiceTarget): void {
		const keybindings = this.resolveUserBinding(userSettingsLabel);
148
		if (keybindings.length >= 1) {
149 150 151 152
			this._doDispatch(keybindings[0], target);
		}
	}

153
	protected _dispatch(e: IKeyboardEvent, target: IContextKeyServiceTarget): boolean {
154 155 156 157
		return this._doDispatch(this.resolveKeyboardEvent(e), target);
	}

	private _doDispatch(keybinding: ResolvedKeybinding, target: IContextKeyServiceTarget): boolean {
158
		let shouldPreventDefault = false;
159 160 161

		if (keybinding.isChord()) {
			console.warn('Unexpected keyboard event mapped to a chord');
A
Alex Dima 已提交
162
			return false;
163 164 165 166
		}
		const [firstPart,] = keybinding.getDispatchParts();
		if (firstPart === null) {
			// cannot be dispatched, probably only modifier keys
167
			return shouldPreventDefault;
D
Daniel Imms 已提交
168
		}
E
Erich Gamma 已提交
169

J
Joao Moreno 已提交
170
		const contextValue = this._contextKeyService.getContext(target);
171
		const currentChord = this._currentChord ? this._currentChord.keypress : null;
172
		const keypressLabel = keybinding.getLabel();
173
		const resolveResult = this._getResolver().resolve(contextValue, currentChord, firstPart);
D
Daniel Imms 已提交
174 175

		if (resolveResult && resolveResult.enterChord) {
176
			shouldPreventDefault = true;
177
			this._enterChordMode(firstPart, keypressLabel);
178
			return shouldPreventDefault;
E
Erich Gamma 已提交
179 180
		}

181
		if (this._currentChord) {
E
Erich Gamma 已提交
182
			if (!resolveResult || !resolveResult.commandId) {
183
				this._notificationService.status(nls.localize('missing.chord', "The key combination ({0}, {1}) is not a command.", this._currentChord.label, keypressLabel), { hideAfter: 10 * 1000 /* 10s */ });
184
				shouldPreventDefault = true;
E
Erich Gamma 已提交
185 186
			}
		}
187 188

		this._leaveChordMode();
E
Erich Gamma 已提交
189 190

		if (resolveResult && resolveResult.commandId) {
191
			if (!resolveResult.bubble) {
192
				shouldPreventDefault = true;
E
Erich Gamma 已提交
193
			}
194
			if (typeof resolveResult.commandArgs === 'undefined') {
195
				this._commandService.executeCommand(resolveResult.commandId).then(undefined, err => this._notificationService.warn(err));
196
			} else {
197
				this._commandService.executeCommand(resolveResult.commandId, resolveResult.commandArgs).then(undefined, err => this._notificationService.warn(err));
198
			}
199
			this._telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: resolveResult.commandId, from: 'keybinding' });
E
Erich Gamma 已提交
200
		}
201 202

		return shouldPreventDefault;
E
Erich Gamma 已提交
203
	}
204 205 206 207 208 209 210 211 212 213 214 215 216 217

	mightProducePrintableCharacter(event: IKeyboardEvent): boolean {
		if (event.ctrlKey || event.metaKey) {
			// ignore ctrl/cmd-combination but not shift/alt-combinatios
			return false;
		}
		// weak check for certain ranges. this is properly implemented in a subclass
		// with access to the KeyboardMapperFactory.
		if ((event.keyCode >= KeyCode.KEY_A && event.keyCode <= KeyCode.KEY_Z)
			|| (event.keyCode >= KeyCode.KEY_0 && event.keyCode <= KeyCode.KEY_9)) {
			return true;
		}
		return false;
	}
218
}