diff --git a/extensions/typescript/src/features/codeActionProvider.ts b/extensions/typescript/src/features/codeActionProvider.ts index 1c085d65e93..288aad0f122 100644 --- a/extensions/typescript/src/features/codeActionProvider.ts +++ b/extensions/typescript/src/features/codeActionProvider.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CodeActionProvider, TextDocument, Range, CancellationToken, CodeActionContext, Command, commands } from 'vscode'; +import { CodeActionProvider, TextDocument, Range, CancellationToken, CodeActionContext, Command } from 'vscode'; import * as Proto from '../protocol'; import { ITypeScriptServiceClient } from '../typescriptService'; import { vsRangeToTsFileRange } from '../utils/convert'; import FormattingConfigurationManager from './formattingConfigurationManager'; import { applyCodeAction } from '../utils/codeAction'; +import { CommandManager } from '../utils/commandManager'; interface NumberSet { [key: number]: boolean; @@ -23,10 +24,11 @@ export default class TypeScriptCodeActionProvider implements CodeActionProvider constructor( private readonly client: ITypeScriptServiceClient, private readonly formattingConfigurationManager: FormattingConfigurationManager, - mode: string + mode: string, + commandManager: CommandManager ) { this.commandId = `_typescript.applyCodeAction.${mode}`; - commands.registerCommand(this.commandId, this.onCodeAction, this); + commandManager.registerCommand(this.commandId, this.onCodeAction, this); } public async provideCodeActions( diff --git a/extensions/typescript/src/features/completionItemProvider.ts b/extensions/typescript/src/features/completionItemProvider.ts index 376bf8fd3f2..445526ca8ad 100644 --- a/extensions/typescript/src/features/completionItemProvider.ts +++ b/extensions/typescript/src/features/completionItemProvider.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CompletionItem, TextDocument, Position, CompletionItemKind, CompletionItemProvider, CancellationToken, TextEdit, Range, SnippetString, workspace, ProviderResult, CompletionContext, commands, Uri, MarkdownString } from 'vscode'; +import { CompletionItem, TextDocument, Position, CompletionItemKind, CompletionItemProvider, CancellationToken, TextEdit, Range, SnippetString, workspace, ProviderResult, CompletionContext, Uri, MarkdownString } from 'vscode'; import { ITypeScriptServiceClient } from '../typescriptService'; import TypingsStatus from '../utils/typingsStatus'; @@ -16,6 +16,7 @@ import { tsTextSpanToVsRange, vsPositionToTsFileLocation } from '../utils/conver import * as nls from 'vscode-nls'; import { applyCodeAction } from '../utils/codeAction'; import * as languageModeIds from '../utils/languageModeIds'; +import { CommandManager } from '../utils/commandManager'; let localize = nls.loadMessageBundle(); @@ -146,10 +147,11 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP constructor( private client: ITypeScriptServiceClient, mode: string, - private typingsStatus: TypingsStatus + private readonly typingsStatus: TypingsStatus, + commandManager: CommandManager ) { this.commandId = `_typescript.applyCompletionCodeAction.${mode}`; - commands.registerCommand(this.commandId, this.applyCompletionCodeAction, this); + commandManager.registerCommand(this.commandId, this.applyCompletionCodeAction, this); } public async provideCompletionItems( diff --git a/extensions/typescript/src/features/refactorProvider.ts b/extensions/typescript/src/features/refactorProvider.ts index 4a3a3588040..baf21e2485a 100644 --- a/extensions/typescript/src/features/refactorProvider.ts +++ b/extensions/typescript/src/features/refactorProvider.ts @@ -11,6 +11,7 @@ import * as Proto from '../protocol'; import { ITypeScriptServiceClient } from '../typescriptService'; import { tsTextSpanToVsRange, vsRangeToTsFileRange, tsLocationToVsPosition } from '../utils/convert'; import FormattingOptionsManager from './formattingConfigurationManager'; +import { CommandManager } from '../utils/commandManager'; export default class TypeScriptRefactorProvider implements CodeActionProvider { private doRefactorCommandId: string; @@ -19,13 +20,14 @@ export default class TypeScriptRefactorProvider implements CodeActionProvider { constructor( private readonly client: ITypeScriptServiceClient, private formattingOptionsManager: FormattingOptionsManager, - mode: string + mode: string, + commandManager: CommandManager ) { this.doRefactorCommandId = `_typescript.applyRefactoring.${mode}`; this.selectRefactorCommandId = `_typescript.selectRefactoring.${mode}`; - commands.registerCommand(this.doRefactorCommandId, this.doRefactoring, this); - commands.registerCommand(this.selectRefactorCommandId, this.selectRefactoring, this); + commandManager.registerCommand(this.doRefactorCommandId, this.doRefactoring, this); + commandManager.registerCommand(this.selectRefactorCommandId, this.selectRefactoring, this); } public async provideCodeActions( diff --git a/extensions/typescript/src/typescriptMain.ts b/extensions/typescript/src/typescriptMain.ts index feb2e85e356..8b213604a60 100644 --- a/extensions/typescript/src/typescriptMain.ts +++ b/extensions/typescript/src/typescriptMain.ts @@ -36,6 +36,7 @@ import { openOrCreateConfigFile, isImplicitProjectConfigFile } from './utils/tsc import { tsLocationToVsPosition } from './utils/convert'; import FormattingConfigurationManager from './features/formattingConfigurationManager'; import * as languageModeIds from './utils/languageModeIds'; +import { CommandManager } from './utils/commandManager'; interface LanguageDescription { id: string; @@ -62,11 +63,14 @@ const standardLanguageDescriptions: LanguageDescription[] = [ export function activate(context: ExtensionContext): void { const plugins = getContributedTypeScriptServerPlugins(); + const commandManager = new CommandManager(); + context.subscriptions.push(commandManager); + const lazyClientHost = (() => { let clientHost: TypeScriptServiceClientHost | undefined; return () => { if (!clientHost) { - clientHost = new TypeScriptServiceClientHost(standardLanguageDescriptions, context.workspaceState, plugins); + clientHost = new TypeScriptServiceClientHost(standardLanguageDescriptions, context.workspaceState, plugins, commandManager); context.subscriptions.push(clientHost); const host = clientHost; @@ -83,25 +87,25 @@ export function activate(context: ExtensionContext): void { })(); - context.subscriptions.push(commands.registerCommand('typescript.reloadProjects', () => { + commandManager.registerCommand('typescript.reloadProjects', () => { lazyClientHost().reloadProjects(); - })); + }); - context.subscriptions.push(commands.registerCommand('javascript.reloadProjects', () => { + commandManager.registerCommand('javascript.reloadProjects', () => { lazyClientHost().reloadProjects(); - })); + }); - context.subscriptions.push(commands.registerCommand('typescript.selectTypeScriptVersion', () => { + commandManager.registerCommand('typescript.selectTypeScriptVersion', () => { lazyClientHost().serviceClient.onVersionStatusClicked(); - })); + }); - context.subscriptions.push(commands.registerCommand('typescript.openTsServerLog', () => { + commandManager.registerCommand('typescript.openTsServerLog', () => { lazyClientHost().serviceClient.openTsServerLogFile(); - })); + }); - context.subscriptions.push(commands.registerCommand('typescript.restartTsServer', () => { + commandManager.registerCommand('typescript.restartTsServer', () => { lazyClientHost().serviceClient.restartTsServer(); - })); + }); context.subscriptions.push(new TypeScriptTaskProviderManager(() => lazyClientHost().serviceClient)); @@ -111,11 +115,11 @@ export function activate(context: ExtensionContext): void { lazyClientHost().goToProjectConfig(isTypeScript, editor.document.uri); } }; - context.subscriptions.push(commands.registerCommand('typescript.goToProjectConfig', goToProjectConfig.bind(null, true))); - context.subscriptions.push(commands.registerCommand('javascript.goToProjectConfig', goToProjectConfig.bind(null, false))); + commandManager.registerCommand('typescript.goToProjectConfig', goToProjectConfig.bind(null, true)); + commandManager.registerCommand('javascript.goToProjectConfig', goToProjectConfig.bind(null, false)); const jsDocCompletionCommand = new TryCompleteJsDocCommand(() => lazyClientHost().serviceClient); - context.subscriptions.push(commands.registerCommand(TryCompleteJsDocCommand.COMMAND_NAME, jsDocCompletionCommand.tryCompleteJsDoc, jsDocCompletionCommand)); + commandManager.registerCommand(TryCompleteJsDocCommand.COMMAND_NAME, jsDocCompletionCommand.tryCompleteJsDoc, jsDocCompletionCommand); const EMPTY_ELEMENTS: string[] = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr']; @@ -177,7 +181,8 @@ class LanguageProvider { constructor( private readonly client: TypeScriptServiceClient, - private readonly description: LanguageDescription + private readonly description: LanguageDescription, + private readonly commandManager: CommandManager ) { this.formattingOptionsManager = new FormattingConfigurationManager(client); this.bufferSyncSupport = new BufferSyncSupport(client, description.modeIds, { @@ -228,7 +233,7 @@ class LanguageProvider { const selector = this.description.modeIds; const config = workspace.getConfiguration(this.id); - const completionItemProvider = new (await import('./features/completionItemProvider')).default(client, this.description.id, this.typingsStatus); + const completionItemProvider = new (await import('./features/completionItemProvider')).default(client, this.description.id, this.typingsStatus, this.commandManager); this.disposables.push(languages.registerCompletionItemProvider(selector, completionItemProvider, '.', '"', '\'', '/', '@')); this.disposables.push(languages.registerCompletionItemProvider(selector, new (await import('./features/directiveCommentCompletionProvider')).default(client), '@')); @@ -251,8 +256,8 @@ class LanguageProvider { this.disposables.push(languages.registerDocumentSymbolProvider(selector, new (await import('./features/documentSymbolProvider')).default(client))); this.disposables.push(languages.registerSignatureHelpProvider(selector, new (await import('./features/signatureHelpProvider')).default(client), '(', ',')); this.disposables.push(languages.registerRenameProvider(selector, new (await import('./features/renameProvider')).default(client))); - this.disposables.push(languages.registerCodeActionsProvider(selector, new (await import('./features/codeActionProvider')).default(client, this.formattingOptionsManager, this.description.id))); - this.disposables.push(languages.registerCodeActionsProvider(selector, new (await import('./features/refactorProvider')).default(client, this.formattingOptionsManager, this.description.id))); + this.disposables.push(languages.registerCodeActionsProvider(selector, new (await import('./features/codeActionProvider')).default(client, this.formattingOptionsManager, this.description.id, this.commandManager))); + this.disposables.push(languages.registerCodeActionsProvider(selector, new (await import('./features/refactorProvider')).default(client, this.formattingOptionsManager, this.description.id, this.commandManager))); this.registerVersionDependentProviders(); for (const modeId of this.description.modeIds) { @@ -432,7 +437,8 @@ class TypeScriptServiceClientHost implements ITypeScriptServiceClientHost { constructor( descriptions: LanguageDescription[], workspaceState: Memento, - plugins: TypeScriptServerPlugin[] + plugins: TypeScriptServerPlugin[], + private commandManager: CommandManager ) { const handleProjectCreateOrDelete = () => { this.client.execute('reloadProjects', null, false); @@ -457,7 +463,7 @@ class TypeScriptServiceClientHost implements ITypeScriptServiceClientHost { this.languagePerId = new Map(); for (const description of descriptions) { - const manager = new LanguageProvider(this.client, description); + const manager = new LanguageProvider(this.client, description, this.commandManager); this.languages.push(manager); this.disposables.push(manager); this.languagePerId.set(description.id, manager); @@ -481,7 +487,7 @@ class TypeScriptServiceClientHost implements ITypeScriptServiceClientHost { diagnosticSource: 'ts-plugins', isExternal: true }; - const manager = new LanguageProvider(this.client, description); + const manager = new LanguageProvider(this.client, description, this.commandManager); this.languages.push(manager); this.disposables.push(manager); this.languagePerId.set(description.id, manager); diff --git a/extensions/typescript/src/utils/commandManager.ts b/extensions/typescript/src/utils/commandManager.ts new file mode 100644 index 00000000000..1a5b2ced886 --- /dev/null +++ b/extensions/typescript/src/utils/commandManager.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +export interface Command { + readonly id: string; + + execute(...args: any[]): void; +} + +export class CommandManager { + private readonly commands: vscode.Disposable[] = []; + + public dispose() { + while (this.commands.length) { + const obj = this.commands.pop(); + if (obj) { + obj.dispose(); + } + } + } + + public registerCommand(id: string, impl: (...args: any[]) => void, thisArg?: any) { + this.commands.push(vscode.commands.registerCommand(id, impl, thisArg)); + } + + public register(command: Command) { + this.registerCommand(command.id, command.execute, command); + } +} \ No newline at end of file