1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | import { injectable, inject, named } from 'inversify';
|
18 | import { isOSX } from '../common/os';
|
19 | import { Emitter, Event } from '../common/event';
|
20 | import { CommandRegistry, Command } from '../common/command';
|
21 | import { Disposable, DisposableCollection } from '../common/disposable';
|
22 | import { KeyCode, KeySequence, Key } from './keyboard/keys';
|
23 | import { KeyboardLayoutService } from './keyboard/keyboard-layout-service';
|
24 | import { ContributionProvider } from '../common/contribution-provider';
|
25 | import { ILogger } from '../common/logger';
|
26 | import { StatusBarAlignment, StatusBar } from './status-bar/status-bar';
|
27 | import { ContextKeyService } from './context-key-service';
|
28 | import { CorePreferences } from './core-preferences';
|
29 | import * as common from '../common/keybinding';
|
30 | import { nls } from '../common/nls';
|
31 |
|
32 | export enum KeybindingScope {
|
33 | DEFAULT,
|
34 | USER,
|
35 | WORKSPACE,
|
36 | END
|
37 | }
|
38 | export namespace KeybindingScope {
|
39 | export const length = KeybindingScope.END - KeybindingScope.DEFAULT;
|
40 | }
|
41 |
|
42 |
|
43 |
|
44 |
|
45 | export type Keybinding = common.Keybinding;
|
46 | export const Keybinding = common.Keybinding;
|
47 |
|
48 | export interface ResolvedKeybinding extends common.Keybinding {
|
49 | |
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 | resolved?: KeyCode[];
|
56 | }
|
57 |
|
58 | export interface ScopedKeybinding extends common.Keybinding {
|
59 |
|
60 | scope: KeybindingScope;
|
61 | }
|
62 |
|
63 | export const KeybindingContribution = Symbol('KeybindingContribution');
|
64 |
|
65 |
|
66 |
|
67 | export interface KeybindingContribution {
|
68 | |
69 |
|
70 |
|
71 |
|
72 | registerKeybindings(keybindings: KeybindingRegistry): void;
|
73 | }
|
74 |
|
75 | export const KeybindingContext = Symbol('KeybindingContext');
|
76 | export interface KeybindingContext {
|
77 | |
78 |
|
79 |
|
80 | readonly id: string;
|
81 |
|
82 | isEnabled(arg: common.Keybinding): boolean;
|
83 | }
|
84 | export namespace KeybindingContexts {
|
85 |
|
86 | export const NOOP_CONTEXT: KeybindingContext = {
|
87 | id: 'noop.keybinding.context',
|
88 | isEnabled: () => true
|
89 | };
|
90 |
|
91 | export const DEFAULT_CONTEXT: KeybindingContext = {
|
92 | id: 'default.keybinding.context',
|
93 | isEnabled: () => false
|
94 | };
|
95 | }
|
96 |
|
97 | @injectable()
|
98 | export class KeybindingRegistry {
|
99 |
|
100 | static readonly PASSTHROUGH_PSEUDO_COMMAND = 'passthrough';
|
101 | protected keySequence: KeySequence = [];
|
102 |
|
103 | protected readonly contexts: { [id: string]: KeybindingContext } = {};
|
104 | protected readonly keymaps: ScopedKeybinding[][] = [...Array(KeybindingScope.length)].map(() => []);
|
105 |
|
106 | @inject(CorePreferences)
|
107 | protected readonly corePreferences: CorePreferences;
|
108 |
|
109 | @inject(KeyboardLayoutService)
|
110 | protected readonly keyboardLayoutService: KeyboardLayoutService;
|
111 |
|
112 | @inject(ContributionProvider) @named(KeybindingContext)
|
113 | protected readonly contextProvider: ContributionProvider<KeybindingContext>;
|
114 |
|
115 | @inject(CommandRegistry)
|
116 | protected readonly commandRegistry: CommandRegistry;
|
117 |
|
118 | @inject(ContributionProvider) @named(KeybindingContribution)
|
119 | protected readonly contributions: ContributionProvider<KeybindingContribution>;
|
120 |
|
121 | @inject(StatusBar)
|
122 | protected readonly statusBar: StatusBar;
|
123 |
|
124 | @inject(ILogger)
|
125 | protected readonly logger: ILogger;
|
126 |
|
127 | @inject(ContextKeyService)
|
128 | protected readonly whenContextService: ContextKeyService;
|
129 |
|
130 | async onStart(): Promise<void> {
|
131 | await this.keyboardLayoutService.initialize();
|
132 | this.keyboardLayoutService.onKeyboardLayoutChanged(newLayout => {
|
133 | this.clearResolvedKeybindings();
|
134 | this.keybindingsChanged.fire(undefined);
|
135 | });
|
136 | this.registerContext(KeybindingContexts.NOOP_CONTEXT);
|
137 | this.registerContext(KeybindingContexts.DEFAULT_CONTEXT);
|
138 | this.registerContext(...this.contextProvider.getContributions());
|
139 | for (const contribution of this.contributions.getContributions()) {
|
140 | contribution.registerKeybindings(this);
|
141 | }
|
142 | }
|
143 |
|
144 | protected keybindingsChanged = new Emitter<void>();
|
145 |
|
146 | |
147 |
|
148 |
|
149 |
|
150 | get onKeybindingsChanged(): Event<void> {
|
151 | return this.keybindingsChanged.event;
|
152 | }
|
153 |
|
154 | |
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 | protected registerContext(...contexts: KeybindingContext[]): void {
|
161 | for (const context of contexts) {
|
162 | const { id } = context;
|
163 | if (this.contexts[id]) {
|
164 | this.logger.error(`A keybinding context with ID ${id} is already registered.`);
|
165 | } else {
|
166 | this.contexts[id] = context;
|
167 | }
|
168 | }
|
169 | }
|
170 |
|
171 | |
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 | registerKeybinding(binding: common.Keybinding): Disposable {
|
179 | return this.doRegisterKeybinding(binding);
|
180 | }
|
181 |
|
182 | |
183 |
|
184 |
|
185 |
|
186 |
|
187 | registerKeybindings(...bindings: common.Keybinding[]): Disposable {
|
188 | return this.doRegisterKeybindings(bindings, KeybindingScope.DEFAULT);
|
189 | }
|
190 |
|
191 | |
192 |
|
193 |
|
194 |
|
195 |
|
196 | unregisterKeybinding(binding: common.Keybinding): void;
|
197 | |
198 |
|
199 |
|
200 |
|
201 |
|
202 | unregisterKeybinding(key: string): void;
|
203 | |
204 |
|
205 |
|
206 |
|
207 | unregisterKeybinding(command: Command): void;
|
208 |
|
209 | unregisterKeybinding(arg: common.Keybinding | string | Command): void {
|
210 | const keymap = this.keymaps[KeybindingScope.DEFAULT];
|
211 | const filter = Command.is(arg)
|
212 | ? ({ command }: common.Keybinding) => command === arg.id
|
213 | : ({ keybinding }: common.Keybinding) => Keybinding.is(arg)
|
214 | ? keybinding === arg.keybinding
|
215 | : keybinding === arg;
|
216 | for (const binding of keymap.filter(filter)) {
|
217 | const idx = keymap.indexOf(binding);
|
218 | if (idx !== -1) {
|
219 | keymap.splice(idx, 1);
|
220 | }
|
221 | }
|
222 | }
|
223 |
|
224 | protected doRegisterKeybindings(bindings: common.Keybinding[], scope: KeybindingScope = KeybindingScope.DEFAULT): Disposable {
|
225 | const toDispose = new DisposableCollection();
|
226 | for (const binding of bindings) {
|
227 | toDispose.push(this.doRegisterKeybinding(binding, scope));
|
228 | }
|
229 | return toDispose;
|
230 | }
|
231 |
|
232 | protected doRegisterKeybinding(binding: common.Keybinding, scope: KeybindingScope = KeybindingScope.DEFAULT): Disposable {
|
233 | try {
|
234 | this.resolveKeybinding(binding);
|
235 | const scoped = Object.assign(binding, { scope });
|
236 | this.insertBindingIntoScope(scoped, scope);
|
237 | return Disposable.create(() => {
|
238 | const index = this.keymaps[scope].indexOf(scoped);
|
239 | if (index !== -1) {
|
240 | this.keymaps[scope].splice(index, 1);
|
241 | }
|
242 | });
|
243 | } catch (error) {
|
244 | this.logger.warn(`Could not register keybinding:\n ${common.Keybinding.stringify(binding)}\n${error}`);
|
245 | return Disposable.NULL;
|
246 | }
|
247 | }
|
248 |
|
249 | |
250 |
|
251 |
|
252 |
|
253 | protected insertBindingIntoScope(item: common.Keybinding & { scope: KeybindingScope; }, scope: KeybindingScope): void {
|
254 | const scopedKeymap = this.keymaps[scope];
|
255 | const getNumberOfKeystrokes = (binding: common.Keybinding): number => (binding.keybinding.trim().match(/\s/g)?.length ?? 0) + 1;
|
256 | const numberOfKeystrokesInBinding = getNumberOfKeystrokes(item);
|
257 | const indexOfFirstItemWithEqualStrokes = scopedKeymap.findIndex(existingBinding => getNumberOfKeystrokes(existingBinding) === numberOfKeystrokesInBinding);
|
258 | if (indexOfFirstItemWithEqualStrokes > -1) {
|
259 | scopedKeymap.splice(indexOfFirstItemWithEqualStrokes, 0, item);
|
260 | } else {
|
261 | scopedKeymap.push(item);
|
262 | }
|
263 | }
|
264 |
|
265 | |
266 |
|
267 |
|
268 | resolveKeybinding(binding: ResolvedKeybinding): KeyCode[] {
|
269 | if (!binding.resolved) {
|
270 | const sequence = KeySequence.parse(binding.keybinding);
|
271 | binding.resolved = sequence.map(code => this.keyboardLayoutService.resolveKeyCode(code));
|
272 | }
|
273 | return binding.resolved;
|
274 | }
|
275 |
|
276 | |
277 |
|
278 |
|
279 |
|
280 | protected clearResolvedKeybindings(): void {
|
281 | for (let i = KeybindingScope.DEFAULT; i < KeybindingScope.END; i++) {
|
282 | const bindings = this.keymaps[i];
|
283 | for (let j = 0; j < bindings.length; j++) {
|
284 | const binding = bindings[j] as ResolvedKeybinding;
|
285 | binding.resolved = undefined;
|
286 | }
|
287 | }
|
288 | }
|
289 |
|
290 | |
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 | containsKeybindingInScope(binding: common.Keybinding, scope = KeybindingScope.USER): boolean {
|
297 | const bindingKeySequence = this.resolveKeybinding(binding);
|
298 | const collisions = this.getKeySequenceCollisions(this.getUsableBindings(this.keymaps[scope]), bindingKeySequence)
|
299 | .filter(b => b.context === binding.context && !b.when && !binding.when);
|
300 | if (collisions.full.length > 0) {
|
301 | return true;
|
302 | }
|
303 | if (collisions.partial.length > 0) {
|
304 | return true;
|
305 | }
|
306 | if (collisions.shadow.length > 0) {
|
307 | return true;
|
308 | }
|
309 | return false;
|
310 | }
|
311 |
|
312 | |
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 | acceleratorFor(keybinding: common.Keybinding, separator: string = ' ', asciiOnly = false): string[] {
|
319 | const bindingKeySequence = this.resolveKeybinding(keybinding);
|
320 | return this.acceleratorForSequence(bindingKeySequence, separator, asciiOnly);
|
321 | }
|
322 |
|
323 | |
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 | acceleratorForSequence(keySequence: KeySequence, separator: string = ' ', asciiOnly = false): string[] {
|
330 | return keySequence.map(keyCode => this.acceleratorForKeyCode(keyCode, separator, asciiOnly));
|
331 | }
|
332 |
|
333 | |
334 |
|
335 |
|
336 |
|
337 |
|
338 |
|
339 |
|
340 | acceleratorForKeyCode(keyCode: KeyCode, separator: string = ' ', asciiOnly = false): string {
|
341 | return this.componentsForKeyCode(keyCode, asciiOnly).join(separator);
|
342 | }
|
343 |
|
344 | componentsForKeyCode(keyCode: KeyCode, asciiOnly = false): string[] {
|
345 | const keyCodeResult = [];
|
346 | const useSymbols = isOSX && !asciiOnly;
|
347 | if (keyCode.meta && isOSX) {
|
348 | keyCodeResult.push(useSymbols ? '⌘' : 'Cmd');
|
349 | }
|
350 | if (keyCode.ctrl) {
|
351 | keyCodeResult.push(useSymbols ? '⌃' : 'Ctrl');
|
352 | }
|
353 | if (keyCode.alt) {
|
354 | keyCodeResult.push(useSymbols ? '⌥' : 'Alt');
|
355 | }
|
356 | if (keyCode.shift) {
|
357 | keyCodeResult.push(useSymbols ? '⇧' : 'Shift');
|
358 | }
|
359 | if (keyCode.key) {
|
360 | keyCodeResult.push(this.acceleratorForKey(keyCode.key, asciiOnly));
|
361 | }
|
362 | return keyCodeResult;
|
363 | }
|
364 |
|
365 | |
366 |
|
367 |
|
368 |
|
369 |
|
370 | acceleratorForKey(key: Key, asciiOnly = false): string {
|
371 | if (isOSX && !asciiOnly) {
|
372 | if (key === Key.ARROW_LEFT) {
|
373 | return '←';
|
374 | }
|
375 | if (key === Key.ARROW_RIGHT) {
|
376 | return '→';
|
377 | }
|
378 | if (key === Key.ARROW_UP) {
|
379 | return '↑';
|
380 | }
|
381 | if (key === Key.ARROW_DOWN) {
|
382 | return '↓';
|
383 | }
|
384 | }
|
385 | const keyString = this.keyboardLayoutService.getKeyboardCharacter(key);
|
386 | if (key.keyCode >= Key.KEY_A.keyCode && key.keyCode <= Key.KEY_Z.keyCode ||
|
387 | key.keyCode >= Key.F1.keyCode && key.keyCode <= Key.F24.keyCode) {
|
388 | return keyString.toUpperCase();
|
389 | } else if (keyString.length > 1) {
|
390 | return keyString.charAt(0).toUpperCase() + keyString.slice(1);
|
391 | } else {
|
392 | return keyString;
|
393 | }
|
394 | }
|
395 |
|
396 | |
397 |
|
398 |
|
399 |
|
400 |
|
401 |
|
402 | protected getKeySequenceCollisions(bindings: ScopedKeybinding[], candidate: KeySequence): KeybindingRegistry.KeybindingsResult {
|
403 | const result = new KeybindingRegistry.KeybindingsResult();
|
404 | for (const binding of bindings) {
|
405 | try {
|
406 | const bindingKeySequence = this.resolveKeybinding(binding);
|
407 | const compareResult = KeySequence.compare(candidate, bindingKeySequence);
|
408 | switch (compareResult) {
|
409 | case KeySequence.CompareResult.FULL: {
|
410 | result.full.push(binding);
|
411 | break;
|
412 | }
|
413 | case KeySequence.CompareResult.PARTIAL: {
|
414 | result.partial.push(binding);
|
415 | break;
|
416 | }
|
417 | case KeySequence.CompareResult.SHADOW: {
|
418 | result.shadow.push(binding);
|
419 | break;
|
420 | }
|
421 | }
|
422 | } catch (error) {
|
423 | this.logger.warn(error);
|
424 | }
|
425 | }
|
426 | return result;
|
427 | }
|
428 |
|
429 | |
430 |
|
431 |
|
432 |
|
433 |
|
434 |
|
435 | getKeybindingsForCommand(commandId: string): ScopedKeybinding[] {
|
436 | const result: ScopedKeybinding[] = [];
|
437 | const disabledBindings: ScopedKeybinding[] = [];
|
438 | for (let scope = KeybindingScope.END - 1; scope >= KeybindingScope.DEFAULT; scope--) {
|
439 | this.keymaps[scope].forEach(binding => {
|
440 | if (binding.command?.startsWith('-')) {
|
441 | disabledBindings.push(binding);
|
442 | }
|
443 | const command = this.commandRegistry.getCommand(binding.command);
|
444 | if (command
|
445 | && command.id === commandId
|
446 | && !disabledBindings.some(disabled => common.Keybinding.equals(disabled, { ...binding, command: '-' + binding.command }, false, true))) {
|
447 | result.push({ ...binding, scope });
|
448 | }
|
449 | });
|
450 | }
|
451 | return result;
|
452 | }
|
453 |
|
454 | protected isActive(binding: common.Keybinding): boolean {
|
455 | |
456 |
|
457 | if (this.isPseudoCommand(binding.command)) {
|
458 | return true;
|
459 | }
|
460 |
|
461 | const command = this.commandRegistry.getCommand(binding.command);
|
462 | return !!command && !!this.commandRegistry.getActiveHandler(command.id);
|
463 | }
|
464 |
|
465 | |
466 |
|
467 |
|
468 |
|
469 |
|
470 |
|
471 | protected executeKeyBinding(binding: common.Keybinding, event: KeyboardEvent): void {
|
472 | if (this.isPseudoCommand(binding.command)) {
|
473 |
|
474 | } else {
|
475 | const command = this.commandRegistry.getCommand(binding.command);
|
476 | if (command) {
|
477 | if (this.commandRegistry.isEnabled(binding.command, binding.args)) {
|
478 | this.commandRegistry.executeCommand(binding.command, binding.args)
|
479 | .catch(e => console.error('Failed to execute command:', e));
|
480 | }
|
481 |
|
482 | |
483 |
|
484 | event.preventDefault();
|
485 | event.stopPropagation();
|
486 | }
|
487 | }
|
488 | }
|
489 |
|
490 | |
491 |
|
492 |
|
493 | protected isEnabled(binding: common.Keybinding, event: KeyboardEvent): boolean {
|
494 | const context = binding.context && this.contexts[binding.context];
|
495 | if (context && !context.isEnabled(binding)) {
|
496 | return false;
|
497 | }
|
498 | if (binding.when && !this.whenContextService.match(binding.when, <HTMLElement>event.target)) {
|
499 | return false;
|
500 | }
|
501 | return true;
|
502 | }
|
503 |
|
504 | dispatchCommand(id: string, target?: EventTarget): void {
|
505 | const keybindings = this.getKeybindingsForCommand(id);
|
506 | if (keybindings.length) {
|
507 | for (const keyCode of this.resolveKeybinding(keybindings[0])) {
|
508 | this.dispatchKeyDown(keyCode, target);
|
509 | }
|
510 | }
|
511 | }
|
512 |
|
513 | dispatchKeyDown(input: KeyboardEventInit | KeyCode | string, target: EventTarget = document.activeElement || window): void {
|
514 | const eventInit = this.asKeyboardEventInit(input);
|
515 | const emulatedKeyboardEvent = new KeyboardEvent('keydown', eventInit);
|
516 | target.dispatchEvent(emulatedKeyboardEvent);
|
517 | }
|
518 | protected asKeyboardEventInit(input: KeyboardEventInit | KeyCode | string): KeyboardEventInit & Partial<{ keyCode: number }> {
|
519 | if (typeof input === 'string') {
|
520 | return this.asKeyboardEventInit(KeyCode.createKeyCode(input));
|
521 | }
|
522 | if (input instanceof KeyCode) {
|
523 | return {
|
524 | metaKey: input.meta,
|
525 | shiftKey: input.shift,
|
526 | altKey: input.alt,
|
527 | ctrlKey: input.ctrl,
|
528 | code: input.key && input.key.code,
|
529 | key: (input && input.character) || (input.key && input.key.code),
|
530 | keyCode: input.key && input.key.keyCode
|
531 | };
|
532 | }
|
533 | return input;
|
534 | }
|
535 |
|
536 | registerEventListeners(win: Window): Disposable {
|
537 | |
538 |
|
539 |
|
540 |
|
541 |
|
542 | let inComposition = false;
|
543 | const compositionStart = () => {
|
544 | inComposition = true;
|
545 | };
|
546 | win.document.addEventListener('compositionstart', compositionStart);
|
547 |
|
548 | const compositionEnd = () => {
|
549 | inComposition = false;
|
550 | };
|
551 | win.document.addEventListener('compositionend', compositionEnd);
|
552 |
|
553 | const keydown = (event: KeyboardEvent) => {
|
554 | if (inComposition !== true) {
|
555 | this.run(event);
|
556 | }
|
557 | };
|
558 | win.document.addEventListener('keydown', keydown, true);
|
559 |
|
560 | return Disposable.create(() => {
|
561 | win.document.removeEventListener('compositionstart', compositionStart);
|
562 | win.document.removeEventListener('compositionend', compositionEnd);
|
563 | win.document.removeEventListener('keydown', keydown);
|
564 | });
|
565 | }
|
566 | |
567 |
|
568 |
|
569 | run(event: KeyboardEvent): void {
|
570 | if (event.defaultPrevented) {
|
571 | return;
|
572 | }
|
573 |
|
574 | const eventDispatch = this.corePreferences['keyboard.dispatch'];
|
575 | const keyCode = KeyCode.createKeyCode(event, eventDispatch);
|
576 | |
577 |
|
578 | if (keyCode.isModifierOnly()) {
|
579 | return;
|
580 | }
|
581 |
|
582 | this.keyboardLayoutService.validateKeyCode(keyCode);
|
583 | this.keySequence.push(keyCode);
|
584 | const match = this.matchKeybinding(this.keySequence, event);
|
585 |
|
586 | if (match && match.kind === 'partial') {
|
587 |
|
588 | event.preventDefault();
|
589 | event.stopPropagation();
|
590 |
|
591 | this.statusBar.setElement('keybinding-status', {
|
592 | text: nls.localize('theia/core/keybindingStatus', '{0} was pressed, waiting for more keys', `(${this.acceleratorForSequence(this.keySequence, '+')})`),
|
593 | alignment: StatusBarAlignment.LEFT,
|
594 | priority: 2
|
595 | });
|
596 | } else {
|
597 | if (match && match.kind === 'full') {
|
598 | this.executeKeyBinding(match.binding, event);
|
599 | }
|
600 | this.keySequence = [];
|
601 | this.statusBar.removeElement('keybinding-status');
|
602 | }
|
603 | }
|
604 |
|
605 | |
606 |
|
607 |
|
608 |
|
609 |
|
610 |
|
611 |
|
612 |
|
613 |
|
614 | matchKeybinding(keySequence: KeySequence, event?: KeyboardEvent): KeybindingRegistry.Match {
|
615 | let disabled: Set<string> | undefined;
|
616 | const isEnabled = (binding: ScopedKeybinding) => {
|
617 | if (event && !this.isEnabled(binding, event)) {
|
618 | return false;
|
619 | }
|
620 | const { command, context, when, keybinding } = binding;
|
621 | if (!this.isUsable(binding)) {
|
622 | disabled = disabled || new Set<string>();
|
623 | disabled.add(JSON.stringify({ command: command.substr(1), context, when, keybinding }));
|
624 | return false;
|
625 | }
|
626 | return !disabled?.has(JSON.stringify({ command, context, when, keybinding }));
|
627 | };
|
628 |
|
629 | for (let scope = KeybindingScope.END; --scope >= KeybindingScope.DEFAULT;) {
|
630 | for (const binding of this.keymaps[scope]) {
|
631 | const resolved = this.resolveKeybinding(binding);
|
632 | const compareResult = KeySequence.compare(keySequence, resolved);
|
633 | if (compareResult === KeySequence.CompareResult.FULL && isEnabled(binding)) {
|
634 | return { kind: 'full', binding };
|
635 | }
|
636 | if (compareResult === KeySequence.CompareResult.PARTIAL && isEnabled(binding)) {
|
637 | return { kind: 'partial', binding };
|
638 | }
|
639 | }
|
640 | }
|
641 | return undefined;
|
642 | }
|
643 |
|
644 | |
645 |
|
646 |
|
647 |
|
648 | protected isUsable(binding: common.Keybinding): boolean {
|
649 | return binding.command.charAt(0) !== '-';
|
650 | }
|
651 |
|
652 | |
653 |
|
654 |
|
655 |
|
656 | protected getUsableBindings<T extends common.Keybinding>(bindings: T[]): T[] {
|
657 | return bindings.filter(binding => this.isUsable(binding));
|
658 | }
|
659 |
|
660 | |
661 |
|
662 |
|
663 |
|
664 |
|
665 |
|
666 |
|
667 | isPseudoCommand(commandId: string): boolean {
|
668 | return commandId === KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND;
|
669 | }
|
670 |
|
671 | |
672 |
|
673 |
|
674 |
|
675 |
|
676 | setKeymap(scope: KeybindingScope, bindings: common.Keybinding[]): void {
|
677 | this.resetKeybindingsForScope(scope);
|
678 | this.toResetKeymap.set(scope, this.doRegisterKeybindings(bindings, scope));
|
679 | this.keybindingsChanged.fire(undefined);
|
680 | }
|
681 |
|
682 | protected readonly toResetKeymap = new Map<KeybindingScope, Disposable>();
|
683 |
|
684 | |
685 |
|
686 |
|
687 |
|
688 | resetKeybindingsForScope(scope: KeybindingScope): void {
|
689 | const toReset = this.toResetKeymap.get(scope);
|
690 | if (toReset) {
|
691 | toReset.dispose();
|
692 | }
|
693 | }
|
694 |
|
695 | |
696 |
|
697 |
|
698 | resetKeybindings(): void {
|
699 | for (let i = KeybindingScope.DEFAULT + 1; i < KeybindingScope.END; i++) {
|
700 | this.keymaps[i] = [];
|
701 | }
|
702 | }
|
703 |
|
704 | |
705 |
|
706 |
|
707 |
|
708 |
|
709 | getKeybindingsByScope(scope: KeybindingScope): ScopedKeybinding[] {
|
710 | return this.keymaps[scope];
|
711 | }
|
712 | }
|
713 |
|
714 | export namespace KeybindingRegistry {
|
715 | export type Match = {
|
716 | kind: 'full' | 'partial'
|
717 | binding: ScopedKeybinding
|
718 | } | undefined;
|
719 | export class KeybindingsResult {
|
720 | full: ScopedKeybinding[] = [];
|
721 | partial: ScopedKeybinding[] = [];
|
722 | shadow: ScopedKeybinding[] = [];
|
723 |
|
724 | |
725 |
|
726 |
|
727 |
|
728 |
|
729 |
|
730 | merge(other: KeybindingsResult): KeybindingsResult {
|
731 | this.full.push(...other.full);
|
732 | this.partial.push(...other.partial);
|
733 | this.shadow.push(...other.shadow);
|
734 | return this;
|
735 | }
|
736 |
|
737 | |
738 |
|
739 |
|
740 |
|
741 |
|
742 |
|
743 | filter(fn: (binding: common.Keybinding) => boolean): KeybindingsResult {
|
744 | const result = new KeybindingsResult();
|
745 | result.full = this.full.filter(fn);
|
746 | result.partial = this.partial.filter(fn);
|
747 | result.shadow = this.shadow.filter(fn);
|
748 | return result;
|
749 | }
|
750 | }
|
751 | }
|