提交 0e4e0d8f 编写于 作者: M Matt Bierner

Make sure we send format requests before code actions / refactor

上级 84605ab7
......@@ -3,23 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CodeActionProvider, TextDocument, Range, CancellationToken, CodeActionContext, Command, commands, Uri, workspace, WorkspaceEdit, TextEdit, FormattingOptions, window } from 'vscode';
import { CodeActionProvider, TextDocument, Range, CancellationToken, CodeActionContext, Command, commands, workspace, WorkspaceEdit } from 'vscode';
import * as Proto from '../protocol';
import { ITypescriptServiceClient } from '../typescriptService';
import { tsTextSpanToVsRange, vsRangeToTsFileRange } from '../utils/convert';
import FormattingConfigurationManager from './formattingConfigurationManager';
interface NumberSet {
[key: number]: boolean;
}
interface Source {
uri: Uri;
version: number;
range: Range;
formattingOptions: FormattingOptions | undefined;
}
export default class TypeScriptCodeActionProvider implements CodeActionProvider {
private commandId: string;
......@@ -27,6 +21,7 @@ export default class TypeScriptCodeActionProvider implements CodeActionProvider
constructor(
private readonly client: ITypescriptServiceClient,
private readonly formattingConfigurationManager: FormattingConfigurationManager,
mode: string
) {
this.commandId = `_typescript.applyCodeAction.${mode}`;
......@@ -53,26 +48,14 @@ export default class TypeScriptCodeActionProvider implements CodeActionProvider
return [];
}
let formattingOptions: FormattingOptions | undefined = undefined;
for (const editor of window.visibleTextEditors) {
if (editor.document.fileName === document.fileName) {
formattingOptions = { tabSize: editor.options.tabSize, insertSpaces: editor.options.insertSpaces } as FormattingOptions;
break;
}
}
await this.formattingConfigurationManager.ensureFormatOptionsForDocument(document, token);
const source: Source = {
uri: document.uri,
version: document.version,
range: range,
formattingOptions: formattingOptions
};
const args: Proto.CodeFixRequestArgs = {
...vsRangeToTsFileRange(file, range),
errorCodes: Array.from(supportedActions)
};
const response = await this.client.execute('getCodeFixes', args, token);
return (response.body || []).map(action => this.getCommandForAction(source, action));
return (response.body || []).map(action => this.getCommandForAction(action));
}
private get supportedCodeActions(): Thenable<NumberSet> {
......@@ -96,15 +79,15 @@ export default class TypeScriptCodeActionProvider implements CodeActionProvider
.filter(code => supportedActions[code])));
}
private getCommandForAction(source: Source, action: Proto.CodeAction): Command {
private getCommandForAction(action: Proto.CodeAction): Command {
return {
title: action.description,
command: this.commandId,
arguments: [source, action]
arguments: [action]
};
}
private async onCodeAction(source: Source, action: Proto.CodeAction): Promise<boolean> {
private async onCodeAction(action: Proto.CodeAction): Promise<boolean> {
const workspaceEdit = new WorkspaceEdit();
for (const change of action.changes) {
for (const textChange of change.textChanges) {
......@@ -114,35 +97,6 @@ export default class TypeScriptCodeActionProvider implements CodeActionProvider
}
}
const success = workspace.applyEdit(workspaceEdit);
if (!success) {
return false;
}
let firstEdit: TextEdit | undefined = undefined;
for (const [uri, edits] of workspaceEdit.entries()) {
if (uri.toString() === source.uri.toString()) {
firstEdit = edits[0];
break;
}
}
if (!firstEdit) {
return true;
}
const newLines = firstEdit.newText.match(/\n/g);
const editedRange = new Range(
firstEdit.range.start.line, 0,
firstEdit.range.end.line + 1 + (newLines ? newLines.length : 0), 0);
// TODO: Workaround for https://github.com/Microsoft/TypeScript/issues/12249
// apply formatting to the source range until TS returns formatted results
const edits = (await commands.executeCommand('vscode.executeFormatRangeProvider', source.uri, editedRange, source.formattingOptions || {})) as TextEdit[];
if (!edits || !edits.length) {
return false;
}
const formattingEdit = new WorkspaceEdit();
formattingEdit.set(source.uri, edits);
return workspace.applyEdit(formattingEdit);
return workspace.applyEdit(workspaceEdit);
}
}
\ No newline at end of file
......@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { workspace as Workspace, FormattingOptions, TextDocument, CancellationToken, WorkspaceConfiguration } from 'vscode';
import { workspace as Workspace, FormattingOptions, TextDocument, CancellationToken, WorkspaceConfiguration, window } from 'vscode';
import * as Proto from '../protocol';
import { ITypescriptServiceClient } from '../typescriptService';
......@@ -58,8 +58,7 @@ namespace FormattingConfiguration {
}
export default class FormattingConfigurationManager {
private jsConfig: FormattingConfiguration = FormattingConfiguration.def;
private tsConfig: FormattingConfiguration = FormattingConfiguration.def;
private config: FormattingConfiguration = FormattingConfiguration.def;
private formatOptions: { [key: string]: Proto.FormatCodeSettings | undefined; } = Object.create(null);
......@@ -76,67 +75,72 @@ export default class FormattingConfigurationManager {
});
}
public async ensureFormatOptionsForDocument(
document: TextDocument,
token: CancellationToken | undefined
): Promise<void> {
for (const editor of window.visibleTextEditors) {
if (editor.document.fileName === document.fileName) {
const formattingOptions = { tabSize: editor.options.tabSize, insertSpaces: editor.options.insertSpaces } as FormattingOptions;
return this.ensureFormatOptions(document, formattingOptions, token);
}
}
}
public async ensureFormatOptions(
document: TextDocument,
options: FormattingOptions,
token: CancellationToken
): Promise<Proto.FormatCodeSettings> {
token: CancellationToken | undefined
): Promise<void> {
const key = document.uri.toString();
const currentOptions = this.formatOptions[key];
if (currentOptions && currentOptions.tabSize === options.tabSize && currentOptions.indentSize === options.tabSize && currentOptions.convertTabsToSpaces === options.insertSpaces) {
return currentOptions;
return;
}
const absPath = this.client.normalizePath(document.uri);
if (!absPath) {
return Object.create(null);
}
const formatOptions = this.getFormatOptions(document, options);
const formatOptions = this.getFormatOptions(options);
const args: Proto.ConfigureRequestArguments = {
file: absPath,
formatOptions: formatOptions
};
await this.client.execute('configure', args, token);
this.formatOptions[key] = formatOptions;
return formatOptions;
}
public updateConfiguration(config: WorkspaceConfiguration): void {
const newJsConfig = config.get('javascript.format', FormattingConfiguration.def);
const newTsConfig = config.get('typeScript.format', FormattingConfiguration.def);
const newConfig = config.get('format', FormattingConfiguration.def);
if (!FormattingConfiguration.equals(this.jsConfig, newJsConfig) || !FormattingConfiguration.equals(this.tsConfig, newTsConfig)) {
if (!FormattingConfiguration.equals(this.config, newConfig)) {
this.formatOptions = Object.create(null);
}
this.jsConfig = newJsConfig;
this.tsConfig = newTsConfig;
this.config = newConfig;
}
private getFormatOptions(
document: TextDocument,
options: FormattingOptions
): Proto.FormatCodeSettings {
const config = document.languageId === 'typescript' || document.languageId === 'typescriptreact' ? this.tsConfig : this.jsConfig;
private getFormatOptions(options: FormattingOptions): Proto.FormatCodeSettings {
return {
tabSize: options.tabSize,
indentSize: options.tabSize,
convertTabsToSpaces: options.insertSpaces,
// We can use \n here since the editor normalizes later on to its line endings.
newLineCharacter: '\n',
insertSpaceAfterCommaDelimiter: config.insertSpaceAfterCommaDelimiter,
insertSpaceAfterConstructor: config.insertSpaceAfterConstructor,
insertSpaceAfterSemicolonInForStatements: config.insertSpaceAfterSemicolonInForStatements,
insertSpaceBeforeAndAfterBinaryOperators: config.insertSpaceBeforeAndAfterBinaryOperators,
insertSpaceAfterKeywordsInControlFlowStatements: config.insertSpaceAfterKeywordsInControlFlowStatements,
insertSpaceAfterFunctionKeywordForAnonymousFunctions: config.insertSpaceAfterFunctionKeywordForAnonymousFunctions,
insertSpaceBeforeFunctionParenthesis: config.insertSpaceBeforeFunctionParenthesis,
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: config.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: config.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: config.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces,
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: config.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces,
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: config.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces,
insertSpaceAfterTypeAssertion: config.insertSpaceAfterTypeAssertion,
placeOpenBraceOnNewLineForFunctions: config.placeOpenBraceOnNewLineForFunctions,
placeOpenBraceOnNewLineForControlBlocks: config.placeOpenBraceOnNewLineForControlBlocks,
insertSpaceAfterCommaDelimiter: this.config.insertSpaceAfterCommaDelimiter,
insertSpaceAfterConstructor: this.config.insertSpaceAfterConstructor,
insertSpaceAfterSemicolonInForStatements: this.config.insertSpaceAfterSemicolonInForStatements,
insertSpaceBeforeAndAfterBinaryOperators: this.config.insertSpaceBeforeAndAfterBinaryOperators,
insertSpaceAfterKeywordsInControlFlowStatements: this.config.insertSpaceAfterKeywordsInControlFlowStatements,
insertSpaceAfterFunctionKeywordForAnonymousFunctions: this.config.insertSpaceAfterFunctionKeywordForAnonymousFunctions,
insertSpaceBeforeFunctionParenthesis: this.config.insertSpaceBeforeFunctionParenthesis,
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: this.config.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: this.config.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: this.config.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces,
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: this.config.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces,
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: this.config.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces,
insertSpaceAfterTypeAssertion: this.config.insertSpaceAfterTypeAssertion,
placeOpenBraceOnNewLineForFunctions: this.config.placeOpenBraceOnNewLineForFunctions,
placeOpenBraceOnNewLineForControlBlocks: this.config.placeOpenBraceOnNewLineForControlBlocks,
};
}
}
......@@ -19,7 +19,7 @@ export class TypeScriptFormattingProvider implements DocumentRangeFormattingEdit
) { }
public updateConfiguration(config: WorkspaceConfiguration): void {
this.enabled = config.get('format.enabled', true);
this.enabled = config.get('format.enable', true);
}
public isEnabled(): boolean {
......
......@@ -10,7 +10,7 @@ import { CodeActionProvider, TextDocument, Range, CancellationToken, CodeActionC
import * as Proto from '../protocol';
import { ITypescriptServiceClient } from '../typescriptService';
import { tsTextSpanToVsRange, vsRangeToTsFileRange, tsLocationToVsPosition } from '../utils/convert';
import FormattingOptionsManager from './formattingConfigurationManager';
export default class TypeScriptRefactorProvider implements CodeActionProvider {
private doRefactorCommandId: string;
......@@ -18,6 +18,7 @@ export default class TypeScriptRefactorProvider implements CodeActionProvider {
constructor(
private readonly client: ITypescriptServiceClient,
private formattingOptionsManager: FormattingOptionsManager,
mode: string
) {
this.doRefactorCommandId = `_typescript.applyRefactoring.${mode}`;
......@@ -55,14 +56,14 @@ export default class TypeScriptRefactorProvider implements CodeActionProvider {
actions.push({
title: info.description,
command: this.selectRefactorCommandId,
arguments: [file, info, range]
arguments: [document, file, info, range]
});
} else {
for (const action of info.actions) {
actions.push({
title: action.description,
command: this.doRefactorCommandId,
arguments: [file, info.name, action.name, range]
arguments: [document, file, info.name, action.name, range]
});
}
}
......@@ -85,7 +86,7 @@ export default class TypeScriptRefactorProvider implements CodeActionProvider {
return workspaceEdit;
}
private async selectRefactoring(file: string, info: Proto.ApplicableRefactorInfo, range: Range): Promise<boolean> {
private async selectRefactoring(document: TextDocument, file: string, info: Proto.ApplicableRefactorInfo, range: Range): Promise<boolean> {
return window.showQuickPick(info.actions.map((action): QuickPickItem => ({
label: action.name,
description: action.description
......@@ -93,11 +94,13 @@ export default class TypeScriptRefactorProvider implements CodeActionProvider {
if (!selected) {
return false;
}
return this.doRefactoring(file, info.name, selected.label, range);
return this.doRefactoring(document, file, info.name, selected.label, range);
});
}
private async doRefactoring(file: string, refactor: string, action: string, range: Range): Promise<boolean> {
private async doRefactoring(document: TextDocument, file: string, refactor: string, action: string, range: Range): Promise<boolean> {
await this.formattingOptionsManager.ensureFormatOptionsForDocument(document, undefined);
const args: Proto.GetEditsForRefactorRequestArgs = {
...vsRangeToTsFileRange(file, range),
refactor,
......
......@@ -267,8 +267,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.description.id)));
this.disposables.push(languages.registerCodeActionsProvider(selector, new (await import('./features/refactorProvider')).default(client, this.description.id)));
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.registerVersionDependentProviders();
for (const modeId of this.description.modeIds) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册