Skip to content

Commit c27fc0c

Browse files
committed
Auto merge of rust-lang#15896 - minestarks:run-quickpick, r=Veykril
Show placeholder while run command gets runnables from server This PR fixes a UI annoyance in the VS Code extension when working in large codebases where rust-analyzer can take a few moments to interact with the server. Scenario: 1. Invoke "rust-analyzer: Run" from the command palette or hotkey 2. Quickly start typing to filter the list (or press Enter to accept the last runnable) We often do this quickly from muscle memory without waiting to see the picker. The picker often takes several seconds to come up, causing us to type garbage into the currently open editor. Fix: Show a placeholder item before we call out to the server. ![image](https://github.com/rust-lang/rust-analyzer/assets/16928427/09de6a1c-6f3c-4d29-8031-ba4baeb43282) Selecting this item does nothing so if the user accidentally hits Enter nothing happens. The list is populated and the placeholder dismissed when the actual runnables are retrieved. From here the behavior is the same as before. ![image](https://github.com/rust-lang/rust-analyzer/assets/16928427/837c7dfc-c060-4d68-bbf6-df8aa3101b78)
2 parents bc9c952 + ec3f35b commit c27fc0c

File tree

1 file changed

+94
-57
lines changed

1 file changed

+94
-57
lines changed

editors/code/src/run.ts

+94-57
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import type { CtxInit } from "./ctx";
77
import { makeDebugConfig } from "./debug";
88
import type { Config, RunnableEnvCfg, RunnableEnvCfgItem } from "./config";
99
import { unwrapUndefinable } from "./undefinable";
10+
import type { LanguageClient } from "vscode-languageclient/node";
11+
import type { RustEditor } from "./util";
1012

1113
const quickPickButtons = [
1214
{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configuration." },
@@ -21,73 +23,36 @@ export async function selectRunnable(
2123
const editor = ctx.activeRustEditor;
2224
if (!editor) return;
2325

24-
const client = ctx.client;
25-
const textDocument: lc.TextDocumentIdentifier = {
26-
uri: editor.document.uri.toString(),
27-
};
28-
29-
const runnables = await client.sendRequest(ra.runnables, {
30-
textDocument,
31-
position: client.code2ProtocolConverter.asPosition(editor.selection.active),
32-
});
33-
const items: RunnableQuickPick[] = [];
34-
if (prevRunnable) {
35-
items.push(prevRunnable);
26+
// show a placeholder while we get the runnables from the server
27+
const quickPick = vscode.window.createQuickPick();
28+
quickPick.title = "Select Runnable";
29+
if (showButtons) {
30+
quickPick.buttons = quickPickButtons;
3631
}
37-
for (const r of runnables) {
38-
if (prevRunnable && JSON.stringify(prevRunnable.runnable) === JSON.stringify(r)) {
39-
continue;
40-
}
32+
quickPick.items = [{ label: "Looking for runnables..." }];
33+
quickPick.activeItems = [];
34+
quickPick.show();
4135

42-
if (debuggeeOnly && (r.label.startsWith("doctest") || r.label.startsWith("cargo"))) {
43-
continue;
44-
}
45-
items.push(new RunnableQuickPick(r));
46-
}
36+
const runnables = await getRunnables(ctx.client, editor, prevRunnable, debuggeeOnly);
4737

48-
if (items.length === 0) {
38+
if (runnables.length === 0) {
4939
// it is the debug case, run always has at least 'cargo check ...'
5040
// see crates\rust-analyzer\src\main_loop\handlers.rs, handle_runnables
5141
await vscode.window.showErrorMessage("There's no debug target!");
42+
quickPick.dispose();
5243
return;
5344
}
5445

55-
return await new Promise((resolve) => {
56-
const disposables: vscode.Disposable[] = [];
57-
const close = (result?: RunnableQuickPick) => {
58-
resolve(result);
59-
disposables.forEach((d) => d.dispose());
60-
};
46+
// clear the list before we hook up listeners to to avoid invoking them
47+
// if the user happens to accept the placeholder item
48+
quickPick.items = [];
6149

62-
const quickPick = vscode.window.createQuickPick<RunnableQuickPick>();
63-
quickPick.items = items;
64-
quickPick.title = "Select Runnable";
65-
if (showButtons) {
66-
quickPick.buttons = quickPickButtons;
67-
}
68-
disposables.push(
69-
quickPick.onDidHide(() => close()),
70-
quickPick.onDidAccept(() => close(quickPick.selectedItems[0])),
71-
quickPick.onDidTriggerButton(async (_button) => {
72-
const runnable = unwrapUndefinable(quickPick.activeItems[0]).runnable;
73-
await makeDebugConfig(ctx, runnable);
74-
close();
75-
}),
76-
quickPick.onDidChangeActive((activeList) => {
77-
if (showButtons && activeList.length > 0) {
78-
const active = unwrapUndefinable(activeList[0]);
79-
if (active.label.startsWith("cargo")) {
80-
// save button makes no sense for `cargo test` or `cargo check`
81-
quickPick.buttons = [];
82-
} else if (quickPick.buttons.length === 0) {
83-
quickPick.buttons = quickPickButtons;
84-
}
85-
}
86-
}),
87-
quickPick,
88-
);
89-
quickPick.show();
90-
});
50+
return await populateAndGetSelection(
51+
quickPick as vscode.QuickPick<RunnableQuickPick>,
52+
runnables,
53+
ctx,
54+
showButtons,
55+
);
9156
}
9257

9358
export class RunnableQuickPick implements vscode.QuickPickItem {
@@ -187,3 +152,75 @@ export function createArgs(runnable: ra.Runnable): string[] {
187152
}
188153
return args;
189154
}
155+
156+
async function getRunnables(
157+
client: LanguageClient,
158+
editor: RustEditor,
159+
prevRunnable?: RunnableQuickPick,
160+
debuggeeOnly = false,
161+
): Promise<RunnableQuickPick[]> {
162+
const textDocument: lc.TextDocumentIdentifier = {
163+
uri: editor.document.uri.toString(),
164+
};
165+
166+
const runnables = await client.sendRequest(ra.runnables, {
167+
textDocument,
168+
position: client.code2ProtocolConverter.asPosition(editor.selection.active),
169+
});
170+
const items: RunnableQuickPick[] = [];
171+
if (prevRunnable) {
172+
items.push(prevRunnable);
173+
}
174+
for (const r of runnables) {
175+
if (prevRunnable && JSON.stringify(prevRunnable.runnable) === JSON.stringify(r)) {
176+
continue;
177+
}
178+
179+
if (debuggeeOnly && (r.label.startsWith("doctest") || r.label.startsWith("cargo"))) {
180+
continue;
181+
}
182+
items.push(new RunnableQuickPick(r));
183+
}
184+
185+
return items;
186+
}
187+
188+
async function populateAndGetSelection(
189+
quickPick: vscode.QuickPick<RunnableQuickPick>,
190+
runnables: RunnableQuickPick[],
191+
ctx: CtxInit,
192+
showButtons: boolean,
193+
): Promise<RunnableQuickPick | undefined> {
194+
return new Promise((resolve) => {
195+
const disposables: vscode.Disposable[] = [];
196+
const close = (result?: RunnableQuickPick) => {
197+
resolve(result);
198+
disposables.forEach((d) => d.dispose());
199+
};
200+
disposables.push(
201+
quickPick.onDidHide(() => close()),
202+
quickPick.onDidAccept(() => close(quickPick.selectedItems[0] as RunnableQuickPick)),
203+
quickPick.onDidTriggerButton(async (_button) => {
204+
const runnable = unwrapUndefinable(
205+
quickPick.activeItems[0] as RunnableQuickPick,
206+
).runnable;
207+
await makeDebugConfig(ctx, runnable);
208+
close();
209+
}),
210+
quickPick.onDidChangeActive((activeList) => {
211+
if (showButtons && activeList.length > 0) {
212+
const active = unwrapUndefinable(activeList[0]);
213+
if (active.label.startsWith("cargo")) {
214+
// save button makes no sense for `cargo test` or `cargo check`
215+
quickPick.buttons = [];
216+
} else if (quickPick.buttons.length === 0) {
217+
quickPick.buttons = quickPickButtons;
218+
}
219+
}
220+
}),
221+
quickPick,
222+
);
223+
// populate the list with the actual runnables
224+
quickPick.items = runnables;
225+
});
226+
}

0 commit comments

Comments
 (0)