diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index b6468cddfc1..6d16f975897 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -225,7 +225,9 @@ export class StandaloneCommandService implements ICommandService { private readonly _dynamicCommands: { [id: string]: ICommand; }; private readonly _onWillExecuteCommand = new Emitter(); + private readonly _onDidExecuteCommand = new Emitter(); public readonly onWillExecuteCommand: Event = this._onWillExecuteCommand.event; + public readonly onDidExecuteCommand: Event = this._onDidExecuteCommand.event; constructor(instantiationService: IInstantiationService) { this._instantiationService = instantiationService; @@ -247,8 +249,9 @@ export class StandaloneCommandService implements ICommandService { } try { - this._onWillExecuteCommand.fire({ commandId: id }); + this._onWillExecuteCommand.fire({ commandId: id, args }); const result = this._instantiationService.invokeFunction.apply(this._instantiationService, [command.handler, ...args]) as T; + this._onDidExecuteCommand.fire({ commandId: id, args }); return Promise.resolve(result); } catch (err) { return Promise.reject(err); diff --git a/src/vs/editor/test/browser/editorTestServices.ts b/src/vs/editor/test/browser/editorTestServices.ts index 25af3b0efa6..fc005b742a9 100644 --- a/src/vs/editor/test/browser/editorTestServices.ts +++ b/src/vs/editor/test/browser/editorTestServices.ts @@ -32,6 +32,9 @@ export class TestCommandService implements ICommandService { private readonly _onWillExecuteCommand = new Emitter(); public readonly onWillExecuteCommand: Event = this._onWillExecuteCommand.event; + private readonly _onDidExecuteCommand = new Emitter(); + public readonly onDidExecuteCommand: Event = this._onDidExecuteCommand.event; + constructor(instantiationService: IInstantiationService) { this._instantiationService = instantiationService; } @@ -43,7 +46,7 @@ export class TestCommandService implements ICommandService { } try { - this._onWillExecuteCommand.fire({ commandId: id }); + this._onWillExecuteCommand.fire({ commandId: id, args }); const result = this._instantiationService.invokeFunction.apply(this._instantiationService, [command.handler, ...args]) as T; return Promise.resolve(result); } catch (err) { diff --git a/src/vs/editor/test/browser/services/openerService.test.ts b/src/vs/editor/test/browser/services/openerService.test.ts index 3e643742469..eadf902357a 100644 --- a/src/vs/editor/test/browser/services/openerService.test.ts +++ b/src/vs/editor/test/browser/services/openerService.test.ts @@ -17,6 +17,7 @@ suite('OpenerService', function () { const commandService = new class implements ICommandService { _serviceBrand: any; onWillExecuteCommand = () => ({ dispose: () => { } }); + onDidExecuteCommand = () => ({ dispose: () => { } }); executeCommand(id: string, ...args: any[]): Promise { lastCommand = { id, args }; return Promise.resolve(undefined); diff --git a/src/vs/platform/commands/common/commands.ts b/src/vs/platform/commands/common/commands.ts index 91541847a17..bb15b457bc8 100644 --- a/src/vs/platform/commands/common/commands.ts +++ b/src/vs/platform/commands/common/commands.ts @@ -14,11 +14,13 @@ export const ICommandService = createDecorator('commandService' export interface ICommandEvent { commandId: string; + args: any[]; } export interface ICommandService { _serviceBrand: any; onWillExecuteCommand: Event; + onDidExecuteCommand: Event; executeCommand(commandId: string, ...args: any[]): Promise; } @@ -133,6 +135,7 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR export const NullCommandService: ICommandService = { _serviceBrand: undefined, onWillExecuteCommand: () => ({ dispose: () => { } }), + onDidExecuteCommand: () => ({ dispose: () => { } }), executeCommand() { return Promise.resolve(undefined); } diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index d2b97151250..055362596c5 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -119,6 +119,7 @@ suite('AbstractKeybindingService', () => { let commandService: ICommandService = { _serviceBrand: undefined, onWillExecuteCommand: () => ({ dispose: () => { } }), + onDidExecuteCommand: () => ({ dispose: () => { } }), executeCommand: (commandId: string, ...args: any[]): Promise => { executeCommandCalls.push({ commandId: commandId, diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index b1c6b84bb3b..ffbbb08e063 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1,3 +1,5 @@ +import { ICommandEvent } from 'vs/platform/commands/common/commands' + /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. @@ -555,6 +557,12 @@ declare module 'vscode' { * @return Disposable which unregisters this command on disposal. */ export function registerDiffInformationCommand(command: string, callback: (diff: LineChange[], ...args: any[]) => any, thisArg?: any): Disposable; + + + /** + * An event that is emitted when a [command](#Command) is executed. + */ + export const onDidExecuteCommand: Event; } //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadCommands.ts b/src/vs/workbench/api/browser/mainThreadCommands.ts index c61ad7e5733..6d0e3c9276c 100644 --- a/src/vs/workbench/api/browser/mainThreadCommands.ts +++ b/src/vs/workbench/api/browser/mainThreadCommands.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ICommandService, CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; +import { ICommandService, CommandsRegistry, ICommandHandlerDescription, ICommandEvent } from 'vs/platform/commands/common/commands'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ExtHostContext, MainThreadCommandsShape, ExtHostCommandsShape, MainContext, IExtHostContext } from '../common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; @@ -78,9 +78,17 @@ export class MainThreadCommands implements MainThreadCommandsShape { return this._commandService.executeCommand(id, ...args); } + $onDidExecuteCommand() { + return this._commandService.onDidExecuteCommand((command) => this.handleExecuteCommand(command)); + } + $getCommands(): Promise { return Promise.resolve(Object.keys(CommandsRegistry.getCommands())); } + + handleExecuteCommand(command: ICommandEvent) { + this._proxy.$handleDidExecuteCommand(command); + } } // --- command doc diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index cff14ddb273..8e836e5de1a 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -17,7 +17,7 @@ import { ISingleEditOperation, EndOfLineSequence } from 'vs/editor/common/model' import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel'; import * as modes from 'vs/editor/common/modes'; import { CharacterPair, CommentRule, EnterAction } from 'vs/editor/common/modes/languageConfiguration'; -import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; +import { ICommandHandlerDescription, ICommandEvent } from 'vs/platform/commands/common/commands'; import { ConfigurationTarget, IConfigurationData, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import * as files from 'vs/platform/files/common/files'; @@ -113,6 +113,7 @@ export interface MainThreadClipboardShape extends IDisposable { export interface MainThreadCommandsShape extends IDisposable { $registerCommand(id: string): void; + $onDidExecuteCommand(): void; $unregisterCommand(id: string): void; $executeCommand(id: string, args: any[]): Promise; $getCommands(): Promise; @@ -709,6 +710,7 @@ export interface MainThreadWindowShape extends IDisposable { export interface ExtHostCommandsShape { $executeContributedCommand(id: string, ...args: any[]): Promise; $getContributedCommandHandlerDescriptions(): Promise<{ [id: string]: string | ICommandHandlerDescription }>; + $handleDidExecuteCommand(command: ICommandEvent): void; } export interface ExtHostConfigurationShape { diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index 59edc45abf3..449e15e600b 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { validateConstraint } from 'vs/base/common/types'; -import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; +import { ICommandHandlerDescription, ICommandEvent } from 'vs/platform/commands/common/commands'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import * as extHostTypeConverter from 'vs/workbench/api/common/extHostTypeConverters'; import { cloneAndChange } from 'vs/base/common/objects'; @@ -18,6 +18,8 @@ import { revive } from 'vs/base/common/marshalling'; import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; import { URI } from 'vs/base/common/uri'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IDisposable } from 'vs/base/common/lifecycle'; interface CommandHandler { callback: Function; @@ -30,6 +32,8 @@ export interface ArgumentProcessor { } export class ExtHostCommands implements ExtHostCommandsShape { + private readonly _onDidExecuteCommand: Emitter = new Emitter(); + public readonly onDidExecuteCommandEmitter: Event = this._onDidExecuteCommand.event; private readonly _commands = new Map(); private readonly _proxy: MainThreadCommandsShape; @@ -107,6 +111,15 @@ export class ExtHostCommands implements ExtHostCommandsShape { }); } + onDidExecuteCommand(listener: (command: ICommandEvent) => any, thisArgs?: any, disposables?: IDisposable[]) { + this._proxy.$onDidExecuteCommand(); + return this.onDidExecuteCommandEmitter(listener, thisArgs, disposables); + } + + $handleDidExecuteCommand(command: ICommandEvent): void { + this._onDidExecuteCommand.fire(command); + } + executeCommand(id: string, ...args: any[]): Promise { this._logService.trace('ExtHostCommands#executeCommand', id); @@ -133,7 +146,9 @@ export class ExtHostCommands implements ExtHostCommandsShape { } }); - return this._proxy.$executeCommand(id, args).then(result => revive(result, 0)); + return this._proxy.$executeCommand(id, args).then(result => { + return revive(result, 0); + }); } } diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index ffe929454e3..0e527dab21a 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -230,7 +230,10 @@ export function createApiFactory( }, getCommands(filterInternal: boolean = false): Thenable { return extHostCommands.getCommands(filterInternal); - } + }, + onDidExecuteCommand: (listener, thisArgs?, disposables?) => { + return extHostCommands.onDidExecuteCommand(listener, thisArgs, disposables); + }, }; // namespace: env diff --git a/src/vs/workbench/services/commands/common/commandService.ts b/src/vs/workbench/services/commands/common/commandService.ts index ffb7d1ebd83..c396d7ed897 100644 --- a/src/vs/workbench/services/commands/common/commandService.ts +++ b/src/vs/workbench/services/commands/common/commandService.ts @@ -22,6 +22,9 @@ export class CommandService extends Disposable implements ICommandService { private readonly _onWillExecuteCommand: Emitter = this._register(new Emitter()); public readonly onWillExecuteCommand: Event = this._onWillExecuteCommand.event; + private readonly _onDidExecuteCommand: Emitter = new Emitter(); + public readonly onDidExecuteCommand: Event = this._onDidExecuteCommand.event; + constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, @IExtensionService private readonly _extensionService: IExtensionService, @@ -77,8 +80,12 @@ export class CommandService extends Disposable implements ICommandService { return Promise.reject(new Error(`command '${id}' not found`)); } try { - this._onWillExecuteCommand.fire({ commandId: id }); + this._onWillExecuteCommand.fire({ commandId: id, args }); const result = this._instantiationService.invokeFunction.apply(this._instantiationService, [command.handler, ...args]); + this._onDidExecuteCommand.fire({ + commandId: id, + args, + }); return Promise.resolve(result); } catch (err) { return Promise.reject(err); diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index cefee9701c9..5b9f44514e9 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -523,6 +523,7 @@ class MockCommandService implements ICommandService { public callCount = 0; onWillExecuteCommand = () => Disposable.None; + onDidExecuteCommand = () => Disposable.None; public executeCommand(commandId: string, ...args: any[]): Promise { this.callCount++; diff --git a/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts b/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts index 9ffa9aee794..c19beb1371f 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts @@ -24,6 +24,7 @@ const emptyDialogService = new class implements IDialogService { const emptyCommandService: ICommandService = { _serviceBrand: undefined, onWillExecuteCommand: () => ({ dispose: () => { } }), + onDidExecuteCommand: () => ({ dispose: () => { } }), executeCommand: (commandId: string, ...args: any[]): Promise => { return Promise.resolve(undefined); }