Path: blob/main/src/vs/platform/commands/common/commands.ts
3294 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Emitter, Event } from '../../../base/common/event.js';6import { Iterable } from '../../../base/common/iterator.js';7import { IJSONSchema } from '../../../base/common/jsonSchema.js';8import { IDisposable, markAsSingleton, toDisposable } from '../../../base/common/lifecycle.js';9import { LinkedList } from '../../../base/common/linkedList.js';10import { TypeConstraint, validateConstraints } from '../../../base/common/types.js';11import { ILocalizedString } from '../../action/common/action.js';12import { createDecorator, ServicesAccessor } from '../../instantiation/common/instantiation.js';1314export const ICommandService = createDecorator<ICommandService>('commandService');1516export interface ICommandEvent {17commandId: string;18args: any[];19}2021export interface ICommandService {22readonly _serviceBrand: undefined;23onWillExecuteCommand: Event<ICommandEvent>;24onDidExecuteCommand: Event<ICommandEvent>;25executeCommand<T = any>(commandId: string, ...args: any[]): Promise<T | undefined>;26}2728export type ICommandsMap = Map<string, ICommand>;2930export interface ICommandHandler {31(accessor: ServicesAccessor, ...args: any[]): void;32}3334export interface ICommand {35id: string;36handler: ICommandHandler;37metadata?: ICommandMetadata | null;38}3940export interface ICommandMetadata {41/**42* NOTE: Please use an ILocalizedString. string is in the type for backcompat for now.43* A short summary of what the command does. This will be used in:44* - API commands45* - when showing keybindings that have no other UX46* - when searching for commands in the Command Palette47*/48readonly description: ILocalizedString | string;49readonly args?: ReadonlyArray<{50readonly name: string;51readonly isOptional?: boolean;52readonly description?: string;53readonly constraint?: TypeConstraint;54readonly schema?: IJSONSchema;55}>;56readonly returns?: string;57}5859export interface ICommandRegistry {60onDidRegisterCommand: Event<string>;61registerCommand(id: string, command: ICommandHandler): IDisposable;62registerCommand(command: ICommand): IDisposable;63registerCommandAlias(oldId: string, newId: string): IDisposable;64getCommand(id: string): ICommand | undefined;65getCommands(): ICommandsMap;66}6768export const CommandsRegistry: ICommandRegistry = new class implements ICommandRegistry {6970private readonly _commands = new Map<string, LinkedList<ICommand>>();7172private readonly _onDidRegisterCommand = new Emitter<string>();73readonly onDidRegisterCommand: Event<string> = this._onDidRegisterCommand.event;7475registerCommand(idOrCommand: string | ICommand, handler?: ICommandHandler): IDisposable {7677if (!idOrCommand) {78throw new Error(`invalid command`);79}8081if (typeof idOrCommand === 'string') {82if (!handler) {83throw new Error(`invalid command`);84}85return this.registerCommand({ id: idOrCommand, handler });86}8788// add argument validation if rich command metadata is provided89if (idOrCommand.metadata && Array.isArray(idOrCommand.metadata.args)) {90const constraints: Array<TypeConstraint | undefined> = [];91for (const arg of idOrCommand.metadata.args) {92constraints.push(arg.constraint);93}94const actualHandler = idOrCommand.handler;95idOrCommand.handler = function (accessor, ...args: any[]) {96validateConstraints(args, constraints);97return actualHandler(accessor, ...args);98};99}100101// find a place to store the command102const { id } = idOrCommand;103104let commands = this._commands.get(id);105if (!commands) {106commands = new LinkedList<ICommand>();107this._commands.set(id, commands);108}109110const removeFn = commands.unshift(idOrCommand);111112const ret = toDisposable(() => {113removeFn();114const command = this._commands.get(id);115if (command?.isEmpty()) {116this._commands.delete(id);117}118});119120// tell the world about this command121this._onDidRegisterCommand.fire(id);122123return markAsSingleton(ret);124}125126registerCommandAlias(oldId: string, newId: string): IDisposable {127return CommandsRegistry.registerCommand(oldId, (accessor, ...args) => accessor.get(ICommandService).executeCommand(newId, ...args));128}129130getCommand(id: string): ICommand | undefined {131const list = this._commands.get(id);132if (!list || list.isEmpty()) {133return undefined;134}135return Iterable.first(list);136}137138getCommands(): ICommandsMap {139const result = new Map<string, ICommand>();140for (const key of this._commands.keys()) {141const command = this.getCommand(key);142if (command) {143result.set(key, command);144}145}146return result;147}148};149150CommandsRegistry.registerCommand('noop', () => { });151152153