about summary refs log tree commit diff
path: root/src/tools/rust-analyzer/editors/code
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rust-analyzer/editors/code')
-rw-r--r--src/tools/rust-analyzer/editors/code/package.json10
-rw-r--r--src/tools/rust-analyzer/editors/code/src/client.ts114
-rw-r--r--src/tools/rust-analyzer/editors/code/src/commands.ts4
-rw-r--r--src/tools/rust-analyzer/editors/code/src/main.ts2
-rw-r--r--src/tools/rust-analyzer/editors/code/src/run.ts57
5 files changed, 129 insertions, 58 deletions
diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json
index 26a21c1468d..3cb4c21ee1f 100644
--- a/src/tools/rust-analyzer/editors/code/package.json
+++ b/src/tools/rust-analyzer/editors/code/package.json
@@ -1532,6 +1532,16 @@
             {
                 "title": "highlightRelated",
                 "properties": {
+                    "rust-analyzer.highlightRelated.branchExitPoints.enable": {
+                        "markdownDescription": "Enables highlighting of related return values while the cursor is on any `match`, `if`, or match arm arrow (`=>`).",
+                        "default": true,
+                        "type": "boolean"
+                    }
+                }
+            },
+            {
+                "title": "highlightRelated",
+                "properties": {
                     "rust-analyzer.highlightRelated.breakPoints.enable": {
                         "markdownDescription": "Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.",
                         "default": true,
diff --git a/src/tools/rust-analyzer/editors/code/src/client.ts b/src/tools/rust-analyzer/editors/code/src/client.ts
index cdeea7333a6..073ff2f4703 100644
--- a/src/tools/rust-analyzer/editors/code/src/client.ts
+++ b/src/tools/rust-analyzer/editors/code/src/client.ts
@@ -3,7 +3,7 @@ import * as lc from "vscode-languageclient/node";
 import * as vscode from "vscode";
 import * as ra from "../src/lsp_ext";
 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 { WorkspaceEdit } from "vscode";
 import { type Config, prepareVSCodeConfig } from "./config";
@@ -188,11 +188,17 @@ export async function createClient(
                 context: await client.code2ProtocolConverter.asCodeActionContext(context, token),
             };
             const callback = async (
-                values: (lc.Command | lc.CodeAction)[] | null,
+                values: (lc.Command | lc.CodeAction | object)[] | null,
             ): Promise<(vscode.Command | vscode.CodeAction)[] | undefined> => {
                 if (values === null) return undefined;
                 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) {
                     // In our case we expect to get code edits only from diagnostics
                     if (lc.CodeAction.is(item)) {
@@ -204,62 +210,55 @@ export async function createClient(
                         result.push(action);
                         continue;
                     }
-                    assert(
-                        isCodeActionWithoutEditsAndCommands(item),
-                        "We don't expect edits or commands here",
-                    );
-                    // 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],
-                    };
+                    assertIsCodeActionWithoutEditsAndCommands(item);
+                    const kind = client.protocol2CodeConverter.asCodeActionKind(item.kind);
+                    const group = item.group;
 
-                    // Set a dummy edit, so that VS Code doesn't try to resolve this.
-                    action.edit = new WorkspaceEdit();
+                    const mkAction = () => {
+                        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) {
                         let entry = groups.get(group);
                         if (!entry) {
-                            entry = { index: result.length, items: [] };
+                            entry = { primary: mkAction(), items: [] };
                             groups.set(group, entry);
-                            result.push(action);
+                        } else {
+                            entry.items.push({
+                                label: item.title,
+                                arguments: item,
+                            });
                         }
-                        entry.items.push(action);
                     } else {
-                        result.push(action);
+                        result.push(mkAction());
                     }
                 }
-                for (const [group, { index, items }] of groups) {
-                    if (items.length === 1) {
-                        const item = unwrapUndefinable(items[0]);
-                        result[index] = item;
-                    } else {
-                        const action = new vscode.CodeAction(group);
-                        const item = unwrapUndefinable(items[0]);
-                        action.kind = item.kind;
-                        action.command = {
+                for (const [group, { items, primary }] of groups) {
+                    // This group contains more than one item, so rewrite it to be a group action
+                    if (items.length !== 0) {
+                        const args = [
+                            {
+                                label: primary.title,
+                                arguments: primary.command!.arguments![0],
+                            },
+                            ...items,
+                        ];
+                        primary.title = group;
+                        primary.command = {
                             command: "rust-analyzer.applyActionGroup",
                             title: "",
-                            arguments: [
-                                items.map((item) => {
-                                    return {
-                                        label: item.title,
-                                        arguments: item.command!.arguments![0],
-                                    };
-                                }),
-                            ],
+                            arguments: [args],
                         };
-
-                        // 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;
             };
@@ -363,17 +362,22 @@ class OverrideFeatures implements lc.StaticFeature {
     clear(): void {}
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-function isCodeActionWithoutEditsAndCommands(value: any): boolean {
-    const candidate: lc.CodeAction = value;
-    return (
+function assertIsCodeActionWithoutEditsAndCommands(
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    candidate: any,
+): asserts candidate is lc.CodeAction & {
+    group?: string;
+} {
+    assert(
         candidate &&
-        Is.string(candidate.title) &&
-        (candidate.diagnostics === void 0 ||
-            Is.typedArray(candidate.diagnostics, lc.Diagnostic.is)) &&
-        (candidate.kind === void 0 || Is.string(candidate.kind)) &&
-        candidate.edit === void 0 &&
-        candidate.command === void 0
+            Is.string(candidate.title) &&
+            (candidate.diagnostics === undefined ||
+                Is.typedArray(candidate.diagnostics, lc.Diagnostic.is)) &&
+            (candidate.group === undefined || Is.string(candidate.group)) &&
+            (candidate.kind === undefined || Is.string(candidate.kind)) &&
+            candidate.edit === undefined &&
+            candidate.command === undefined,
+        `Expected a CodeAction without edits or commands, got: ${JSON.stringify(candidate)}`,
     );
 }
 
diff --git a/src/tools/rust-analyzer/editors/code/src/commands.ts b/src/tools/rust-analyzer/editors/code/src/commands.ts
index 3ac1a933d9e..25b30013fa1 100644
--- a/src/tools/rust-analyzer/editors/code/src/commands.ts
+++ b/src/tools/rust-analyzer/editors/code/src/commands.ts
@@ -1114,11 +1114,11 @@ export function applySnippetWorkspaceEditCommand(_ctx: CtxInit): Cmd {
     };
 }
 
-export function run(ctx: CtxInit): Cmd {
+export function run(ctx: CtxInit, mode?: "cursor"): Cmd {
     let prevRunnable: RunnableQuickPick | undefined;
 
     return async () => {
-        const item = await selectRunnable(ctx, prevRunnable);
+        const item = await selectRunnable(ctx, prevRunnable, false, true, mode);
         if (!item) return;
 
         item.detail = "rerun";
diff --git a/src/tools/rust-analyzer/editors/code/src/main.ts b/src/tools/rust-analyzer/editors/code/src/main.ts
index 5e500730693..996298524f1 100644
--- a/src/tools/rust-analyzer/editors/code/src/main.ts
+++ b/src/tools/rust-analyzer/editors/code/src/main.ts
@@ -167,7 +167,7 @@ function createCommands(): Record<string, CommandFactory> {
         viewCrateGraph: { enabled: commands.viewCrateGraph },
         viewFullCrateGraph: { enabled: commands.viewFullCrateGraph },
         expandMacro: { enabled: commands.expandMacro },
-        run: { enabled: commands.run },
+        run: { enabled: (ctx) => (mode?: "cursor") => commands.run(ctx, mode)() },
         copyRunCommandLine: { enabled: commands.copyRunCommandLine },
         debug: { enabled: commands.debug },
         newDebugConfig: { enabled: commands.newDebugConfig },
diff --git a/src/tools/rust-analyzer/editors/code/src/run.ts b/src/tools/rust-analyzer/editors/code/src/run.ts
index 40027cc7c85..95166c427b2 100644
--- a/src/tools/rust-analyzer/editors/code/src/run.ts
+++ b/src/tools/rust-analyzer/editors/code/src/run.ts
@@ -18,10 +18,15 @@ export async function selectRunnable(
     prevRunnable?: RunnableQuickPick,
     debuggeeOnly = false,
     showButtons: boolean = true,
+    mode?: "cursor",
 ): Promise<RunnableQuickPick | undefined> {
     const editor = ctx.activeRustEditor ?? ctx.activeCargoTomlEditor;
     if (!editor) return;
 
+    if (mode === "cursor") {
+        return selectRunnableAtCursor(ctx, editor, prevRunnable);
+    }
+
     // show a placeholder while we get the runnables from the server
     const quickPick = vscode.window.createQuickPick();
     quickPick.title = "Select Runnable";
@@ -54,6 +59,58 @@ export async function selectRunnable(
     );
 }
 
+async function selectRunnableAtCursor(
+    ctx: CtxInit,
+    editor: RustEditor,
+    prevRunnable?: RunnableQuickPick,
+): Promise<RunnableQuickPick | undefined> {
+    const runnableQuickPicks = await getRunnables(ctx.client, editor, prevRunnable, false);
+    let runnableQuickPickAtCursor = null;
+    const cursorPosition = ctx.client.code2ProtocolConverter.asPosition(editor.selection.active);
+    for (const runnableQuickPick of runnableQuickPicks) {
+        if (!runnableQuickPick.runnable.location?.targetRange) {
+            continue;
+        }
+        const runnableQuickPickRange = runnableQuickPick.runnable.location.targetRange;
+        if (
+            runnableQuickPickAtCursor?.runnable?.location?.targetRange != null &&
+            rangeContainsOtherRange(
+                runnableQuickPickRange,
+                runnableQuickPickAtCursor.runnable.location.targetRange,
+            )
+        ) {
+            continue;
+        }
+        if (rangeContainsPosition(runnableQuickPickRange, cursorPosition)) {
+            runnableQuickPickAtCursor = runnableQuickPick;
+        }
+    }
+    if (runnableQuickPickAtCursor == null) {
+        return;
+    }
+    return Promise.resolve(runnableQuickPickAtCursor);
+}
+
+function rangeContainsPosition(range: lc.Range, position: lc.Position): boolean {
+    return (
+        (position.line > range.start.line ||
+            (position.line === range.start.line && position.character >= range.start.character)) &&
+        (position.line < range.end.line ||
+            (position.line === range.end.line && position.character <= range.end.character))
+    );
+}
+
+function rangeContainsOtherRange(range: lc.Range, otherRange: lc.Range) {
+    return (
+        (range.start.line < otherRange.start.line ||
+            (range.start.line === otherRange.start.line &&
+                range.start.character <= otherRange.start.character)) &&
+        (range.end.line > otherRange.end.line ||
+            (range.end.line === otherRange.end.line &&
+                range.end.character >= otherRange.end.character))
+    );
+}
+
 export class RunnableQuickPick implements vscode.QuickPickItem {
     public label: string;
     public description?: string | undefined;