您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

216 行
5.2 KiB

5 年前
5 年前
  1. 'use strict';
  2. import { commands, Disposable } from 'vscode';
  3. import { ContextKeys, extensionId, setContext } from './constants';
  4. import { Logger } from './logger';
  5. import { log } from './system';
  6. export declare interface KeyCommand {
  7. onDidPressKey?(key: Keys): void | Promise<void>;
  8. }
  9. const keyNoopCommand = Object.create(null) as KeyCommand;
  10. export { keyNoopCommand as KeyNoopCommand };
  11. export const keys = [
  12. 'left',
  13. 'alt+left',
  14. 'ctrl+left',
  15. 'right',
  16. 'alt+right',
  17. 'ctrl+right',
  18. 'alt+,',
  19. 'alt+.',
  20. 'escape',
  21. ] as const;
  22. export type Keys = typeof keys[number];
  23. export type KeyMapping = { [K in Keys]?: KeyCommand | (() => Promise<KeyCommand>) };
  24. type IndexableKeyMapping = KeyMapping & {
  25. [index: string]: KeyCommand | (() => Promise<KeyCommand>) | undefined;
  26. };
  27. const mappings: KeyMapping[] = [];
  28. export class KeyboardScope implements Disposable {
  29. private readonly _mapping: IndexableKeyMapping;
  30. constructor(mapping: KeyMapping) {
  31. this._mapping = mapping;
  32. for (const key in this._mapping) {
  33. this._mapping[key] = this._mapping[key] ?? keyNoopCommand;
  34. }
  35. mappings.push(this._mapping);
  36. }
  37. @log({
  38. args: false,
  39. prefix: context => `${context.prefix}[${mappings.length}]`,
  40. })
  41. async dispose() {
  42. const index = mappings.indexOf(this._mapping);
  43. const cc = Logger.getCorrelationContext();
  44. if (cc != null) {
  45. cc.exitDetails = ` \u2022 index=${index}`;
  46. }
  47. if (index === mappings.length - 1) {
  48. mappings.pop();
  49. await this.updateKeyCommandsContext(mappings[mappings.length - 1]);
  50. } else {
  51. mappings.splice(index, 1);
  52. }
  53. }
  54. private _paused = true;
  55. get paused() {
  56. return this._paused;
  57. }
  58. @log<KeyboardScope['clearKeyCommand']>({
  59. args: false,
  60. prefix: (context, key) => `${context.prefix}[${mappings.length}](${key})`,
  61. })
  62. async clearKeyCommand(key: Keys) {
  63. const cc = Logger.getCorrelationContext();
  64. const mapping = mappings[mappings.length - 1];
  65. if (mapping !== this._mapping || mapping[key] == null) {
  66. if (cc != null) {
  67. cc.exitDetails = ' \u2022 skipped';
  68. }
  69. return;
  70. }
  71. mapping[key] = undefined;
  72. await setContext(`${ContextKeys.Key}:${key}`, false);
  73. }
  74. @log({
  75. args: false,
  76. prefix: context => `${context.prefix}(paused=${context.instance._paused})`,
  77. })
  78. async pause(keys?: Keys[]) {
  79. if (this._paused) return;
  80. this._paused = true;
  81. const mapping = (Object.keys(this._mapping) as Keys[]).reduce((accumulator, key) => {
  82. accumulator[key] = keys == null || keys.includes(key) ? undefined : this._mapping[key];
  83. return accumulator;
  84. }, Object.create(null) as KeyMapping);
  85. await this.updateKeyCommandsContext(mapping);
  86. }
  87. @log({
  88. args: false,
  89. prefix: context => `${context.prefix}(paused=${context.instance._paused})`,
  90. })
  91. async resume() {
  92. if (!this._paused) return;
  93. this._paused = false;
  94. await this.updateKeyCommandsContext(this._mapping);
  95. }
  96. async start() {
  97. await this.resume();
  98. }
  99. @log<KeyboardScope['setKeyCommand']>({
  100. args: false,
  101. prefix: (context, key) => `${context.prefix}[${mappings.length}](${key})`,
  102. })
  103. async setKeyCommand(key: Keys, command: KeyCommand | (() => Promise<KeyCommand>)) {
  104. const cc = Logger.getCorrelationContext();
  105. const mapping = mappings[mappings.length - 1];
  106. if (mapping !== this._mapping) {
  107. if (cc != null) {
  108. cc.exitDetails = ' \u2022 skipped';
  109. }
  110. return;
  111. }
  112. const set = Boolean(mapping[key]);
  113. mapping[key] = command;
  114. if (!set) {
  115. await setContext(`${ContextKeys.Key}:${key}`, true);
  116. }
  117. }
  118. private async updateKeyCommandsContext(mapping: KeyMapping) {
  119. await Promise.all(keys.map(key => setContext(`${ContextKeys.Key}:${key}`, Boolean(mapping?.[key]))));
  120. }
  121. }
  122. export class Keyboard implements Disposable {
  123. private readonly _disposable: Disposable;
  124. constructor() {
  125. const subscriptions = keys.map(key =>
  126. commands.registerCommand(`${extensionId}.key.${key}`, () => this.execute(key), this),
  127. );
  128. this._disposable = Disposable.from(...subscriptions);
  129. }
  130. dispose() {
  131. this._disposable.dispose();
  132. }
  133. @log<Keyboard['createScope']>({
  134. args: false,
  135. prefix: (context, mapping) =>
  136. `${context.prefix}[${mappings.length}](${mapping === undefined ? '' : Object.keys(mapping).join(',')})`,
  137. })
  138. createScope(mapping?: KeyMapping): KeyboardScope {
  139. return new KeyboardScope({ ...mapping });
  140. }
  141. @log<Keyboard['beginScope']>({
  142. args: false,
  143. prefix: (context, mapping) =>
  144. `${context.prefix}[${mappings.length}](${mapping === undefined ? '' : Object.keys(mapping).join(',')})`,
  145. })
  146. async beginScope(mapping?: KeyMapping): Promise<KeyboardScope> {
  147. const scope = this.createScope(mapping);
  148. await scope.start();
  149. return scope;
  150. }
  151. @log()
  152. async execute(key: Keys): Promise<void> {
  153. const cc = Logger.getCorrelationContext();
  154. if (!mappings.length) {
  155. if (cc != null) {
  156. cc.exitDetails = ' \u2022 skipped, no mappings';
  157. }
  158. return;
  159. }
  160. try {
  161. const mapping = mappings[mappings.length - 1];
  162. let command = mapping[key] as KeyCommand | (() => Promise<KeyCommand>);
  163. if (typeof command === 'function') {
  164. command = await command();
  165. }
  166. if (typeof command?.onDidPressKey !== 'function') {
  167. if (cc != null) {
  168. cc.exitDetails = ' \u2022 skipped, no callback';
  169. }
  170. return;
  171. }
  172. void (await command.onDidPressKey(key));
  173. } catch (ex) {
  174. Logger.error(ex, cc);
  175. }
  176. }
  177. }