diff --git a/packages/plugin-ext/src/common/plugin-api-rpc-model.ts b/packages/plugin-ext/src/common/plugin-api-rpc-model.ts index 4938d87486706..fa219971e5d4f 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc-model.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc-model.ts @@ -322,7 +322,6 @@ export interface DocumentLinkProvider { export interface CodeLensSymbol { range: Range; - id?: string; command?: Command; } diff --git a/packages/plugin-ext/src/plugin/command-registry.ts b/packages/plugin-ext/src/plugin/command-registry.ts index 87352948df467..60b801dbe05f5 100644 --- a/packages/plugin-ext/src/plugin/command-registry.ts +++ b/packages/plugin-ext/src/plugin/command-registry.ts @@ -19,6 +19,7 @@ *--------------------------------------------------------------------------------------------*/ import * as theia from '@theia/plugin'; +import * as model from '../common/plugin-api-rpc-model'; import { CommandRegistryExt, PLUGIN_RPC_CONTEXT as Ext, CommandRegistryMain } from '../common/plugin-api-rpc'; import { RPCProtocol } from '../common/rpc-protocol'; import { Disposable } from './types-impl'; @@ -153,26 +154,47 @@ export class CommandsConverter { /** * Convert to a command that can be safely passed over JSON-RPC. */ - toSafeCommand(command: theia.Command, disposables: DisposableCollection): theia.Command { + toSafeCommand(command: undefined, disposables: DisposableCollection): undefined; + toSafeCommand(command: theia.Command, disposables: DisposableCollection): model.Command; + toSafeCommand(command: theia.Command | undefined, disposables: DisposableCollection): model.Command | undefined; + toSafeCommand(command: theia.Command | undefined, disposables: DisposableCollection): model.Command | undefined { + if (!command) { + return undefined; + } + const result = this.toInternalCommand(command); + if (KnownCommands.mapped(result.id)) { + return result; + } + if (!this.isSafeCommandRegistered) { this.commands.registerCommand({ id: this.safeCommandId }, this.executeSafeCommand, this); this.isSafeCommandRegistered = true; } - const result: theia.Command = {}; - Object.assign(result, command); - if (command.command && command.arguments && command.arguments.length > 0) { const id = this.handle++; this.commandsMap.set(id, command); disposables.push(new Disposable(() => this.commandsMap.delete(id))); - result.command = this.safeCommandId; + result.id = this.safeCommandId; result.arguments = [id]; } return result; } + protected toInternalCommand(external: theia.Command): model.Command { + // we're deprecating Command.id, so it has to be optional. + // Existing code will have compiled against a non - optional version of the field, so asserting it to exist is ok + // tslint:disable-next-line: no-any + return KnownCommands.map((external.command || external.id)!, external.arguments, (mappedId: string, mappedArgs: any[]) => + ({ + id: mappedId, + title: external.title || external.label || ' ', + tooltip: external.tooltip, + arguments: mappedArgs + })); + } + // tslint:disable-next-line:no-any private executeSafeCommand(...args: any[]): PromiseLike { const command = this.commandsMap.get(args[0]); diff --git a/packages/plugin-ext/src/plugin/languages.ts b/packages/plugin-ext/src/plugin/languages.ts index 42bc2b79487a5..a60325e72acea 100644 --- a/packages/plugin-ext/src/plugin/languages.ts +++ b/packages/plugin-ext/src/plugin/languages.ts @@ -78,6 +78,7 @@ import { FoldingProviderAdapter } from './languages/folding'; import { ColorProviderAdapter } from './languages/color'; import { RenameAdapter } from './languages/rename'; import { Event } from '@theia/core/lib/common/event'; +import { CommandRegistryImpl } from './command-registry'; type Adapter = CompletionAdapter | SignatureHelpAdapter | @@ -109,7 +110,10 @@ export class LanguagesExtImpl implements LanguagesExt { private callId = 0; private adaptersMap = new Map(); - constructor(rpc: RPCProtocol, private readonly documents: DocumentsExtImpl) { + constructor( + rpc: RPCProtocol, + private readonly documents: DocumentsExtImpl, + private readonly commands: CommandRegistryImpl) { this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.LANGUAGES_MAIN); this.diagnostics = new Diagnostics(rpc); } @@ -225,7 +229,7 @@ export class LanguagesExtImpl implements LanguagesExt { } registerCompletionItemProvider(selector: theia.DocumentSelector, provider: theia.CompletionItemProvider, triggerCharacters: string[]): theia.Disposable { - const callId = this.addNewAdapter(new CompletionAdapter(provider, this.documents)); + const callId = this.addNewAdapter(new CompletionAdapter(provider, this.documents, this.commands)); this.proxy.$registerCompletionSupport(callId, this.transformDocumentSelector(selector), triggerCharacters, CompletionAdapter.hasResolveSupport(provider)); return this.createDisposable(callId); } @@ -395,7 +399,7 @@ export class LanguagesExtImpl implements LanguagesExt { pluginModel: PluginModel, metadata?: theia.CodeActionProviderMetadata ): theia.Disposable { - const callId = this.addNewAdapter(new CodeActionAdapter(provider, this.documents, this.diagnostics, pluginModel ? pluginModel.id : '')); + const callId = this.addNewAdapter(new CodeActionAdapter(provider, this.documents, this.diagnostics, pluginModel ? pluginModel.id : '', this.commands)); this.proxy.$registerQuickFixProvider( callId, this.transformDocumentSelector(selector), @@ -416,7 +420,7 @@ export class LanguagesExtImpl implements LanguagesExt { // ### Code Lens Provider begin registerCodeLensProvider(selector: theia.DocumentSelector, provider: theia.CodeLensProvider): theia.Disposable { - const callId = this.addNewAdapter(new CodeLensAdapter(provider, this.documents)); + const callId = this.addNewAdapter(new CodeLensAdapter(provider, this.documents, this.commands)); const eventHandle = typeof provider.onDidChangeCodeLenses === 'function' ? this.nextCallId() : undefined; this.proxy.$registerCodeLensSupport(callId, this.transformDocumentSelector(selector), eventHandle); let result = this.createDisposable(callId); diff --git a/packages/plugin-ext/src/plugin/languages/code-action.ts b/packages/plugin-ext/src/plugin/languages/code-action.ts index 6e7722fa6d1ca..04c2646ac976e 100644 --- a/packages/plugin-ext/src/plugin/languages/code-action.ts +++ b/packages/plugin-ext/src/plugin/languages/code-action.ts @@ -22,6 +22,8 @@ import * as Converter from '../type-converters'; import { DocumentsExtImpl } from '../documents'; import { Diagnostics } from './diagnostics'; import { CodeActionKind } from '../types-impl'; +import { CommandRegistryImpl } from '../command-registry'; +import { DisposableCollection } from '@theia/core/lib/common/disposable'; export class CodeActionAdapter { @@ -29,7 +31,8 @@ export class CodeActionAdapter { private readonly provider: theia.CodeActionProvider, private readonly document: DocumentsExtImpl, private readonly diagnostics: Diagnostics, - private readonly pluginId: string + private readonly pluginId: string, + private readonly commands: CommandRegistryImpl ) { } provideCodeAction(resource: URI, rangeOrSelection: Range | Selection, @@ -60,6 +63,8 @@ export class CodeActionAdapter { if (!Array.isArray(commandsOrActions) || commandsOrActions.length === 0) { return undefined!; } + // TODO cache toDispose and dispose it + const toDispose = new DisposableCollection(); const result: monaco.languages.CodeAction[] = []; for (const candidate of commandsOrActions) { if (!candidate) { @@ -68,7 +73,7 @@ export class CodeActionAdapter { if (CodeActionAdapter._isCommand(candidate)) { result.push({ title: candidate.title || '', - command: Converter.toInternalCommand(candidate) + command: this.commands.converter.toSafeCommand(candidate, toDispose) }); } else { if (codeActionContext.only) { @@ -83,7 +88,7 @@ export class CodeActionAdapter { result.push({ title: candidate.title, - command: candidate.command && Converter.toInternalCommand(candidate.command), + command: this.commands.converter.toSafeCommand(candidate.command, toDispose), diagnostics: candidate.diagnostics && candidate.diagnostics.map(Converter.convertDiagnosticToMarkerData) as monaco.editor.IMarker[], edit: candidate.edit && Converter.fromWorkspaceEdit(candidate.edit) as monaco.languages.WorkspaceEdit, kind: candidate.kind && candidate.kind.value diff --git a/packages/plugin-ext/src/plugin/languages/completion.ts b/packages/plugin-ext/src/plugin/languages/completion.ts index 6f83655dfb250..4a3e893d2c0af 100644 --- a/packages/plugin-ext/src/plugin/languages/completion.ts +++ b/packages/plugin-ext/src/plugin/languages/completion.ts @@ -22,15 +22,19 @@ import * as Converter from '../type-converters'; import { mixin } from '../../common/types'; import { Position } from '../../common/plugin-api-rpc'; import { CompletionContext, CompletionResultDto, Completion, CompletionDto } from '../../common/plugin-api-rpc-model'; +import { CommandRegistryImpl } from '../command-registry'; +import { DisposableCollection } from '@theia/core/lib/common/disposable'; export class CompletionAdapter { private cacheId = 0; - private cache = new Map(); + private readonly cache = new Map(); + private readonly disposables = new Map(); - constructor(private readonly delegate: theia.CompletionItemProvider, - private readonly documents: DocumentsExtImpl) { - - } + constructor( + private readonly delegate: theia.CompletionItemProvider, + private readonly documents: DocumentsExtImpl, + private readonly commands: CommandRegistryImpl + ) { } provideCompletionItems(resource: URI, position: Position, context: CompletionContext, token: theia.CancellationToken): Promise { const document = this.documents.getDocumentData(resource); @@ -43,6 +47,10 @@ export class CompletionAdapter { const pos = Converter.toPosition(position); return Promise.resolve(this.delegate.provideCompletionItems(doc, pos, token, context)).then(value => { const id = this.cacheId++; + + const toDispose = new DisposableCollection(); + this.disposables.set(id, toDispose); + const result: CompletionResultDto = { id, completions: [], @@ -102,9 +110,13 @@ export class CompletionAdapter { }); } - releaseCompletionItems(id: number): Promise { + async releaseCompletionItems(id: number): Promise { this.cache.delete(id); - return Promise.resolve(); + const toDispose = this.disposables.get(id); + if (toDispose) { + toDispose.dispose(); + this.disposables.delete(id); + } } private convertCompletionItem(item: theia.CompletionItem, position: theia.Position, defaultRange: theia.Range, id: number, parentId: number): CompletionDto | undefined { @@ -113,6 +125,11 @@ export class CompletionAdapter { return undefined; } + const toDispose = this.disposables.get(parentId); + if (!toDispose) { + throw Error('DisposableCollection is missing...'); + } + const result: CompletionDto = { id, parentId, @@ -125,7 +142,7 @@ export class CompletionAdapter { preselect: item.preselect, insertText: '', additionalTextEdits: item.additionalTextEdits && item.additionalTextEdits.map(Converter.fromTextEdit), - command: undefined, // TODO: implement this: this.commands.toInternal(item.command), + command: this.commands.converter.toSafeCommand(item.command, toDispose), commitCharacters: item.commitCharacters }; diff --git a/packages/plugin-ext/src/plugin/languages/lens.ts b/packages/plugin-ext/src/plugin/languages/lens.ts index 0cc942c695674..1900f1dc166fb 100644 --- a/packages/plugin-ext/src/plugin/languages/lens.ts +++ b/packages/plugin-ext/src/plugin/languages/lens.ts @@ -20,6 +20,8 @@ import { DocumentsExtImpl } from '../documents'; import { CodeLensSymbol } from '../../common/plugin-api-rpc-model'; import * as Converter from '../type-converters'; import { ObjectIdentifier } from '../../common/object-identifier'; +import { CommandRegistryImpl } from '../command-registry'; +import { DisposableCollection } from '@theia/core/lib/common/disposable'; /** Adapts the calls from main to extension thread for providing/resolving the code lenses. */ export class CodeLensAdapter { @@ -27,11 +29,13 @@ export class CodeLensAdapter { private static readonly BAD_CMD: theia.Command = { command: 'missing', title: '<>' }; private cacheId = 0; - private cache = new Map(); + private readonly cache = new Map(); + private readonly disposables = new Map(); constructor( private readonly provider: theia.CodeLensProvider, private readonly documents: DocumentsExtImpl, + private readonly commands: CommandRegistryImpl ) { } provideCodeLenses(resource: URI, token: theia.CancellationToken): Promise { @@ -45,12 +49,15 @@ export class CodeLensAdapter { return Promise.resolve(this.provider.provideCodeLenses(doc, token)).then(lenses => { if (Array.isArray(lenses)) { return lenses.map(lens => { - const id = this.cacheId++; + const cacheId = this.cacheId++; + const toDispose = new DisposableCollection(); const lensSymbol = ObjectIdentifier.mixin({ range: Converter.fromRange(lens.range)!, - command: lens.command ? Converter.toInternalCommand(lens.command) : undefined - }, id); - this.cache.set(id, lens); + command: this.commands.converter.toSafeCommand(lens.command, toDispose) + }, cacheId); + // TODO: invalidate caches and dispose command handlers + this.cache.set(cacheId, lens); + this.disposables.set(cacheId, toDispose); return lensSymbol; }); } @@ -58,23 +65,28 @@ export class CodeLensAdapter { }); } - resolveCodeLens(resource: URI, symbol: CodeLensSymbol, token: theia.CancellationToken): Promise { - const lens = this.cache.get(ObjectIdentifier.of(symbol)); + async resolveCodeLens(resource: URI, symbol: CodeLensSymbol, token: theia.CancellationToken): Promise { + const cacheId = ObjectIdentifier.of(symbol); + const lens = this.cache.get(cacheId); if (!lens) { - return Promise.resolve(undefined); + return undefined; } - let resolve: Promise; - if (typeof this.provider.resolveCodeLens !== 'function' || lens.isResolved) { - resolve = Promise.resolve(lens); - } else { - resolve = Promise.resolve(this.provider.resolveCodeLens(lens, token)); + let newLens: theia.CodeLens | undefined; + if (typeof this.provider.resolveCodeLens === 'function' && !lens.isResolved) { + newLens = await this.provider.resolveCodeLens(lens, token); + if (token.isCancellationRequested) { + return undefined; + } } + newLens = newLens || lens; - return resolve.then(newLens => { - newLens = newLens || lens; - symbol.command = Converter.toInternalCommand(newLens.command ? newLens.command : CodeLensAdapter.BAD_CMD); - return symbol; - }); + const disposables = this.disposables.get(cacheId); + if (!disposables) { + // already been disposed of + return undefined; + } + symbol.command = this.commands.converter.toSafeCommand(newLens.command ? newLens.command : CodeLensAdapter.BAD_CMD, disposables); + return symbol; } } diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index 802a07696463e..6935baca1928e 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -160,7 +160,7 @@ export function createAPIFactory( const statusBarMessageRegistryExt = new StatusBarMessageRegistryExt(rpc); const terminalExt = rpc.set(MAIN_RPC_CONTEXT.TERMINAL_EXT, new TerminalServiceExtImpl(rpc)); const outputChannelRegistryExt = new OutputChannelRegistryExt(rpc); - const languagesExt = rpc.set(MAIN_RPC_CONTEXT.LANGUAGES_EXT, new LanguagesExtImpl(rpc, documents)); + const languagesExt = rpc.set(MAIN_RPC_CONTEXT.LANGUAGES_EXT, new LanguagesExtImpl(rpc, documents, commandRegistry)); const treeViewsExt = rpc.set(MAIN_RPC_CONTEXT.TREE_VIEWS_EXT, new TreeViewsExtImpl(rpc, commandRegistry)); const webviewExt = rpc.set(MAIN_RPC_CONTEXT.WEBVIEWS_EXT, new WebviewsExtImpl(rpc)); const tasksExt = rpc.set(MAIN_RPC_CONTEXT.TASKS_EXT, new TasksExtImpl(rpc)); diff --git a/packages/plugin-ext/src/plugin/scm.ts b/packages/plugin-ext/src/plugin/scm.ts index 209e9a09fcf43..eeb2a2005451c 100644 --- a/packages/plugin-ext/src/plugin/scm.ts +++ b/packages/plugin-ext/src/plugin/scm.ts @@ -15,11 +15,14 @@ ********************************************************************************/ import * as theia from '@theia/plugin'; -import { CommandRegistryExt, Plugin as InternalPlugin, PLUGIN_RPC_CONTEXT, ScmExt, ScmMain, ScmCommandArg } from '../common/plugin-api-rpc'; +import { Plugin as InternalPlugin, PLUGIN_RPC_CONTEXT, ScmExt, ScmMain, ScmCommandArg } from '../common/plugin-api-rpc'; import { RPCProtocol } from '../common/rpc-protocol'; -import { CancellationToken } from '@theia/core'; +import { CancellationToken } from '@theia/core/lib/common/cancellation'; +import { DisposableCollection, Disposable } from '@theia/core/lib/common/disposable'; import { UriComponents } from '../common/uri-components'; import URI from '@theia/core/lib/common/uri'; +import { CommandRegistryImpl } from './command-registry'; +import { ScmCommand } from '@theia/scm/lib/browser/scm-provider'; export class ScmExtImpl implements ScmExt { private handle: number = 0; @@ -27,7 +30,7 @@ export class ScmExtImpl implements ScmExt { private readonly sourceControlMap = new Map(); private readonly sourceControlsByPluginMap: Map = new Map(); - constructor(readonly rpc: RPCProtocol, private readonly commands: CommandRegistryExt) { + constructor(readonly rpc: RPCProtocol, private readonly commands: CommandRegistryImpl) { this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.SCM_MAIN); commands.registerArgumentProcessor({ // tslint:disable-next-line:no-any @@ -140,15 +143,18 @@ class SourceControlImpl implements theia.SourceControl { private _acceptInputCommand: theia.Command | undefined; private _statusBarCommands: theia.Command[] | undefined; + private readonly toDispose = new DisposableCollection(); + constructor( private proxy: ScmMain, - private commands: CommandRegistryExt, + private commands: CommandRegistryImpl, private _id: string, private _label: string, private _rootUri?: theia.Uri ) { this._inputBox = new InputBoxImpl(proxy, this.handle); this.proxy.$registerSourceControl(this.handle, _id, _label, _rootUri ? _rootUri.path : undefined); + this.toDispose.push(Disposable.create(() => this.proxy.$unregisterSourceControl(this.handle))); } get id(): string { @@ -166,6 +172,7 @@ class SourceControlImpl implements theia.SourceControl { createResourceGroup(id: string, label: string): theia.SourceControlResourceGroup { const sourceControlResourceGroup = new SourceControlResourceGroupImpl(this.proxy, this.commands, this.handle, id, label); this.resourceGroupsMap.set(SourceControlImpl.resourceGroupHandle++, sourceControlResourceGroup); + this.toDispose.push(sourceControlResourceGroup); return sourceControlResourceGroup; } @@ -203,38 +210,45 @@ class SourceControlImpl implements theia.SourceControl { } dispose(): void { - this.proxy.$unregisterSourceControl(this.handle); + this.toDispose.dispose(); } + protected toDisposeOnAcceptInputCommand = new DisposableCollection(); + get acceptInputCommand(): theia.Command | undefined { return this._acceptInputCommand; } set acceptInputCommand(acceptInputCommand: theia.Command | undefined) { + this.toDisposeOnAcceptInputCommand.dispose(); + this.toDispose.push(this.toDisposeOnAcceptInputCommand); + this._acceptInputCommand = acceptInputCommand; - if (acceptInputCommand && acceptInputCommand.command) { - const command = { - id: acceptInputCommand.command, - title: acceptInputCommand.title || '' - }; - this.proxy.$updateSourceControl(this.handle, { acceptInputCommand: command }); - } + this.proxy.$updateSourceControl(this.handle, { + acceptInputCommand: this.commands.converter.toSafeCommand(acceptInputCommand, this.toDisposeOnAcceptInputCommand) + }); } + protected toDisposeOnStatusBarCommands = new DisposableCollection(); + get statusBarCommands(): theia.Command[] | undefined { return this._statusBarCommands; } set statusBarCommands(statusBarCommands: theia.Command[] | undefined) { + this.toDisposeOnStatusBarCommands.dispose(); + this.toDispose.push(this.toDisposeOnStatusBarCommands); + this._statusBarCommands = statusBarCommands; + + let safeStatusBarCommands: ScmCommand[] | undefined; if (statusBarCommands) { - const commands = statusBarCommands.map(statusBarCommand => ({ - command: statusBarCommand.command, - title: statusBarCommand.title || '' - })); - this.proxy.$updateSourceControl(this.handle, { statusBarCommands: commands }); + safeStatusBarCommands = statusBarCommands.map(statusBarCommand => this.commands.converter.toSafeCommand(statusBarCommand, this.toDisposeOnStatusBarCommands)); } + this.proxy.$updateSourceControl(this.handle, { + statusBarCommands: safeStatusBarCommands + }); } getResourceGroup(handle: number): SourceControlResourceGroupImpl | undefined { @@ -253,7 +267,7 @@ class SourceControlResourceGroupImpl implements theia.SourceControlResourceGroup constructor( private proxy: ScmMain, - private commands: CommandRegistryExt, + private commands: CommandRegistryImpl, private sourceControlHandle: number, private _id: string, private _label: string, diff --git a/packages/plugin-ext/src/plugin/tree/tree-views.ts b/packages/plugin-ext/src/plugin/tree/tree-views.ts index d3e50160bd88e..500dfc4d6391c 100644 --- a/packages/plugin-ext/src/plugin/tree/tree-views.ts +++ b/packages/plugin-ext/src/plugin/tree/tree-views.ts @@ -32,7 +32,6 @@ import { RPCProtocol } from '../../common/rpc-protocol'; import { CommandRegistryImpl, CommandsConverter } from '../command-registry'; import { TreeViewSelection } from '../../common'; import { PluginPackage } from '../../common/plugin-protocol'; -import { toInternalCommand } from '../type-converters'; export class TreeViewsExtImpl implements TreeViewsExt { @@ -319,7 +318,7 @@ class TreeViewExtImpl implements Disposable { tooltip: treeItem.tooltip, collapsibleState: treeItem.collapsibleState, contextValue: treeItem.contextValue, - command: treeItem.command ? toInternalCommand(this.commandsConverter.toSafeCommand(treeItem.command, toDisposeElement)) : undefined + command: this.commandsConverter.toSafeCommand(treeItem.command, toDisposeElement) } as TreeViewItem; treeItems.push(treeViewItem); diff --git a/packages/plugin-ext/src/plugin/type-converters.ts b/packages/plugin-ext/src/plugin/type-converters.ts index 8b3b3ec1ba8c4..6c747414b1f28 100644 --- a/packages/plugin-ext/src/plugin/type-converters.ts +++ b/packages/plugin-ext/src/plugin/type-converters.ts @@ -442,19 +442,6 @@ export function fromDocumentHighlight(documentHighlight: theia.DocumentHighlight }; } -export function toInternalCommand(external: theia.Command): model.Command { - // we're deprecating Command.id, so it has to be optional. - // Existing code will have compiled against a non - optional version of the field, so asserting it to exist is ok - // tslint:disable-next-line: no-any - return KnownCommands.map((external.command || external.id)!, external.arguments, (mappedId: string, mappedArgs: any[]) => - ({ - id: mappedId, - title: external.title || external.label || ' ', - tooltip: external.tooltip, - arguments: mappedArgs - })); -} - export namespace KnownCommands { // tslint:disable: no-any const mappings: { [id: string]: [string, (args: any[] | undefined) => any[] | undefined] } = {}; @@ -463,6 +450,10 @@ export namespace KnownCommands { fromPositionToP, toArrayConversion(fromLocationToL))]; + export function mapped(id: string): boolean { + return !!mappings[id]; + } + export function map(id: string, args: any[] | undefined, toDo: (mappedId: string, mappedArgs: any[] | undefined) => T): T { if (mappings[id]) { return toDo(mappings[id][0], mappings[id][1](args)); diff --git a/packages/scm/src/browser/scm-provider.ts b/packages/scm/src/browser/scm-provider.ts index 4c27cb07ea736..d1b5c94726b0a 100644 --- a/packages/scm/src/browser/scm-provider.ts +++ b/packages/scm/src/browser/scm-provider.ts @@ -65,6 +65,8 @@ export interface ScmCommand { title: string; tooltip?: string; command?: string; + // tslint:disable-next-line:no-any + arguments?: any[]; } export interface ScmCommit {