Skip to content

Commit c68964c

Browse files
authored
Add nested code actions to csharp extension (#6572)
* version * wip * wip * comments * fixes * feedback * fixes * feedback * feedbacK * updated based on roslyn side * update roslyn version
1 parent 1755b7b commit c68964c

File tree

5 files changed

+161
-40
lines changed

5 files changed

+161
-40
lines changed

l10n/bundle.l10n.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@
136136
"Restore": "Restore",
137137
"Sending request": "Sending request",
138138
"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?",
139+
"Nested Code Action": "Nested Code Action",
140+
"Fix All: ": "Fix All: ",
139141
"Pick a fix all scope": "Pick a fix all scope",
140142
"Fix All Code Action": "Fix All Code Action",
141143
"pipeArgs must be a string or a string array type": "pipeArgs must be a string or a string array type",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
}
3838
},
3939
"defaults": {
40-
"roslyn": "4.9.0-2.23563.2",
40+
"roslyn": "4.9.0-2.23566.1",
4141
"omniSharp": "1.39.10",
4242
"razor": "7.0.0-preview.23528.1",
4343
"razorOmnisharp": "7.0.0-preview.23363.1",

src/lsptoolshost/fixAllCodeAction.ts

Lines changed: 45 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -23,52 +23,59 @@ export function registerCodeActionFixAllCommands(
2323
);
2424
}
2525

