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