diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index a4601403c..859647485 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -128,6 +128,8 @@ "Choose and set default": "Choose and set default", "Do not load any": "Do not load any", "C# configuration has changed. Would you like to reload the window to apply your changes?": "C# configuration has changed. Would you like to reload the window to apply your changes?", + "Pick a fix all scope": "Pick a fix all scope", + "Fix All Code Action": "Fix All Code Action", "pipeArgs must be a string or a string array type": "pipeArgs must be a string or a string array type", "Name not defined in current configuration.": "Name not defined in current configuration.", "Configuration \"{0}\" in launch.json does not have a {1} argument with {2} for remote process listing.": "Configuration \"{0}\" in launch.json does not have a {1} argument with {2} for remote process listing.", diff --git a/src/lsptoolshost/fixAllCodeAction.ts b/src/lsptoolshost/fixAllCodeAction.ts new file mode 100644 index 000000000..aa133d73a --- /dev/null +++ b/src/lsptoolshost/fixAllCodeAction.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * 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'; +import * as RoslynProtocol from './roslynProtocol'; +import { LSPAny } from 'vscode-languageserver-protocol'; +import { RoslynLanguageServer } from './roslynLanguageServer'; +import { URIConverter, createConverter } from 'vscode-languageclient/lib/common/protocolConverter'; +import { UriConverter } from './uriConverter'; + +export function registerCodeActionFixAllCommands( + context: vscode.ExtensionContext, + languageServer: RoslynLanguageServer, + outputChannel: vscode.OutputChannel +) { + context.subscriptions.push( + vscode.commands.registerCommand( + 'roslyn.client.fixAllCodeAction', + async (request): Promise => registerFixAllResolveCodeAction(languageServer, request, outputChannel) + ) + ); +} + +async function registerFixAllResolveCodeAction( + languageServer: RoslynLanguageServer, + codeActionData: any, + outputChannel: vscode.OutputChannel +) { + if (codeActionData) { + const data = codeActionData; + const result = await vscode.window.showQuickPick(data.FixAllFlavors, { + placeHolder: vscode.l10n.t('Pick a fix all scope'), + }); + + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: vscode.l10n.t('Fix All Code Action'), + cancellable: true, + }, + async (_, token) => { + if (result) { + const fixAllCodeAction: RoslynProtocol.RoslynFixAllCodeAction = { + title: data.UniqueIdentifier, + data: data, + scope: result, + }; + + const response = await languageServer.sendRequest( + RoslynProtocol.CodeActionFixAllResolveRequest.type, + fixAllCodeAction, + token + ); + + if (response.edit) { + const uriConverter: URIConverter = (value: string): vscode.Uri => + UriConverter.deserialize(value); + const protocolConverter = createConverter(uriConverter, true, true); + const fixAllEdit = await protocolConverter.asWorkspaceEdit(response.edit); + if (!(await vscode.workspace.applyEdit(fixAllEdit))) { + const componentName = '[roslyn.client.fixAllCodeAction]'; + const errorMessage = 'Failed to make a fix all edit for completion.'; + outputChannel.show(); + outputChannel.appendLine(`${componentName} ${errorMessage}`); + throw new Error('Tried to insert multiple code action edits, but an error occurred.'); + } + } + } + } + ); + } +} diff --git a/src/lsptoolshost/roslynLanguageServer.ts b/src/lsptoolshost/roslynLanguageServer.ts index fe3decc90..946fc3609 100644 --- a/src/lsptoolshost/roslynLanguageServer.ts +++ b/src/lsptoolshost/roslynLanguageServer.ts @@ -52,6 +52,7 @@ import { RoslynLanguageServerEvents } from './languageServerEvents'; import { registerShowToastNotification } from './showToastNotification'; import { registerRazorCommands } from './razorCommands'; import { registerOnAutoInsert } from './onAutoInsert'; +import { registerCodeActionFixAllCommands } from './fixAllCodeAction'; import { commonOptions, languageServerOptions, omnisharpOptions } from '../shared/options'; import { NamedPipeInformation } from './roslynProtocol'; import { IDisposable } from '../disposable'; @@ -862,6 +863,8 @@ export async function activateRoslynLanguageServer( // Register any commands that need to be handled by the extension. registerCommands(context, languageServer, hostExecutableResolver, _channel); + registerCodeActionFixAllCommands(context, languageServer, _channel); + registerRazorCommands(context, languageServer); registerUnitTestingCommands(context, languageServer, dotnetTestChannel); diff --git a/src/lsptoolshost/roslynProtocol.ts b/src/lsptoolshost/roslynProtocol.ts index b5883db50..9e6691468 100644 --- a/src/lsptoolshost/roslynProtocol.ts +++ b/src/lsptoolshost/roslynProtocol.ts @@ -5,6 +5,7 @@ import { Command } from 'vscode'; import * as lsp from 'vscode-languageserver-protocol'; +import { CodeAction } from 'vscode-languageserver-protocol'; import { ProjectConfigurationMessage } from '../shared/projectConfiguration'; export interface WorkspaceDebugConfigurationParams { @@ -141,6 +142,10 @@ export interface BuildOnlyDiagnosticIdsResult { ids: string[]; } +export interface RoslynFixAllCodeAction extends CodeAction { + scope: string; +} + export interface NamedPipeInformation { pipeName: string; } @@ -218,3 +223,9 @@ export namespace BuildOnlyDiagnosticIdsRequest { export const messageDirection: lsp.MessageDirection = lsp.MessageDirection.clientToServer; export const type = new lsp.RequestType0(method); } + +export namespace CodeActionFixAllResolveRequest { + export const method = 'codeAction/resolveFixAll'; + export const messageDirection: lsp.MessageDirection = lsp.MessageDirection.clientToServer; + export const type = new lsp.RequestType(method); +}