26-
async function registerFixAllResolveCodeAction(
26+
export async function getFixAllResponse(
27+
data: LSPAny,
2728
languageServer: RoslynLanguageServer,
28-
codeActionData: any,
2929
outputChannel: vscode.OutputChannel
3030
) {
31-
if (codeActionData) {
32-
const data = <LSPAny>codeActionData;
33-
const result = await vscode.window.showQuickPick(data.FixAllFlavors, {
34-
placeHolder: vscode.l10n.t('Pick a fix all scope'),
35-
});
31+
const result = await vscode.window.showQuickPick(data.FixAllFlavors, {
32+
placeHolder: vscode.l10n.t('Pick a fix all scope'),
33+
});
3634

37-
await vscode.window.withProgress(
38-
{
39-
location: vscode.ProgressLocation.Notification,
40-
title: vscode.l10n.t('Fix All Code Action'),
41-
cancellable: true,
42-
},
43-
async (_, token) => {
44-
if (result) {
45-
const fixAllCodeAction: RoslynProtocol.RoslynFixAllCodeAction = {
46-
title: data.UniqueIdentifier,
47-
data: data,
48-
scope: result,
49-
};
35+
await vscode.window.withProgress(
36+
{
37+
location: vscode.ProgressLocation.Notification,
38+
title: vscode.l10n.t('Fix All Code Action'),
39+
cancellable: true,
40+
},
41+
async (_, token) => {
42+
if (result) {
43+
const fixAllCodeAction: RoslynProtocol.RoslynFixAllCodeAction = {
44+
title: data.title,
45+
data: data,
46+
scope: result,
47+
};
5048

51-
const response = await languageServer.sendRequest(
52-
RoslynProtocol.CodeActionFixAllResolveRequest.type,
53-
fixAllCodeAction,
54-
token
55-
);
49+
const response = await languageServer.sendRequest(
50+
RoslynProtocol.CodeActionFixAllResolveRequest.type,
51+
fixAllCodeAction,
52+
token
53+
);
5654

57-
if (response.edit) {
58-
const uriConverter: URIConverter = (value: string): vscode.Uri =>
59-
UriConverter.deserialize(value);
60-
const protocolConverter = createConverter(uriConverter, true, true);
61-
const fixAllEdit = await protocolConverter.asWorkspaceEdit(response.edit);
62-
if (!(await vscode.workspace.applyEdit(fixAllEdit))) {
63-
const componentName = '[roslyn.client.fixAllCodeAction]';
64-
const errorMessage = 'Failed to make a fix all edit for completion.';
65-
outputChannel.show();
66-
outputChannel.appendLine(`${componentName} ${errorMessage}`);
67-
throw new Error('Tried to insert multiple code action edits, but an error occurred.');
68-
}
55+
if (response.edit) {
56+
const uriConverter: URIConverter = (value: string): vscode.Uri => UriConverter.deserialize(value);
57+
const protocolConverter = createConverter(uriConverter, true, true);
58+
const fixAllEdit = await protocolConverter.asWorkspaceEdit(response.edit);
59+
if (!(await vscode.workspace.applyEdit(fixAllEdit))) {
60+
const componentName = '[roslyn.client.fixAllCodeAction]';
61+
const errorMessage = 'Failed to make a fix all edit for completion.';
62+
outputChannel.show();
63+
outputChannel.appendLine(`${componentName} ${errorMessage}`);
64+
throw new Error('Tried to insert multiple code action edits, but an error occurred.');
6965
}
7066
}
7167
}
72-
);
68+
}
69+
);
70+
}
71+
72+
async function registerFixAllResolveCodeAction(
73+
languageServer: RoslynLanguageServer,
74+
codeActionData: any,
75+
outputChannel: vscode.OutputChannel
76+
) {
77+
if (codeActionData) {
78+
const data = <LSPAny>codeActionData;
79+
await getFixAllResponse(data, languageServer, outputChannel);
7380
}
7481
}

src/lsptoolshost/nestedCodeAction.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
import { CodeAction, CodeActionResolveRequest, LSPAny } from 'vscode-languageserver-protocol';
8+
import { RoslynLanguageServer } from './roslynLanguageServer';
9+
import { URIConverter, createConverter } from 'vscode-languageclient/lib/common/protocolConverter';
10+
import { UriConverter } from './uriConverter';
11+
import { getFixAllResponse } from './fixAllCodeAction';
12+
13+
export function registerNestedCodeActionCommands(
14+
context: vscode.ExtensionContext,
15+
languageServer: RoslynLanguageServer,
16+
outputChannel: vscode.OutputChannel
17+
) {
18+
context.subscriptions.push(
19+
vscode.commands.registerCommand(
20+
'roslyn.client.nestedCodeAction',
21+
async (request): Promise<void> => registerNestedResolveCodeAction(languageServer, request, outputChannel)
22+
)
23+
);
24+
}
25+
26+
async function registerNestedResolveCodeAction(
27+
languageServer: RoslynLanguageServer,
28+
codeActionData: any,
29+
outputChannel: vscode.OutputChannel
30+
): Promise<void> {
31+
if (codeActionData) {
32+
const data = <LSPAny>codeActionData;
33+
const actions = data.NestedCodeActions;
34+
35+
if (actions.length > 0) {
36+
const codeActionTitles = getCodeActionTitles(actions);
37+
const selectedValue = await vscode.window.showQuickPick(codeActionTitles, {
38+
placeHolder: vscode.l10n.t(data.UniqueIdentifier),
39+
ignoreFocusOut: true,
40+
});
41+
if (selectedValue) {
42+
const selectedAction = selectedValue.codeAction;
43+
if (!selectedAction) {
44+
return;
45+
}
46+
47+
if (selectedAction.data.FixAllFlavors) {
48+
await getFixAllResponse(selectedAction.data, languageServer, outputChannel);
49+
return;
50+
}
51+
52+
await vscode.window.withProgress(
53+
{
54+
location: vscode.ProgressLocation.Notification,
55+
title: vscode.l10n.t('Nested Code Action'),
56+
cancellable: true,
57+
},
58+
async (_, token) => {
59+
const nestedCodeActionResolve: CodeAction = {
60+
title: selectedAction.title,
61+
data: selectedAction.data,
62+
};
63+
64+
const response = await languageServer.sendRequest(
65+
CodeActionResolveRequest.type,
66+
nestedCodeActionResolve,
67+
token
68+
);
69+
70+
if (!response.edit) {
71+
outputChannel.show();
72+
outputChannel.appendLine(`Failed to make an edit for completion.`);
73+
throw new Error('Tried to retrieve a code action edit, but an error occurred.');
74+
}
75+
76+
const uriConverter: URIConverter = (value: string): vscode.Uri =>
77+
UriConverter.deserialize(value);
78+
const protocolConverter = createConverter(uriConverter, true, true);
79+
const fixAllEdit = await protocolConverter.asWorkspaceEdit(response.edit);
80+
if (!(await vscode.workspace.applyEdit(fixAllEdit))) {
81+
const componentName = '[roslyn.client.nestedCodeAction]';
82+
const errorMessage = 'Failed to make am edit for completion.';
83+
outputChannel.show();
84+
outputChannel.appendLine(`${componentName} ${errorMessage}`);
85+
throw new Error('Tried to insert code action edit, but an error occurred.');
86+
}
87+
}
88+
);
89+
}
90+
}
91+
}
92+
}
93+
94+
function getCodeActionTitles(nestedActions: any): any[] {
95+
return nestedActions.map((nestedAction: { data: { CodeActionPath: any; FixAllFlavors: any } }) => {
96+
const codeActionPath = nestedAction.data.CodeActionPath;
97+
const fixAllFlavors = nestedAction.data.FixAllFlavors;
98+
// If there's only one string, return it directly
99+
if (codeActionPath.length === 1) {
100+
return codeActionPath;
101+
}
102+
103+
// Concatenate multiple strings with " ->"
104+
const concatenatedString = codeActionPath.slice(1).join(' -> ');
105+
const fixAllString = vscode.l10n.t('Fix All: ');
106+
return {
107+
label: fixAllFlavors ? `${fixAllString}${concatenatedString}` : concatenatedString,
108+
codeAction: nestedAction,
109+
};
110+
});
111+
}

src/lsptoolshost/roslynLanguageServer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import { registerCodeActionFixAllCommands } from './fixAllCodeAction';
5757
import { commonOptions, languageServerOptions, omnisharpOptions } from '../shared/options';
5858
import { NamedPipeInformation } from './roslynProtocol';
5959
import { IDisposable } from '../disposable';
60+
import { registerNestedCodeActionCommands } from './nestedCodeAction';
6061
import { registerRestoreCommands } from './restore';
6162
import { BuildDiagnosticsService } from './buildDiagnosticsService';
6263

@@ -888,7 +889,7 @@ export async function activateRoslynLanguageServer(
888889

889890
// Register any commands that need to be handled by the extension.
890891
registerCommands(context, languageServer, hostExecutableResolver, _channel);
891-
892+
registerNestedCodeActionCommands(context, languageServer, _channel);
892893
registerCodeActionFixAllCommands(context, languageServer, _channel);
893894

894895
registerRazorCommands(context, languageServer);

0 commit comments

Comments
 (0)