diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 56a0f094d6e4ffea9541f03ccf6a26231e7fc5c7..576092c98659e1ce1308a97f81d7302a7ec047fc 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -234,7 +234,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; @@ -256,8 +258,10 @@ 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 25af3b0efa6bd44270a431e317d89533b532aaba..f896360acf4c3528a7edd11391b76a090a8a5247 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,8 +46,9 @@ 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; + this._onDidExecuteCommand.fire({ commandId: id, args }); return Promise.resolve(result); } catch (err) { return Promise.reject(err); diff --git a/src/vs/editor/test/browser/services/openerService.test.ts b/src/vs/editor/test/browser/services/openerService.test.ts index 3e643742469452d80237bf20283a682a66523912..eadf902357a35346a8c94ed499296302372c31c7 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 4777d1ddef7e3884136a344fe633a70903d84095..06c33cf281e08f1247c5a8df63e7c96a6db2c5e1 100644 --- a/src/vs/platform/commands/common/commands.ts +++ b/src/vs/platform/commands/common/commands.ts @@ -15,11 +15,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; } @@ -135,6 +137,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 4798e84518d52f24bfacdd4c5670da4760eef2f3..ec4a46372a6e2090470c3a9abfed28264e047d67 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -121,6 +121,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 5bca4d0d5074ca0b428b311b53ba999a2b05d15f..d86d071f6f4fea48f8981dfcc893597eef4642df 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -565,6 +565,22 @@ declare module 'vscode' { //#endregion + //#region Joh: onDidExecuteCommand + + export interface CommandExecutionEvent { + command: string; + arguments: any[]; + } + + export namespace commands { + /** + * An event that is emitted when a [command](#Command) is executed. + */ + export const onDidExecuteCommand: Event; + } + + //#endregion + //#region Joh: decorations //todo@joh -> make class diff --git a/src/vs/workbench/api/browser/mainThreadCommands.ts b/src/vs/workbench/api/browser/mainThreadCommands.ts index 2f4d043f04875d3a4f5fc420a7ee14fe838575da..25dc11380552eb5e45ee7e12f3aaaeedb004eda6 100644 --- a/src/vs/workbench/api/browser/mainThreadCommands.ts +++ b/src/vs/workbench/api/browser/mainThreadCommands.ts @@ -15,6 +15,7 @@ export class MainThreadCommands implements MainThreadCommandsShape { private readonly _commandRegistrations = new Map(); private readonly _generateCommandsDocumentationRegistration: IDisposable; private readonly _proxy: ExtHostCommandsShape; + private _onDidExecuteCommandListener?: IDisposable; constructor( extHostContext: IExtHostContext, @@ -77,6 +78,19 @@ export class MainThreadCommands implements MainThreadCommandsShape { return this._commandService.executeCommand(id, ...args); } + $registerCommandListener() { + if (!this._onDidExecuteCommandListener) { + this._onDidExecuteCommandListener = this._commandService.onDidExecuteCommand(command => this._proxy.$handleDidExecuteCommand(command)); + } + } + + $unregisterCommandListener() { + if (this._onDidExecuteCommandListener) { + this._onDidExecuteCommandListener.dispose(); + this._onDidExecuteCommandListener = undefined; + } + } + $getCommands(): Promise { return Promise.resolve([...CommandsRegistry.getCommands().keys()]); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 5235a582c87d2f76e70f5b4f7d8c1caa7f78d54a..127e45a7439752fc70cbe2e1cbdefe8e75588f9b 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -21,7 +21,7 @@ import { EndOfLineSequence, ISingleEditOperation } 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 { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -115,6 +115,8 @@ export interface MainThreadClipboardShape extends IDisposable { export interface MainThreadCommandsShape extends IDisposable { $registerCommand(id: string): void; + $registerCommandListener(): void; + $unregisterCommandListener(): void; $unregisterCommand(id: string): void; $executeCommand(id: string, args: any[]): Promise; $getCommands(): Promise; @@ -736,6 +738,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 3d0840068b0bcafa7b5b61d12fc907f8921885c2..2c03c57577dec3ef8bd821cb46f2f0d85e617dcf 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'; @@ -17,6 +17,7 @@ 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 { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; interface CommandHandler { @@ -31,6 +32,9 @@ export interface ArgumentProcessor { export class ExtHostCommands implements ExtHostCommandsShape { + private readonly _onDidExecuteCommand: Emitter; + readonly onDidExecuteCommand: Event; + private readonly _commands = new Map(); private readonly _proxy: MainThreadCommandsShape; private readonly _converter: CommandsConverter; @@ -42,6 +46,11 @@ export class ExtHostCommands implements ExtHostCommandsShape { logService: ILogService ) { this._proxy = mainContext.getProxy(MainContext.MainThreadCommands); + this._onDidExecuteCommand = new Emitter({ + onFirstListenerDidAdd: () => this._proxy.$registerCommandListener(), + onLastListenerRemove: () => this._proxy.$unregisterCommandListener(), + }); + this.onDidExecuteCommand = this._onDidExecuteCommand.event; this._logService = logService; this._converter = new CommandsConverter(this); this._argumentProcessors = [ @@ -106,6 +115,10 @@ export class ExtHostCommands implements ExtHostCommandsShape { }); } + $handleDidExecuteCommand(command: ICommandEvent): void { + this._onDidExecuteCommand.fire({ command: command.commandId, arguments: command.args }); + } + executeCommand(id: string, ...args: any[]): Promise { this._logService.trace('ExtHostCommands#executeCommand', id); @@ -154,6 +167,7 @@ export class ExtHostCommands implements ExtHostCommandsShape { try { const result = callback.apply(thisArg, args); + this._onDidExecuteCommand.fire({ command: id, arguments: args }); return Promise.resolve(result); } catch (err) { this._logService.error(err, id); diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 293bddc5fac71102a36a744604119676468c50e8..508a9132834bf833f4c3492d49164fd57d176d7d 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -244,7 +244,11 @@ export function createApiFactory( }, getCommands(filterInternal: boolean = false): Thenable { return extHostCommands.getCommands(filterInternal); - } + }, + onDidExecuteCommand: proposedApiFunction(extension, (listener, thisArgs?, disposables?) => { + checkProposedApiEnabled(extension); + 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 ffb7d1ebd8365ae3710d85064fc1535fb1c7a6de..f1826cd036affa9fc773089411439dd75ca4a7a8 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,9 @@ 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 cefee9701c928fb6ef1a8bfcd334044c8a25d198..5b9f44514e97dd0c3d4674573c63e84a151408cf 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 556d88a5512a58ec1af31ccda18ef2f7adff8da6..991bfbede8aac372462de2d333fe6ebb1da14885 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts @@ -26,6 +26,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); }