Merge pull request #20104 from Veykril/push-nqnmmlvksyty

Cleanup `provideCodeActions` vscode hook
This commit is contained in:
Lukas Wirth 2025-06-26 08:44:57 +00:00 committed by GitHub
commit 332434aecd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -3,7 +3,7 @@ import * as lc from "vscode-languageclient/node";
import * as vscode from "vscode"; import * as vscode from "vscode";
import * as ra from "../src/lsp_ext"; import * as ra from "../src/lsp_ext";
import * as Is from "vscode-languageclient/lib/common/utils/is"; import * as Is from "vscode-languageclient/lib/common/utils/is";
import { assert, unwrapUndefinable } from "./util"; import { assert } from "./util";
import * as diagnostics from "./diagnostics"; import * as diagnostics from "./diagnostics";
import { WorkspaceEdit } from "vscode"; import { WorkspaceEdit } from "vscode";
import { type Config, prepareVSCodeConfig } from "./config"; import { type Config, prepareVSCodeConfig } from "./config";
@ -188,11 +188,17 @@ export async function createClient(
context: await client.code2ProtocolConverter.asCodeActionContext(context, token), context: await client.code2ProtocolConverter.asCodeActionContext(context, token),
}; };
const callback = async ( const callback = async (
values: (lc.Command | lc.CodeAction)[] | null, values: (lc.Command | lc.CodeAction | object)[] | null,
): Promise<(vscode.Command | vscode.CodeAction)[] | undefined> => { ): Promise<(vscode.Command | vscode.CodeAction)[] | undefined> => {
if (values === null) return undefined; if (values === null) return undefined;
const result: (vscode.CodeAction | vscode.Command)[] = []; const result: (vscode.CodeAction | vscode.Command)[] = [];
const groups = new Map<string, { index: number; items: vscode.CodeAction[] }>(); const groups = new Map<
string,
{
primary: vscode.CodeAction;
items: { label: string; arguments: lc.CodeAction }[];
}
>();
for (const item of values) { for (const item of values) {
// In our case we expect to get code edits only from diagnostics // In our case we expect to get code edits only from diagnostics
if (lc.CodeAction.is(item)) { if (lc.CodeAction.is(item)) {
@ -204,62 +210,55 @@ export async function createClient(
result.push(action); result.push(action);
continue; continue;
} }
assert( assertIsCodeActionWithoutEditsAndCommands(item);
isCodeActionWithoutEditsAndCommands(item), const kind = client.protocol2CodeConverter.asCodeActionKind(item.kind);
"We don't expect edits or commands here", const group = item.group;
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const kind = client.protocol2CodeConverter.asCodeActionKind((item as any).kind);
const action = new vscode.CodeAction(item.title, kind);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const group = (item as any).group;
action.command = {
command: "rust-analyzer.resolveCodeAction",
title: item.title,
arguments: [item],
};
// Set a dummy edit, so that VS Code doesn't try to resolve this. const mkAction = () => {
action.edit = new WorkspaceEdit(); const action = new vscode.CodeAction(item.title, kind);
action.command = {
command: "rust-analyzer.resolveCodeAction",
title: item.title,
arguments: [item],
};
// Set a dummy edit, so that VS Code doesn't try to resolve this.
action.edit = new WorkspaceEdit();
return action;
};
if (group) { if (group) {
let entry = groups.get(group); let entry = groups.get(group);
if (!entry) { if (!entry) {
entry = { index: result.length, items: [] }; entry = { primary: mkAction(), items: [] };
groups.set(group, entry); groups.set(group, entry);
result.push(action); } else {
entry.items.push({
label: item.title,
arguments: item,
});
} }
entry.items.push(action);
} else { } else {
result.push(action); result.push(mkAction());
} }
} }
for (const [group, { index, items }] of groups) { for (const [group, { items, primary }] of groups) {
if (items.length === 1) { // This group contains more than one item, so rewrite it to be a group action
const item = unwrapUndefinable(items[0]); if (items.length !== 0) {
result[index] = item; const args = [
} else { {
const action = new vscode.CodeAction(group); label: primary.title,
const item = unwrapUndefinable(items[0]); arguments: primary.command!.arguments![0],
action.kind = item.kind; },
action.command = { ...items,
];
primary.title = group;
primary.command = {
command: "rust-analyzer.applyActionGroup", command: "rust-analyzer.applyActionGroup",
title: "", title: "",
arguments: [ arguments: [args],
items.map((item) => {
return {
label: item.title,
arguments: item.command!.arguments![0],
};
}),
],
}; };
// Set a dummy edit, so that VS Code doesn't try to resolve this.
action.edit = new WorkspaceEdit();
result[index] = action;
} }
result.push(primary);
} }
return result; return result;
}; };
@ -363,17 +362,22 @@ class OverrideFeatures implements lc.StaticFeature {
clear(): void {} clear(): void {}
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any function assertIsCodeActionWithoutEditsAndCommands(
function isCodeActionWithoutEditsAndCommands(value: any): boolean { // eslint-disable-next-line @typescript-eslint/no-explicit-any
const candidate: lc.CodeAction = value; candidate: any,
return ( ): asserts candidate is lc.CodeAction & {
group?: string;
} {
assert(
candidate && candidate &&
Is.string(candidate.title) && Is.string(candidate.title) &&
(candidate.diagnostics === void 0 || (candidate.diagnostics === undefined ||
Is.typedArray(candidate.diagnostics, lc.Diagnostic.is)) && Is.typedArray(candidate.diagnostics, lc.Diagnostic.is)) &&
(candidate.kind === void 0 || Is.string(candidate.kind)) && (candidate.group === undefined || Is.string(candidate.group)) &&
candidate.edit === void 0 && (candidate.kind === undefined || Is.string(candidate.kind)) &&
candidate.command === void 0 candidate.edit === undefined &&
candidate.command === undefined,
`Expected a CodeAction without edits or commands, got: ${JSON.stringify(candidate)}`,
); );
} }