Files
code-server/lib/vscode/src/vs/platform/commands/common/commands.ts
2021-04-09 11:32:27 +05:30

151 lines
4.5 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle';
import { TypeConstraint, validateConstraints } from 'vs/base/common/types';
import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event, Emitter } from 'vs/base/common/event';
import { LinkedList } from 'vs/base/common/linkedList';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { Iterable } from 'vs/base/common/iterator';
export const ICommandService = createDecorator<ICommandService>('commandService');
export interface ICommandEvent {
commandId: string;
args: any[];
}
export interface ICommandService {
readonly _serviceBrand: undefined;
onWillExecuteCommand: Event<ICommandEvent>;
onDidExecuteCommand: Event<ICommandEvent>;
executeCommand<T = any>(commandId: string, ...args: any[]): Promise<T | undefined>;
}
export type ICommandsMap = Map<string, ICommand>;
export interface ICommandHandler {
(accessor: ServicesAccessor, ...args: any[]): void;
}
export interface ICommand {
id: string;
handler: ICommandHandler;
description?: ICommandHandlerDescription | null;
}
export interface ICommandHandlerDescription {
readonly description: string;
readonly args: ReadonlyArray<{
readonly name: string;
readonly isOptional?: boolean;
readonly description?: string;
readonly constraint?: TypeConstraint;
readonly schema?: IJSONSchema;
}>;
readonly returns?: string;
}
export interface ICommandRegistry {
onDidRegisterCommand: Event<string>;
registerCommand(id: string, command: ICommandHandler): IDisposable;
registerCommand(command: ICommand): IDisposable;
registerCommandAlias(oldId: string, newId: string): IDisposable;
getCommand(id: string): ICommand | undefined;
getCommands(): ICommandsMap;
}
export const CommandsRegistry: ICommandRegistry = new class implements ICommandRegistry {
private readonly _commands = new Map<string, LinkedList<ICommand>>();
private readonly _onDidRegisterCommand = new Emitter<string>();
readonly onDidRegisterCommand: Event<string> = this._onDidRegisterCommand.event;
registerCommand(idOrCommand: string | ICommand, handler?: ICommandHandler): IDisposable {
if (!idOrCommand) {
throw new Error(`invalid command`);
}
if (typeof idOrCommand === 'string') {
if (!handler) {
throw new Error(`invalid command`);
}
return this.registerCommand({ id: idOrCommand, handler });
}
// add argument validation if rich command metadata is provided
if (idOrCommand.description) {
const constraints: Array<TypeConstraint | undefined> = [];
for (let arg of idOrCommand.description.args) {
constraints.push(arg.constraint);
}
const actualHandler = idOrCommand.handler;
idOrCommand.handler = function (accessor, ...args: any[]) {
validateConstraints(args, constraints);
return actualHandler(accessor, ...args);
};
}
// find a place to store the command
const { id } = idOrCommand;
let commands = this._commands.get(id);
if (!commands) {
commands = new LinkedList<ICommand>();
this._commands.set(id, commands);
}
let removeFn = commands.unshift(idOrCommand);
let ret = toDisposable(() => {
removeFn();
const command = this._commands.get(id);
if (command?.isEmpty()) {
this._commands.delete(id);
}
});
// tell the world about this command
this._onDidRegisterCommand.fire(id);
return ret;
}
registerCommandAlias(oldId: string, newId: string): IDisposable {
return CommandsRegistry.registerCommand(oldId, (accessor, ...args) => accessor.get(ICommandService).executeCommand(newId, ...args));
}
getCommand(id: string): ICommand | undefined {
const list = this._commands.get(id);
if (!list || list.isEmpty()) {
return undefined;
}
return Iterable.first(list);
}
getCommands(): ICommandsMap {
const result = new Map<string, ICommand>();
for (const key of this._commands.keys()) {
const command = this.getCommand(key);
if (command) {
result.set(key, command);
}
}
return result;
}
};
export const NullCommandService: ICommandService = {
_serviceBrand: undefined,
onWillExecuteCommand: () => Disposable.None,
onDidExecuteCommand: () => Disposable.None,
executeCommand() {
return Promise.resolve(undefined);
}
};