Skip to content

Commit 92da17c

Browse files
committed
feat: Extend vscode 'run' command with optional mode argument for running test(s) or bin at keyboard cursor
1 parent df50136 commit 92da17c

File tree

3 files changed

+70
-4
lines changed

3 files changed

+70
-4
lines changed

editors/code/src/commands.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,11 +1114,11 @@ export function applySnippetWorkspaceEditCommand(_ctx: CtxInit): Cmd {
11141114
};
11151115
}
11161116

1117-
export function run(ctx: CtxInit): Cmd {
1117+
export function run(ctx: CtxInit, mode?: "cursor"): Cmd {
11181118
let prevRunnable: RunnableQuickPick | undefined;
11191119

11201120
return async () => {
1121-
const item = await selectRunnable(ctx, prevRunnable);
1121+
const item = await selectRunnable(ctx, prevRunnable, false, true, mode);
11221122
if (!item) return;
11231123

11241124
item.detail = "rerun";

editors/code/src/ctx.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as vscode from "vscode";
22
import type * as lc from "vscode-languageclient/node";
33
import * as ra from "./lsp_ext";
4+
import * as commands from "./commands";
45

56
import { Config, prepareVSCodeConfig } from "./config";
67
import { createClient } from "./client";
@@ -462,9 +463,17 @@ export class Ctx implements RustAnalyzerExtensionApi {
462463
for (const [name, factory] of Object.entries(this.commandFactories)) {
463464
const fullName = `rust-analyzer.${name}`;
464465
let callback;
466+
465467
if (isClientRunning(this)) {
466-
// we asserted that `client` is defined
467-
callback = factory.enabled(this);
468+
if (name === "run") {
469+
// Special case: support optional argument for `run`
470+
callback = (mode?: "cursor") => {
471+
return commands.run(this, mode)();
472+
};
473+
} else {
474+
// we asserted that `client` is defined
475+
callback = factory.enabled(this);
476+
}
468477
} else if (factory.disabled) {
469478
callback = factory.disabled(this);
470479
} else {

editors/code/src/run.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,15 @@ export async function selectRunnable(
1818
prevRunnable?: RunnableQuickPick,
1919
debuggeeOnly = false,
2020
showButtons: boolean = true,
21+
mode?: "cursor",
2122
): Promise<RunnableQuickPick | undefined> {
2223
const editor = ctx.activeRustEditor ?? ctx.activeCargoTomlEditor;
2324
if (!editor) return;
2425

26+
if (mode === "cursor") {
27+
return selectRunnableAtCursor(ctx, editor, prevRunnable);
28+
}
29+
2530
// show a placeholder while we get the runnables from the server
2631
const quickPick = vscode.window.createQuickPick();
2732
quickPick.title = "Select Runnable";
@@ -54,6 +59,58 @@ export async function selectRunnable(
5459
);
5560
}
5661

62+
async function selectRunnableAtCursor(
63+
ctx: CtxInit,
64+
editor: RustEditor,
65+
prevRunnable?: RunnableQuickPick,
66+
): Promise<RunnableQuickPick | undefined> {
67+
const runnableQuickPicks = await getRunnables(ctx.client, editor, prevRunnable, false);
68+
let runnableQuickPickAtCursor = null;
69+
const cursorPosition = ctx.client.code2ProtocolConverter.asPosition(editor.selection.active);
70+
for (const runnableQuickPick of runnableQuickPicks) {
71+
if (!runnableQuickPick.runnable.location?.targetRange) {
72+
continue;
73+
}
74+
const runnableQuickPickRange = runnableQuickPick.runnable.location.targetRange;
75+
if (
76+
runnableQuickPickAtCursor?.runnable?.location?.targetRange != null &&
77+
rangeContainsOtherRange(
78+
runnableQuickPickRange,
79+
runnableQuickPickAtCursor.runnable.location.targetRange,
80+
)
81+
) {
82+
continue;
83+
}
84+
if (rangeContainsPosition(runnableQuickPickRange, cursorPosition)) {
85+
runnableQuickPickAtCursor = runnableQuickPick;
86+
}
87+
}
88+
if (runnableQuickPickAtCursor == null) {
89+
return;
90+
}
91+
return Promise.resolve(runnableQuickPickAtCursor);
92+
}
93+
94+
function rangeContainsPosition(range: lc.Range, position: lc.Position): boolean {
95+
return (
96+
(position.line > range.start.line ||
97+
(position.line === range.start.line && position.character >= range.start.character)) &&
98+
(position.line < range.end.line ||
99+
(position.line === range.end.line && position.character <= range.end.character))
100+
);
101+
}
102+
103+
function rangeContainsOtherRange(range: lc.Range, otherRange: lc.Range) {
104+
return (
105+
(range.start.line < otherRange.start.line ||
106+
(range.start.line === otherRange.start.line &&
107+
range.start.character <= otherRange.start.character)) &&
108+
(range.end.line > otherRange.end.line ||
109+
(range.end.line === otherRange.end.line &&
110+
range.end.character >= otherRange.end.character))
111+
);
112+
}
113+
57114
export class RunnableQuickPick implements vscode.QuickPickItem {
58115
public label: string;
59116
public description?: string | undefined;

0 commit comments

Comments
 (0)