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

Add nested code actions to csharp extension #6572

Merged
merged 13 commits into from
Nov 17, 2023
Merged
2 changes: 2 additions & 0 deletions l10n/bundle.l10n.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@
"Restore": "Restore",
"Sending request": "Sending request",
"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?",
"Nested Code Action": "Nested Code Action",
"Fix All: ": "Fix All: ",
"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",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
}
},
"defaults": {
"roslyn": "4.9.0-2.23563.2",
"roslyn": "4.9.0-2.23566.1",
"omniSharp": "1.39.10",
"razor": "7.0.0-preview.23528.1",
"razorOmnisharp": "7.0.0-preview.23363.1",
Expand Down
83 changes: 45 additions & 38 deletions src/lsptoolshost/fixAllCodeAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,52 +23,59 @@ export function registerCodeActionFixAllCommands(
);
}

async function registerFixAllResolveCodeAction(
export async function getFixAllResponse(
data: LSPAny,
languageServer: RoslynLanguageServer,
codeActionData: any,
outputChannel: vscode.OutputChannel
) {
if (codeActionData) {
const data = <LSPAny>codeActionData;
const result = await vscode.window.showQuickPick(data.FixAllFlavors, {
placeHolder: vscode.l10n.t('Pick a fix all scope'),
});
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,
};
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.title,
data: data,
scope: result,
};

const response = await languageServer.sendRequest(
RoslynProtocol.CodeActionFixAllResolveRequest.type,
fixAllCodeAction,
token
);
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.');
}
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.');
}
}
}
);
}
);
}

async function registerFixAllResolveCodeAction(
languageServer: RoslynLanguageServer,
codeActionData: any,
outputChannel: vscode.OutputChannel
) {
if (codeActionData) {
const data = <LSPAny>codeActionData;
await getFixAllResponse(data, languageServer, outputChannel);
}
}
111 changes: 111 additions & 0 deletions src/lsptoolshost/nestedCodeAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*---------------------------------------------------------------------------------------------
* 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 { CodeAction, CodeActionResolveRequest, LSPAny } from 'vscode-languageserver-protocol';
import { RoslynLanguageServer } from './roslynLanguageServer';
import { URIConverter, createConverter } from 'vscode-languageclient/lib/common/protocolConverter';
import { UriConverter } from './uriConverter';
import { getFixAllResponse } from './fixAllCodeAction';

export function registerNestedCodeActionCommands(
context: vscode.ExtensionContext,
languageServer: RoslynLanguageServer,
outputChannel: vscode.OutputChannel
) {
context.subscriptions.push(
vscode.commands.registerCommand(
'roslyn.client.nestedCodeAction',
async (request): Promise<void> => registerNestedResolveCodeAction(languageServer, request, outputChannel)
)
);
}

async function registerNestedResolveCodeAction(
languageServer: RoslynLanguageServer,
codeActionData: any,
outputChannel: vscode.OutputChannel
): Promise<void> {
if (codeActionData) {
const data = <LSPAny>codeActionData;
const actions = data.NestedCodeActions;

if (actions.length > 0) {
const codeActionTitles = getCodeActionTitles(actions);
const selectedValue = await vscode.window.showQuickPick(codeActionTitles, {
placeHolder: vscode.l10n.t(data.UniqueIdentifier),
ignoreFocusOut: true,
});
if (selectedValue) {
const selectedAction = selectedValue.codeAction;
if (!selectedAction) {
return;
}

if (selectedAction.data.FixAllFlavors) {
await getFixAllResponse(selectedAction.data, languageServer, outputChannel);
return;
}

await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: vscode.l10n.t('Nested Code Action'),
cancellable: true,
},
async (_, token) => {
const nestedCodeActionResolve: CodeAction = {
title: selectedAction.title,
data: selectedAction.data,
};

const response = await languageServer.sendRequest(
CodeActionResolveRequest.type,
nestedCodeActionResolve,
token
);

if (!response.edit) {
outputChannel.show();
outputChannel.appendLine(`Failed to make an edit for completion.`);
throw new Error('Tried to retrieve a code action edit, but an error occurred.');
}

const uriConverter: URIConverter = (value: string): vscode.Uri =>
dibarbet marked this conversation as resolved.
Show resolved Hide resolved
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.nestedCodeAction]';
const errorMessage = 'Failed to make am edit for completion.';
outputChannel.show();
outputChannel.appendLine(`${componentName} ${errorMessage}`);
throw new Error('Tried to insert code action edit, but an error occurred.');
}
}
);
}
}
}
}

function getCodeActionTitles(nestedActions: any): any[] {
return nestedActions.map((nestedAction: { data: { CodeActionPath: any; FixAllFlavors: any } }) => {
const codeActionPath = nestedAction.data.CodeActionPath;
const fixAllFlavors = nestedAction.data.FixAllFlavors;
// If there's only one string, return it directly
if (codeActionPath.length === 1) {
return codeActionPath;
}

// Concatenate multiple strings with " ->"
const concatenatedString = codeActionPath.slice(1).join(' -> ');
const fixAllString = vscode.l10n.t('Fix All: ');
return {
label: fixAllFlavors ? `${fixAllString}${concatenatedString}` : concatenatedString,
codeAction: nestedAction,
};
});
}
3 changes: 2 additions & 1 deletion src/lsptoolshost/roslynLanguageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import { registerCodeActionFixAllCommands } from './fixAllCodeAction';
import { commonOptions, languageServerOptions, omnisharpOptions } from '../shared/options';
import { NamedPipeInformation } from './roslynProtocol';
import { IDisposable } from '../disposable';
import { registerNestedCodeActionCommands } from './nestedCodeAction';
import { registerRestoreCommands } from './restore';
import { BuildDiagnosticsService } from './buildDiagnosticsService';

Expand Down Expand Up @@ -888,7 +889,7 @@ export async function activateRoslynLanguageServer(

// Register any commands that need to be handled by the extension.
registerCommands(context, languageServer, hostExecutableResolver, _channel);

registerNestedCodeActionCommands(context, languageServer, _channel);
registerCodeActionFixAllCommands(context, languageServer, _channel);

registerRazorCommands(context, languageServer);
Expand Down
Loading