diff --git a/extensions/typescript/src/features/codeActionProvider.ts b/extensions/typescript/src/features/codeActionProvider.ts index 99441cdb14a..80773371264 100644 --- a/extensions/typescript/src/features/codeActionProvider.ts +++ b/extensions/typescript/src/features/codeActionProvider.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CodeActionProvider, TextDocument, Range, CancellationToken, CodeActionContext, Command, commands, workspace, WorkspaceEdit } from 'vscode'; +import { CodeActionProvider, TextDocument, Range, CancellationToken, CodeActionContext, Command, commands } from 'vscode'; import * as Proto from '../protocol'; import { ITypescriptServiceClient } from '../typescriptService'; -import { tsTextSpanToVsRange, vsRangeToTsFileRange } from '../utils/convert'; +import { vsRangeToTsFileRange } from '../utils/convert'; import FormattingConfigurationManager from './formattingConfigurationManager'; +import { applyCodeAction } from '../utils/codeAction'; interface NumberSet { [key: number]: boolean; @@ -87,30 +88,7 @@ export default class TypeScriptCodeActionProvider implements CodeActionProvider }; } - private async onCodeAction(action: Proto.CodeAction, file: string): Promise { - if (action.changes && action.changes.length) { - const workspaceEdit = new WorkspaceEdit(); - for (const change of action.changes) { - for (const textChange of change.textChanges) { - workspaceEdit.replace(this.client.asUrl(change.fileName), - tsTextSpanToVsRange(textChange), - textChange.newText); - } - } - - if (!(await workspace.applyEdit(workspaceEdit))) { - return false; - } - } - - if (action.commands && action.commands.length) { - for (const command of action.commands) { - const response = await this.client.execute('applyCodeActionCommand', { file, command }); - if (!response || !response.body) { - return false; - } - } - } - return true; + private onCodeAction(action: Proto.CodeAction, file: string): Promise { + return applyCodeAction(this.client, action, file); } } \ No newline at end of file diff --git a/extensions/typescript/src/features/completionItemProvider.ts b/extensions/typescript/src/features/completionItemProvider.ts index 7a8d579260d..2036add07d1 100644 --- a/extensions/typescript/src/features/completionItemProvider.ts +++ b/extensions/typescript/src/features/completionItemProvider.ts @@ -3,17 +3,18 @@ * 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 } from 'vscode'; +import { CompletionItem, TextDocument, Position, CompletionItemKind, CompletionItemProvider, CancellationToken, TextEdit, Range, SnippetString, workspace, ProviderResult, CompletionContext, commands } from 'vscode'; import { ITypescriptServiceClient } from '../typescriptService'; import TypingsStatus from '../utils/typingsStatus'; import * as PConst from '../protocol.const'; -import { CompletionEntry, CompletionsRequestArgs, CompletionDetailsRequestArgs, CompletionEntryDetails } from '../protocol'; +import { CompletionEntry, CompletionsRequestArgs, CompletionDetailsRequestArgs, CompletionEntryDetails, CodeAction } from '../protocol'; import * as Previewer from './previewer'; -import { tsTextSpanToVsRange, vsPositionToTsFileLocation, tsLocationToVsPosition } from '../utils/convert'; +import { tsTextSpanToVsRange, vsPositionToTsFileLocation } from '../utils/convert'; import * as nls from 'vscode-nls'; +import { applyCodeAction } from '../utils/codeAction'; let localize = nls.loadMessageBundle(); class MyCompletionItem extends CompletionItem { @@ -132,6 +133,7 @@ namespace Configuration { } export default class TypeScriptCompletionItemProvider implements CompletionItemProvider { + private readonly commandId: string; private config: Configuration = { useCodeSnippetsOnMethodSuggest: false, @@ -141,8 +143,12 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP constructor( private client: ITypescriptServiceClient, + mode: string, private typingsStatus: TypingsStatus - ) { } + ) { + this.commandId = `_typescript.applyCompletionCodeAction.${mode}`; + commands.registerCommand(this.commandId, this.applyCompletionCodeAction, this); + } public updateConfiguration(): void { // Use shared setting for js and ts @@ -280,20 +286,11 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP item.documentation = Previewer.markdownDocumentation(detail.documentation, detail.tags); if (detail.codeActions && detail.codeActions.length) { - const additionalEdits: TextEdit[] = []; - for (const action of detail.codeActions) { - for (const change of action.changes) { - if (change.fileName !== filepath) { - continue; - } - for (const edit of change.textChanges) { - additionalEdits.push(new TextEdit( - new Range(tsLocationToVsPosition(edit.start), tsLocationToVsPosition(edit.end)), - edit.newText)); - } - } - } - item.additionalTextEdits = additionalEdits; + item.command = { + title: '', + command: this.commandId, + arguments: [filepath, detail.codeActions] + }; } if (detail && this.config.useCodeSnippetsOnMethodSuggest && (item.kind === CompletionItemKind.Function || item.kind === CompletionItemKind.Method)) { @@ -357,4 +354,13 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP return new SnippetString(codeSnippet); } + + private async applyCompletionCodeAction(file: string, codeActions: CodeAction[]): Promise { + for (const action of codeActions) { + if (!(await applyCodeAction(this.client, action, file))) { + return false; + } + } + return true; + } } diff --git a/extensions/typescript/src/typescriptMain.ts b/extensions/typescript/src/typescriptMain.ts index 99e6f08e734..b7f99cb8024 100644 --- a/extensions/typescript/src/typescriptMain.ts +++ b/extensions/typescript/src/typescriptMain.ts @@ -239,7 +239,7 @@ class LanguageProvider { const selector = this.description.modeIds; const config = workspace.getConfiguration(this.id); - const completionItemProvider = new (await import('./features/completionItemProvider')).default(client, this.typingsStatus); + const completionItemProvider = new (await import('./features/completionItemProvider')).default(client, this.description.id, this.typingsStatus); completionItemProvider.updateConfiguration(); this.toUpdateOnConfigurationChanged.push(completionItemProvider); this.disposables.push(languages.registerCompletionItemProvider(selector, completionItemProvider, '.', '"', '\'', '/', '@')); diff --git a/extensions/typescript/src/utils/codeAction.ts b/extensions/typescript/src/utils/codeAction.ts new file mode 100644 index 00000000000..c975a4ef9f2 --- /dev/null +++ b/extensions/typescript/src/utils/codeAction.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { WorkspaceEdit, workspace } from 'vscode'; +import * as Proto from '../protocol'; +import { tsTextSpanToVsRange } from './convert'; +import { ITypescriptServiceClient } from '../typescriptService'; + + +export async function applyCodeAction( + client: ITypescriptServiceClient, + action: Proto.CodeAction, + file: string +): Promise { + if (action.changes && action.changes.length) { + const workspaceEdit = new WorkspaceEdit(); + for (const change of action.changes) { + for (const textChange of change.textChanges) { + workspaceEdit.replace(client.asUrl(change.fileName), + tsTextSpanToVsRange(textChange), + textChange.newText); + } + } + + if (!(await workspace.applyEdit(workspaceEdit))) { + return false; + } + } + + if (action.commands && action.commands.length) { + for (const command of action.commands) { + const response = await client.execute('applyCodeActionCommand', { file, command }); + if (!response || !response.body) { + return false; + } + } + } + return true; +} \ No newline at end of file