/*--------------------------------------------------------------------------------------------- * 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('commandService'); export interface ICommandEvent { commandId: string; args: any[]; } export interface ICommandService { readonly _serviceBrand: undefined; onWillExecuteCommand: Event; onDidExecuteCommand: Event; executeCommand(commandId: string, ...args: any[]): Promise; } export type ICommandsMap = Map; 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; 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>(); private readonly _onDidRegisterCommand = new Emitter(); readonly onDidRegisterCommand: Event = 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 = []; 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(); 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(); 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); } };