mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-02 14:31:31 +01:00
Make sure we send format requests before code actions / refactor
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user