Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code Actions - Add fix all support #6310

Merged
merged 10 commits into from
Oct 2, 2023
Merged
44 changes: 44 additions & 0 deletions src/lsptoolshost/roslynLanguageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
RequestType0,
PartialResultParams,
ProtocolRequestType,
CodeAction,
} from 'vscode-languageclient/node';
import { PlatformInformation } from '../shared/platform';
import { readConfigurations } from './configurationMiddleware';
Expand All @@ -45,11 +46,13 @@ import { getDotnetInfo } from '../shared/utils/getDotnetInfo';
import { registerLanguageServerOptionChanges } from './optionChanges';
import { Observable } from 'rxjs';
import { DotnetInfo } from '../shared/utils/dotnetInfo';
import { URIConverter, createConverter } from 'vscode-languageclient/lib/common/protocolConverter';
import { RoslynLanguageServerEvents } from './languageServerEvents';
import { registerShowToastNotification } from './showToastNotification';
import { registerRazorCommands } from './razorCommands';
import { registerOnAutoInsert } from './onAutoInsert';

let _languageServer: RoslynLanguageServer;
let _channel: vscode.OutputChannel;
let _traceChannel: vscode.OutputChannel;

Expand Down Expand Up @@ -202,6 +205,46 @@ export class RoslynLanguageServer {
workspace: {
configuration: (params) => readConfigurations(params),
},
resolveCodeAction: async (codeAction, token, next) => {
akhera99 marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I like the approach you took here, but it is slightly different from what we talked about before. I like this better anyway though, but we can simplify a little bit more.

Since you're directly applying the workspace edit (via vscode.workspace.applyEdit) and not returning a resolved code action to vscode, it would be better to have this implementation just be a command.

Essentially:

  1. On the server side, instead of adding the flavor to the 'data' object, you would return a fix all code action that contains a command (e.g. roslyn.fixAll) with arguments containing whatever flavor information you need to call back to the server as you do now. An example of where we do this is here - https://github.com/dotnet/roslyn/blob/main/src/Features/LanguageServer/Protocol/Handler/CodeLens/CodeLensHandler.cs#L149
  2. Instead of implementing this as part of the resolveCodeAction middleware - you would move this implementation to a command handler. The implementation here basically wouldn't change (minus my other feedback). For example, where the client implements the command I linked above - https://github.com/dotnet/vscode-csharp/blob/main/src/lsptoolshost/unitTesting.ts#L21

Then what happens is that when you select the fix all action in the list - the vscode LSP client will invoke our command handler (based on the command id we returned in the code action)

const lspCodeAction = <CodeAction>codeAction;
const data = lspCodeAction.data;
if (data.FixAllFlavors) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it comes from Roslyn, but it is amusing that it is called FixAll"Flavors" and not "Scopes" or "Kinds".

const result = await vscode.window.showQuickPick(data.FixAllFlavors, {
placeHolder: 'Pick a fix all scope',
akhera99 marked this conversation as resolved.
Show resolved Hide resolved
});

if (result) {
const fixAllCodeAction: RoslynProtocol.RoslynFixAllCodeAction = {
title: lspCodeAction.title,
edit: lspCodeAction.edit,
akhera99 marked this conversation as resolved.
Show resolved Hide resolved
data: data,
scope: result,
};

const response = await _languageServer.sendRequest(
akhera99 marked this conversation as resolved.
Show resolved Hide resolved
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 result = await protocolConverter.asWorkspaceEdit(response.edit);

if (!(await vscode.workspace.applyEdit(result))) {
throw new Error(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after you've finished addressing the other comments (e.g. moving to a command), can you make sure that if this fails it shows an error toast? if it doesn't we might need to explicitly show one.

'Tried to insert multiple code action edits, but an error occurred.'
);
}
}
console.log(response);
akhera99 marked this conversation as resolved.
Show resolved Hide resolved
}
} else {
return await next(codeAction, token);
}
},
},
};

Expand All @@ -224,6 +267,7 @@ export class RoslynLanguageServer {
languageServerEvents
);

_languageServer = server;
// Start the client. This will also launch the server process.
await client.start();
return server;
Expand Down
11 changes: 11 additions & 0 deletions src/lsptoolshost/roslynProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -136,6 +137,10 @@ export interface BuildOnlyDiagnosticIdsResult {
ids: string[];
}

export interface RoslynFixAllCodeAction extends CodeAction {
scope: string;
}

export namespace WorkspaceDebugConfigurationRequest {
export const method = 'workspace/debugConfiguration';
export const messageDirection: lsp.MessageDirection = lsp.MessageDirection.clientToServer;
Expand Down Expand Up @@ -209,3 +214,9 @@ export namespace BuildOnlyDiagnosticIdsRequest {
export const messageDirection: lsp.MessageDirection = lsp.MessageDirection.clientToServer;
export const type = new lsp.RequestType0<BuildOnlyDiagnosticIdsResult, void>(method);
}

export namespace CodeActionFixAllResolveRequest {
export const method = 'codeAction/resolveFixAll';
export const messageDirection: lsp.MessageDirection = lsp.MessageDirection.clientToServer;
export const type = new lsp.RequestType<RoslynFixAllCodeAction, RoslynFixAllCodeAction, void>(method);
}