diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 56a0f094d6e..576092c9865 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 25af3b0efa6..f896360acf4 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 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 4777d1ddef7..06c33cf281e 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 4798e84518d..ec4a46372a6 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 5bca4d0d507..d86d071f6f4 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 2f4d043f048..25dc1138055 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 5235a582c87..127e45a7439 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 3d0840068b0..2c03c57577d 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 293bddc5fac..508a9132834 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 ffb7d1ebd83..f1826cd036a 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 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 556d88a5512..991bfbede8a 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); }