/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 'use strict'; import {Remotable, IThreadService} from 'vs/platform/thread/common/thread'; import {IEventService} from 'vs/platform/event/common/event'; import {PluginsRegistry} from 'vs/platform/plugins/common/pluginsRegistry'; import {SyncActionDescriptor} from 'vs/platform/actions/common/actions'; import {KeybindingsRegistry} from 'vs/platform/keybinding/common/keybindingsRegistry'; import {KeybindingsUtils} from 'vs/platform/keybinding/common/keybindingsUtils'; import {IKeybindingService, ICommandHandlerDescription} from 'vs/platform/keybinding/common/keybindingService'; import {TPromise} from 'vs/base/common/winjs.base'; import {PluginHostEditors} from 'vs/workbench/api/common/pluginHostEditors'; import {IMessageService, Severity} from 'vs/platform/message/common/message'; import {canSerialize} from 'vs/base/common/marshalling'; import {toErrorMessage} from 'vs/base/common/errors'; import * as vscode from 'vscode'; interface CommandHandler { callback: Function; thisArg: any; description: ICommandHandlerDescription; } @Remotable.PluginHostContext('PluginHostCommands') export class PluginHostCommands { private _commands: { [n: string]: CommandHandler } = Object.create(null); private _proxy: MainThreadCommands; private _pluginHostEditors: PluginHostEditors; constructor(@IThreadService threadService: IThreadService) { this._pluginHostEditors = threadService.getRemotable(PluginHostEditors); this._proxy = threadService.getRemotable(MainThreadCommands); } registerCommand(id: string, callback: (...args: any[]) => T | Thenable, thisArg?: any, description?: ICommandHandlerDescription): vscode.Disposable { if (!id.trim().length) { throw new Error('invalid id'); } if (this._commands[id]) { throw new Error('command with id already exists'); } this._commands[id] = { callback, thisArg, description }; this._proxy.$registerCommand(id); return { dispose: () => { delete this._commands[id]; } } } registerTextEditorCommand(id: string, callback: (textEditor: vscode.TextEditor, edit: vscode.TextEditorEdit) => void, thisArg?: any): vscode.Disposable { let actualCallback: (textEditor: vscode.TextEditor, edit: vscode.TextEditorEdit) => void = thisArg ? callback.bind(thisArg) : callback; return this.registerCommand(id, () => { let activeTextEditor = this._pluginHostEditors.getActiveTextEditor(); if (!activeTextEditor) { console.warn('Cannot execute ' + id + ' because there is no active text editor.'); return; } activeTextEditor.edit((edit: vscode.TextEditorEdit) => { actualCallback(activeTextEditor, edit); }).then((result) => { if (!result) { console.warn('Edits from command ' + id + ' were not applied.') } }, (err) => { console.warn('An error occured while running command ' + id, err); }); }) } executeCommand(id: string, ...args: any[]): Thenable { if (this._commands[id]) { // we stay inside the extension host and support // to pass any kind of parameters around return this.$executeContributedCommand(id, ...args); } else { // // check that we can get all parameters over to // // the other side // for (let i = 0; i < args.length; i++) { // if (args[i] !== null && typeof args[i] === 'object' && !canSerialize(args[i])) { // throw new Error('illegal argument - can not serialize argument number: ' + i) // } // } return this._proxy.$executeCommand(id, args); } } $executeContributedCommand(id: string, ...args: any[]): Thenable { let command = this._commands[id]; if (!command) { return Promise.reject(id); } try { let {callback, thisArg} = command; let result = callback.apply(thisArg, args); return Promise.resolve(result); } catch (err) { try { console.log(toErrorMessage(err)); console.log(err); } catch (err) { // } return Promise.reject(`Running the contributed command:'${id}' failed.`); } } getCommands(filterUnderscoreCommands: boolean = false): Thenable { return this._proxy.$getCommands().then(result => { if (filterUnderscoreCommands) { result = result.filter(command => command[0] !== '_'); } return result; }); } $getContributedCommandHandlerDescriptions(): TPromise<{ [id: string]: string | ICommandHandlerDescription }> { const result: { [id: string]: string | ICommandHandlerDescription } = Object.create(null); for (let id in this._commands) { let {description} = this._commands[id]; if (description) { result[id] = description; } } return TPromise.as(result); } } @Remotable.MainContext('MainThreadCommands') export class MainThreadCommands { private _threadService: IThreadService; private _keybindingService: IKeybindingService; private _proxy: PluginHostCommands; constructor( @IThreadService threadService: IThreadService, @IKeybindingService keybindingService: IKeybindingService) { this._threadService = threadService; this._keybindingService = keybindingService; this._proxy = this._threadService.getRemotable(PluginHostCommands); } $registerCommand(id: string): TPromise { KeybindingsRegistry.registerCommandDesc({ id, handler: (serviceAccessor, ...args: any[]) => { return this._proxy.$executeContributedCommand(id, ...args); //TODO@Joh - we cannot serialize the args }, weight: undefined, context: undefined, win: undefined, mac: undefined, linux: undefined, primary: undefined, secondary: undefined }); return undefined; } $executeCommand(id: string, args: any[]): Thenable { return this._keybindingService.executeCommand(id, args); } $getCommands(): Thenable { return TPromise.as(Object.keys(KeybindingsRegistry.getCommands())); } getCommandHandlerDescriptions(): TPromise<{ [id: string]: string | ICommandHandlerDescription }> { return this._proxy.$getContributedCommandHandlerDescriptions().then(result => { const commands = KeybindingsRegistry.getCommands(); for (let id in commands) { let {description} = commands[id]; if (description) { result[id] = description; } } return result; }); } }