Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/commands/common/commands.ts
5263 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Emitter, Event } from '../../../base/common/event.js';
7
import { Iterable } from '../../../base/common/iterator.js';
8
import { IJSONSchema } from '../../../base/common/jsonSchema.js';
9
import { IDisposable, markAsSingleton, toDisposable } from '../../../base/common/lifecycle.js';
10
import { LinkedList } from '../../../base/common/linkedList.js';
11
import { TypeConstraint, validateConstraints } from '../../../base/common/types.js';
12
import { ILocalizedString } from '../../action/common/action.js';
13
import { createDecorator, ServicesAccessor } from '../../instantiation/common/instantiation.js';
14
15
export const ICommandService = createDecorator<ICommandService>('commandService');
16
17
export interface ICommandEvent {
18
readonly commandId: string;
19
readonly args: unknown[];
20
}
21
22
export interface ICommandService {
23
readonly _serviceBrand: undefined;
24
readonly onWillExecuteCommand: Event<ICommandEvent>;
25
readonly onDidExecuteCommand: Event<ICommandEvent>;
26
executeCommand<R = unknown>(commandId: string, ...args: unknown[]): Promise<R | undefined>;
27
}
28
29
export type ICommandsMap = Map<string, ICommand>;
30
31
export type ICommandHandler<Args extends unknown[] = unknown[], R = void> = (accessor: ServicesAccessor, ...args: Args) => R;
32
33
export interface ICommand<Args extends unknown[] = unknown[], R = void> {
34
id: string;
35
handler: ICommandHandler<Args, R>;
36
metadata?: ICommandMetadata | null;
37
}
38
39
export interface ICommandMetadata {
40
/**
41
* NOTE: Please use an ILocalizedString. string is in the type for backcompat for now.
42
* A short summary of what the command does. This will be used in:
43
* - API commands
44
* - when showing keybindings that have no other UX
45
* - when searching for commands in the Command Palette
46
*/
47
readonly description: ILocalizedString | string;
48
readonly args?: ReadonlyArray<{
49
readonly name: string;
50
readonly isOptional?: boolean;
51
readonly description?: string;
52
readonly constraint?: TypeConstraint;
53
readonly schema?: IJSONSchema;
54
}>;
55
readonly returns?: string;
56
}
57
58
export interface ICommandRegistry {
59
readonly onDidRegisterCommand: Event<string>;
60
registerCommand<Args extends unknown[]>(id: string, command: ICommandHandler<Args>): IDisposable;
61
registerCommand<Args extends unknown[]>(command: ICommand<Args>): IDisposable;
62
registerCommandAlias(oldId: string, newId: string): IDisposable;
63
getCommand(id: string): ICommand | undefined;
64
getCommands(): ICommandsMap;
65
}
66
67
export const CommandsRegistry: ICommandRegistry = new class implements ICommandRegistry {
68
69
private readonly _commands = new Map<string, LinkedList<ICommand>>();
70
71
private readonly _onDidRegisterCommand = new Emitter<string>();
72
readonly onDidRegisterCommand: Event<string> = this._onDidRegisterCommand.event;
73
74
registerCommand(idOrCommand: string | ICommand, handler?: ICommandHandler): IDisposable {
75
76
if (!idOrCommand) {
77
throw new Error(`invalid command`);
78
}
79
80
if (typeof idOrCommand === 'string') {
81
if (!handler) {
82
throw new Error(`invalid command`);
83
}
84
return this.registerCommand({ id: idOrCommand, handler });
85
}
86
87
// add argument validation if rich command metadata is provided
88
if (idOrCommand.metadata && Array.isArray(idOrCommand.metadata.args)) {
89
const constraints: Array<TypeConstraint | undefined> = [];
90
for (const arg of idOrCommand.metadata.args) {
91
constraints.push(arg.constraint);
92
}
93
const actualHandler = idOrCommand.handler;
94
idOrCommand.handler = function (accessor, ...args: unknown[]) {
95
validateConstraints(args, constraints);
96
return actualHandler(accessor, ...args);
97
};
98
}
99
100
// find a place to store the command
101
const { id } = idOrCommand;
102
103
let commands = this._commands.get(id);
104
if (!commands) {
105
commands = new LinkedList<ICommand>();
106
this._commands.set(id, commands);
107
}
108
109
const removeFn = commands.unshift(idOrCommand);
110
111
const ret = toDisposable(() => {
112
removeFn();
113
const command = this._commands.get(id);
114
if (command?.isEmpty()) {
115
this._commands.delete(id);
116
}
117
});
118
119
// tell the world about this command
120
this._onDidRegisterCommand.fire(id);
121
122
return markAsSingleton(ret);
123
}
124
125
registerCommandAlias(oldId: string, newId: string): IDisposable {
126
return CommandsRegistry.registerCommand(oldId, (accessor, ...args) => accessor.get(ICommandService).executeCommand(newId, ...args));
127
}
128
129
getCommand(id: string): ICommand | undefined {
130
const list = this._commands.get(id);
131
if (!list || list.isEmpty()) {
132
return undefined;
133
}
134
return Iterable.first(list);
135
}
136
137
getCommands(): ICommandsMap {
138
const result = new Map<string, ICommand>();
139
for (const key of this._commands.keys()) {
140
const command = this.getCommand(key);
141
if (command) {
142
result.set(key, command);
143
}
144
}
145
return result;
146
}
147
};
148
149
CommandsRegistry.registerCommand('noop', () => { });
150
151