You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

142 lines
4.1 KiB

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