mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-21 00:59:03 +01:00
Add TS Code Action Provider (#16299)
* Initial work using ts2.1 and TS Code Actions * Clean up implementation and fix a few issues * Gate provider to ts2.1+ * Switch gate to use 2.1.3 instead * Fix a few null checks * Format after completion
This commit is contained in:
116
extensions/typescript/src/features/codeActionProvider.ts
Normal file
116
extensions/typescript/src/features/codeActionProvider.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { CodeActionProvider, TextDocument, Range, CancellationToken, CodeActionContext, Command, commands, Uri, workspace, WorkspaceEdit, TextEdit } from 'vscode';
|
||||
|
||||
import * as Proto from '../protocol';
|
||||
import { ITypescriptServiceClient } from '../typescriptService';
|
||||
|
||||
interface NumberSet {
|
||||
[key: number]: boolean;
|
||||
}
|
||||
|
||||
interface Source {
|
||||
uri: Uri;
|
||||
version: number;
|
||||
range: Range;
|
||||
}
|
||||
|
||||
export default class TypeScriptCodeActionProvider implements CodeActionProvider {
|
||||
private client: ITypescriptServiceClient;
|
||||
private commandId: string;
|
||||
|
||||
private supportedCodeActions: Promise<NumberSet>;
|
||||
|
||||
constructor(client: ITypescriptServiceClient, modeId: string) {
|
||||
this.client = client;
|
||||
this.commandId = `typescript.codeActions.${modeId}`;
|
||||
this.supportedCodeActions = client.execute('getSupportedCodeFixes', null, undefined)
|
||||
.then(response => response.body || [])
|
||||
.then(codes => {
|
||||
return codes.map(code => +code).filter(code => !isNaN(code));
|
||||
})
|
||||
.then(codes =>
|
||||
codes.reduce((obj, code) => {
|
||||
obj[code] = true;
|
||||
return obj;
|
||||
}, Object.create(null)));
|
||||
|
||||
commands.registerCommand(this.commandId, this.onCodeAction, this);
|
||||
}
|
||||
|
||||
public provideCodeActions(document: TextDocument, range: Range, context: CodeActionContext, token: CancellationToken): Thenable<Command[]> {
|
||||
const file = this.client.asAbsolutePath(document.uri);
|
||||
if (!file) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const source: Source = {
|
||||
uri: document.uri,
|
||||
version: document.version,
|
||||
range: range
|
||||
};
|
||||
return this.getSupportedCodeActions(context)
|
||||
.then(supportedActions => {
|
||||
return this.client.execute('getCodeFixes', {
|
||||
file: file,
|
||||
startLine: range.start.line + 1,
|
||||
endLine: range.end.line + 1,
|
||||
startOffset: range.start.character + 1,
|
||||
endOffset: range.end.character + 1,
|
||||
errorCodes: supportedActions
|
||||
}, token);
|
||||
})
|
||||
.then(response => response.body || [])
|
||||
.then(codeActions => codeActions.map(action => this.actionToEdit(source, action)));
|
||||
}
|
||||
|
||||
private getSupportedCodeActions(context: CodeActionContext): Thenable<number[]> {
|
||||
return this.supportedCodeActions
|
||||
.then(supportedActions => {
|
||||
return context.diagnostics
|
||||
.map(diagnostic => +diagnostic.code)
|
||||
.filter(code => supportedActions[code]);
|
||||
});
|
||||
}
|
||||
|
||||
private actionToEdit(source: Source, action: Proto.CodeAction): Command {
|
||||
const workspaceEdit = new WorkspaceEdit();
|
||||
action.changes.forEach(change => {
|
||||
change.textChanges.forEach(textChange => {
|
||||
workspaceEdit.replace(this.client.asUrl(change.fileName),
|
||||
new Range(
|
||||
textChange.start.line - 1, textChange.start.offset - 1,
|
||||
textChange.end.line - 1, textChange.end.offset - 1),
|
||||
textChange.newText);
|
||||
});
|
||||
});
|
||||
return {
|
||||
title: action.description,
|
||||
command: this.commandId,
|
||||
arguments: [source, workspaceEdit]
|
||||
};
|
||||
}
|
||||
|
||||
private onCodeAction(source: Source, workspaceEdit: WorkspaceEdit) {
|
||||
workspace.applyEdit(workspaceEdit).then(success => {
|
||||
if (!success) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
// TODO: Workaround for https://github.com/Microsoft/TypeScript/issues/12249
|
||||
// apply formatting to the source range until TS returns formatted results
|
||||
return commands.executeCommand('vscode.executeFormatRangeProvider', source.uri, source.range, {}).then((edits: TextEdit[]) => {
|
||||
if (!edits || !edits.length) {
|
||||
return false;
|
||||
}
|
||||
const workspaceEdit = new WorkspaceEdit();
|
||||
workspaceEdit.set(source.uri, edits);
|
||||
return workspace.applyEdit(workspaceEdit);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import FormattingProvider from './features/formattingProvider';
|
||||
import BufferSyncSupport from './features/bufferSyncSupport';
|
||||
import CompletionItemProvider from './features/completionItemProvider';
|
||||
import WorkspaceSymbolProvider from './features/workspaceSymbolProvider';
|
||||
import CodeActionProvider from './features/codeActionProvider';
|
||||
|
||||
import * as VersionStatus from './utils/versionStatus';
|
||||
import * as ProjectStatus from './utils/projectStatus';
|
||||
@@ -164,6 +165,9 @@ class LanguageProvider {
|
||||
languages.registerRenameProvider(selector, renameProvider);
|
||||
languages.registerOnTypeFormattingEditProvider(selector, this.formattingProvider, ';', '}', '\n');
|
||||
languages.registerWorkspaceSymbolProvider(new WorkspaceSymbolProvider(client, modeId));
|
||||
if (client.apiVersion.has213Features()) {
|
||||
languages.registerCodeActionsProvider(selector, new CodeActionProvider(client, modeId));
|
||||
}
|
||||
languages.setLanguageConfiguration(modeId, {
|
||||
indentationRules: {
|
||||
// ^(.*\*/)?\s*\}.*$
|
||||
@@ -427,6 +431,7 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost {
|
||||
let range = new Range(start.line - 1, start.offset - 1, end.line - 1, end.offset - 1);
|
||||
let converted = new Diagnostic(range, text);
|
||||
converted.source = source;
|
||||
converted.code = '' + diagnostic.code;
|
||||
result.push(converted);
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -97,6 +97,8 @@ export interface ITypescriptServiceClient {
|
||||
execute(command: 'reload', args: Proto.ReloadRequestArgs, expectedResult: boolean, token?: CancellationToken): Promise<any>;
|
||||
execute(command: 'compilerOptionsForInferredProjects', args: Proto.SetCompilerOptionsForInferredProjectsArgs, token?: CancellationToken): Promise<any>;
|
||||
execute(command: 'navtree', args: Proto.FileRequestArgs, token?: CancellationToken): Promise<Proto.NavTreeResponse>;
|
||||
execute(command: 'getCodeFixes', args: Proto.CodeFixRequestArgs, token?: CancellationToken): Promise<Proto.GetCodeFixesResponse>;
|
||||
execute(command: 'getSupportedCodeFixes', args: null, token?: CancellationToken): Promise<Proto.GetSupportedCodeFixesResponse>;
|
||||
// execute(command: 'compileOnSaveAffectedFileList', args: Proto.CompileOnSaveEmitFileRequestArgs, token?: CancellationToken): Promise<Proto.CompileOnSaveAffectedFileListResponse>;
|
||||
// execute(command: 'compileOnSaveEmitFile', args: Proto.CompileOnSaveEmitFileRequestArgs, token?: CancellationToken): Promise<any>;
|
||||
execute(command: string, args: any, expectedResult: boolean | CancellationToken, token?: CancellationToken): Promise<any>;
|
||||
|
||||
@@ -670,6 +670,7 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
|
||||
case 'typingsInstalled':
|
||||
let typingsInstalledPayload: Proto.TypingsInstalledTelemetryEventPayload = (telemetryData.payload as Proto.TypingsInstalledTelemetryEventPayload);
|
||||
properties['installedPackages'] = typingsInstalledPayload.installedPackages;
|
||||
|
||||
if (is.defined(typingsInstalledPayload.installSuccess)) {
|
||||
properties['installSuccess'] = typingsInstalledPayload.installSuccess.toString();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user