25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

124 lines
3.9 KiB

  1. 'use strict';
  2. import { commands, Disposable } from 'vscode';
  3. import { CommandContext, extensionId, setCommandContext } from './constants';
  4. import { Logger } from './logger';
  5. export declare interface KeyCommand {
  6. onDidPressKey?(key: Keys): Promise<{} | undefined>;
  7. }
  8. const keyNoopCommand = Object.create(null) as KeyCommand;
  9. export { keyNoopCommand as KeyNoopCommand };
  10. export declare type Keys = 'left' | 'right' | ',' | '.' | 'escape';
  11. export const keys: Keys[] = ['left', 'right', ',', '.', 'escape'];
  12. export declare interface KeyMapping {
  13. [id: string]: KeyCommand | (() => Promise<KeyCommand>) | undefined;
  14. }
  15. const mappings: KeyMapping[] = [];
  16. export class KeyboardScope implements Disposable {
  17. constructor(
  18. private readonly mapping: KeyMapping
  19. ) {
  20. for (const key in mapping) {
  21. mapping[key] = mapping[key] || keyNoopCommand;
  22. }
  23. }
  24. async dispose() {
  25. const index = mappings.indexOf(this.mapping);
  26. Logger.log('KeyboardScope.dispose', mappings.length, index);
  27. if (index === mappings.length - 1) {
  28. mappings.pop();
  29. await this.updateKeyCommandsContext(mappings[mappings.length - 1]);
  30. }
  31. else {
  32. mappings.splice(index, 1);
  33. }
  34. }
  35. async begin() {
  36. mappings.push(this.mapping);
  37. await this.updateKeyCommandsContext(this.mapping);
  38. return this;
  39. }
  40. async clearKeyCommand(key: Keys) {
  41. const mapping = mappings[mappings.length - 1];
  42. if (mapping !== this.mapping || !mapping[key]) return;
  43. Logger.log('KeyboardScope.clearKeyCommand', mappings.length, key);
  44. mapping[key] = undefined;
  45. await setCommandContext(`${CommandContext.Key}:${key}`, false);
  46. }
  47. async setKeyCommand(key: Keys, command: KeyCommand | (() => Promise<KeyCommand>)) {
  48. const mapping = mappings[mappings.length - 1];
  49. if (mapping !== this.mapping) return;
  50. Logger.log('KeyboardScope.setKeyCommand', mappings.length, key, Boolean(mapping[key]));
  51. if (!mapping[key]) {
  52. mapping[key] = command;
  53. await setCommandContext(`${CommandContext.Key}:${key}`, true);
  54. }
  55. else {
  56. mapping[key] = command;
  57. }
  58. }
  59. private async updateKeyCommandsContext(mapping: KeyMapping) {
  60. const promises = [];
  61. for (const key of keys) {
  62. promises.push(setCommandContext(`${CommandContext.Key}:${key}`, Boolean(mapping && mapping[key])));
  63. }
  64. await Promise.all(promises);
  65. }
  66. }
  67. export class Keyboard implements Disposable {
  68. private _disposable: Disposable;
  69. constructor() {
  70. const subscriptions = keys.map(key =>
  71. commands.registerCommand(`${extensionId}.key.${key}`, () => this.execute(key), this)
  72. );
  73. this._disposable = Disposable.from(...subscriptions);
  74. }
  75. dispose() {
  76. this._disposable && this._disposable.dispose();
  77. }
  78. async beginScope(mapping?: KeyMapping): Promise<KeyboardScope> {
  79. Logger.log('Keyboard.beginScope', mappings.length);
  80. return await new KeyboardScope(
  81. mapping ? Object.assign(Object.create(null), mapping) : Object.create(null)
  82. ).begin();
  83. }
  84. async execute(key: Keys): Promise<{} | undefined> {
  85. if (!mappings.length) return undefined;
  86. try {
  87. const mapping = mappings[mappings.length - 1];
  88. let command = mapping[key] as KeyCommand | (() => Promise<KeyCommand>);
  89. if (typeof command === 'function') {
  90. command = await command();
  91. }
  92. if (!command || typeof command.onDidPressKey !== 'function') return undefined;
  93. Logger.log('Keyboard.execute', key);
  94. return await command.onDidPressKey(key);
  95. }
  96. catch (ex) {
  97. Logger.error(ex, 'Keyboard.execute');
  98. return undefined;
  99. }
  100. }
  101. }