Path: blob/main/src/vs/workbench/services/commands/common/commandService.ts
3296 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 { CancelablePromise, notCancellablePromise, raceCancellablePromises, timeout } from '../../../../base/common/async.js';6import { Emitter, Event } from '../../../../base/common/event.js';7import { Disposable } from '../../../../base/common/lifecycle.js';8import { CommandsRegistry, ICommandEvent, ICommandService } from '../../../../platform/commands/common/commands.js';9import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';10import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';11import { ILogService } from '../../../../platform/log/common/log.js';12import { IExtensionService } from '../../extensions/common/extensions.js';1314export class CommandService extends Disposable implements ICommandService {1516declare readonly _serviceBrand: undefined;1718private _extensionHostIsReady: boolean = false;19private _starActivation: CancelablePromise<void> | null;2021private readonly _onWillExecuteCommand: Emitter<ICommandEvent> = this._register(new Emitter<ICommandEvent>());22public readonly onWillExecuteCommand: Event<ICommandEvent> = this._onWillExecuteCommand.event;2324private readonly _onDidExecuteCommand: Emitter<ICommandEvent> = new Emitter<ICommandEvent>();25public readonly onDidExecuteCommand: Event<ICommandEvent> = this._onDidExecuteCommand.event;2627constructor(28@IInstantiationService private readonly _instantiationService: IInstantiationService,29@IExtensionService private readonly _extensionService: IExtensionService,30@ILogService private readonly _logService: ILogService31) {32super();33this._extensionService.whenInstalledExtensionsRegistered().then(value => this._extensionHostIsReady = value);34this._starActivation = null;35}3637private _activateStar(): Promise<void> {38if (!this._starActivation) {39// wait for * activation, limited to at most 30s.40this._starActivation = raceCancellablePromises([41this._extensionService.activateByEvent(`*`),42timeout(30000)43]);44}4546// This is wrapped with notCancellablePromise so it doesn't get cancelled47// early because it is shared between consumers.48return notCancellablePromise(this._starActivation);49}5051async executeCommand<T>(id: string, ...args: any[]): Promise<T> {52this._logService.trace('CommandService#executeCommand', id);5354const activationEvent = `onCommand:${id}`;55const commandIsRegistered = !!CommandsRegistry.getCommand(id);5657if (commandIsRegistered) {5859// if the activation event has already resolved (i.e. subsequent call),60// we will execute the registered command immediately61if (this._extensionService.activationEventIsDone(activationEvent)) {62return this._tryExecuteCommand(id, args);63}6465// if the extension host didn't start yet, we will execute the registered66// command immediately and send an activation event, but not wait for it67if (!this._extensionHostIsReady) {68this._extensionService.activateByEvent(activationEvent); // intentionally not awaited69return this._tryExecuteCommand(id, args);70}7172// we will wait for a simple activation event (e.g. in case an extension wants to overwrite it)73await this._extensionService.activateByEvent(activationEvent);74return this._tryExecuteCommand(id, args);75}7677// finally, if the command is not registered we will send a simple activation event78// as well as a * activation event raced against registration and against 30s79await Promise.all([80this._extensionService.activateByEvent(activationEvent),81raceCancellablePromises<unknown>([82// race * activation against command registration83this._activateStar(),84Event.toPromise(Event.filter(CommandsRegistry.onDidRegisterCommand, e => e === id))85]),86]);8788return this._tryExecuteCommand(id, args);89}9091private _tryExecuteCommand(id: string, args: any[]): Promise<any> {92const command = CommandsRegistry.getCommand(id);93if (!command) {94return Promise.reject(new Error(`command '${id}' not found`));95}96try {97this._onWillExecuteCommand.fire({ commandId: id, args });98const result = this._instantiationService.invokeFunction(command.handler, ...args);99this._onDidExecuteCommand.fire({ commandId: id, args });100return Promise.resolve(result);101} catch (err) {102return Promise.reject(err);103}104}105106public override dispose(): void {107super.dispose();108this._starActivation?.cancel();109}110}111112registerSingleton(ICommandService, CommandService, InstantiationType.Delayed);113114115