- import { commands, Disposable } from 'vscode';
- import { ContextKeys, setContext } from './constants';
- import { Logger } from './logger';
- import { log } from './system/decorators/log';
-
- export declare interface KeyCommand {
- onDidPressKey?(key: Keys): void | Promise<void>;
- }
-
- const keyNoopCommand = Object.create(null) as KeyCommand;
- export { keyNoopCommand as KeyNoopCommand };
-
- export const keys = [
- 'left',
- 'alt+left',
- 'ctrl+left',
- 'right',
- 'alt+right',
- 'ctrl+right',
- 'alt+,',
- 'alt+.',
- 'escape',
- ] as const;
- export type Keys = typeof keys[number];
-
- export type KeyMapping = { [K in Keys]?: KeyCommand | (() => Promise<KeyCommand>) };
- type IndexableKeyMapping = KeyMapping & {
- [index: string]: KeyCommand | (() => Promise<KeyCommand>) | undefined;
- };
-
- const mappings: KeyMapping[] = [];
-
- export class KeyboardScope implements Disposable {
- private readonly _mapping: IndexableKeyMapping;
- constructor(mapping: KeyMapping) {
- this._mapping = mapping;
- for (const key in this._mapping) {
- this._mapping[key] = this._mapping[key] ?? keyNoopCommand;
- }
-
- mappings.push(this._mapping);
- }
-
- @log({
- args: false,
- prefix: context => `${context.prefix}[${mappings.length}]`,
- })
- async dispose() {
- const index = mappings.indexOf(this._mapping);
-
- const cc = Logger.getCorrelationContext();
- if (cc != null) {
- cc.exitDetails = ` \u2022 index=${index}`;
- }
-
- if (index === mappings.length - 1) {
- mappings.pop();
- await this.updateKeyCommandsContext(mappings[mappings.length - 1]);
- } else {
- mappings.splice(index, 1);
- }
- }
-
- private _paused = true;
- get paused() {
- return this._paused;
- }
-
- @log<KeyboardScope['clearKeyCommand']>({
- args: false,
- prefix: (context, key) => `${context.prefix}[${mappings.length}](${key})`,
- })
- async clearKeyCommand(key: Keys) {
- const cc = Logger.getCorrelationContext();
-
- const mapping = mappings[mappings.length - 1];
- if (mapping !== this._mapping || mapping[key] == null) {
- if (cc != null) {
- cc.exitDetails = ' \u2022 skipped';
- }
-
- return;
- }
-
- mapping[key] = undefined;
- await setContext(`${ContextKeys.Key}:${key}`, false);
- }
-
- @log({
- args: false,
- prefix: context => `${context.prefix}(paused=${context.instance._paused})`,
- })
- async pause(keys?: Keys[]) {
- if (this._paused) return;
-
- this._paused = true;
- const mapping = (Object.keys(this._mapping) as Keys[]).reduce((accumulator, key) => {
- accumulator[key] = keys == null || keys.includes(key) ? undefined : this._mapping[key];
- return accumulator;
- }, Object.create(null) as KeyMapping);
-
- await this.updateKeyCommandsContext(mapping);
- }
-
- @log({
- args: false,
- prefix: context => `${context.prefix}(paused=${context.instance._paused})`,
- })
- async resume() {
- if (!this._paused) return;
-
- this._paused = false;
- await this.updateKeyCommandsContext(this._mapping);
- }
-
- async start() {
- await this.resume();
- }
-
- @log<KeyboardScope['setKeyCommand']>({
- args: false,
- prefix: (context, key) => `${context.prefix}[${mappings.length}](${key})`,
- })
- async setKeyCommand(key: Keys, command: KeyCommand | (() => Promise<KeyCommand>)) {
- const cc = Logger.getCorrelationContext();
-
- const mapping = mappings[mappings.length - 1];
- if (mapping !== this._mapping) {
- if (cc != null) {
- cc.exitDetails = ' \u2022 skipped';
- }
-
- return;
- }
-
- const set = Boolean(mapping[key]);
-
- mapping[key] = command;
- if (!set) {
- await setContext(`${ContextKeys.Key}:${key}`, true);
- }
- }
-
- private async updateKeyCommandsContext(mapping: KeyMapping) {
- await Promise.all(keys.map(key => setContext(`${ContextKeys.Key}:${key}`, Boolean(mapping?.[key]))));
- }
- }
-
- export class Keyboard implements Disposable {
- private readonly _disposable: Disposable;
-
- constructor() {
- const subscriptions = keys.map(key =>
- commands.registerCommand(`gitlens.key.${key}`, () => this.execute(key), this),
- );
- this._disposable = Disposable.from(...subscriptions);
- }
-
- dispose() {
- this._disposable.dispose();
- }
-
- @log<Keyboard['createScope']>({
- args: false,
- prefix: (context, mapping) =>
- `${context.prefix}[${mappings.length}](${mapping === undefined ? '' : Object.keys(mapping).join(',')})`,
- })
- createScope(mapping?: KeyMapping): KeyboardScope {
- return new KeyboardScope({ ...mapping });
- }
-
- @log<Keyboard['beginScope']>({
- args: false,
- prefix: (context, mapping) =>
- `${context.prefix}[${mappings.length}](${mapping === undefined ? '' : Object.keys(mapping).join(',')})`,
- })
- async beginScope(mapping?: KeyMapping): Promise<KeyboardScope> {
- const scope = this.createScope(mapping);
- await scope.start();
- return scope;
- }
-
- @log()
- async execute(key: Keys): Promise<void> {
- const cc = Logger.getCorrelationContext();
-
- if (!mappings.length) {
- if (cc != null) {
- cc.exitDetails = ' \u2022 skipped, no mappings';
- }
-
- return;
- }
-
- try {
- const mapping = mappings[mappings.length - 1];
-
- let command = mapping[key] as KeyCommand | (() => Promise<KeyCommand>);
- if (typeof command === 'function') {
- command = await command();
- }
- if (typeof command?.onDidPressKey !== 'function') {
- if (cc != null) {
- cc.exitDetails = ' \u2022 skipped, no callback';
- }
-
- return;
- }
-
- void (await command.onDidPressKey(key));
- } catch (ex) {
- Logger.error(ex, cc);
- }
- }
- }
|