|  | import * as vscode from "vscode"; | 
|  | import type * as lc from "vscode-languageclient"; | 
|  | import * as ra from "./lsp_ext"; | 
|  | import * as tasks from "./tasks"; | 
|  |  | 
|  | import type { CtxInit } from "./ctx"; | 
|  | import { makeDebugConfig } from "./debug"; | 
|  | import type { Config } from "./config"; | 
|  | import type { LanguageClient } from "vscode-languageclient/node"; | 
|  | import { Env, log, unwrapUndefinable, type RustEditor } from "./util"; | 
|  |  | 
|  | const quickPickButtons = [ | 
|  | { iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configuration." }, | 
|  | ]; | 
|  |  | 
|  | export async function selectRunnable( | 
|  | ctx: CtxInit, | 
|  | 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"; | 
|  | if (showButtons) { | 
|  | quickPick.buttons = quickPickButtons; | 
|  | } | 
|  | quickPick.items = [{ label: "Looking for runnables..." }]; | 
|  | quickPick.activeItems = []; | 
|  | quickPick.show(); | 
|  |  | 
|  | const runnables = await getRunnables(ctx.client, editor, prevRunnable, debuggeeOnly); | 
|  |  | 
|  | if (runnables.length === 0) { | 
|  | // it is the debug case, run always has at least 'cargo check ...' | 
|  | // see crates\rust-analyzer\src\handlers\request.rs, handle_runnables | 
|  | await vscode.window.showErrorMessage("There's no debug target!"); | 
|  | quickPick.dispose(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // clear the list before we hook up listeners to avoid invoking them | 
|  | // if the user happens to accept the placeholder item | 
|  | quickPick.items = []; | 
|  |  | 
|  | return await populateAndGetSelection( | 
|  | quickPick as vscode.QuickPick<RunnableQuickPick>, | 
|  | runnables, | 
|  | ctx, | 
|  | showButtons, | 
|  | ); | 
|  | } | 
|  |  | 
|  | 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; | 
|  | public detail?: string | undefined; | 
|  | public picked?: boolean | undefined; | 
|  |  | 
|  | constructor(public runnable: ra.Runnable) { | 
|  | this.label = runnable.label; | 
|  | } | 
|  | } | 
|  |  | 
|  | export function prepareBaseEnv(inheritEnv: boolean, base?: Env): Env { | 
|  | const env: Env = { RUST_BACKTRACE: "short" }; | 
|  | if (inheritEnv) { | 
|  | Object.assign(env, process.env); | 
|  | } | 
|  | if (base) { | 
|  | Object.assign(env, base); | 
|  | } | 
|  | return env; | 
|  | } | 
|  |  | 
|  | export function prepareEnv(inheritEnv: boolean, runnableEnv?: Env, runnableEnvCfg?: Env): Env { | 
|  | const env = prepareBaseEnv(inheritEnv, runnableEnv); | 
|  |  | 
|  | if (runnableEnvCfg) { | 
|  | Object.assign(env, runnableEnvCfg); | 
|  | } | 
|  |  | 
|  | return env; | 
|  | } | 
|  |  | 
|  | export async function createTaskFromRunnable( | 
|  | runnable: ra.Runnable, | 
|  | config: Config, | 
|  | ): Promise<vscode.Task> { | 
|  | const target = vscode.workspace.workspaceFolders?.[0]; | 
|  |  | 
|  | let definition: tasks.TaskDefinition; | 
|  | let options; | 
|  | let cargo = "cargo"; | 
|  | if (runnable.kind === "cargo") { | 
|  | const runnableArgs = runnable.args; | 
|  | let args = createCargoArgs(runnableArgs); | 
|  |  | 
|  | if (runnableArgs.overrideCargo) { | 
|  | // Split on spaces to allow overrides like "wrapper cargo". | 
|  | const cargoParts = runnableArgs.overrideCargo.split(" "); | 
|  |  | 
|  | cargo = unwrapUndefinable(cargoParts[0]); | 
|  | args = [...cargoParts.slice(1), ...args]; | 
|  | } | 
|  |  | 
|  | definition = { | 
|  | type: tasks.CARGO_TASK_TYPE, | 
|  | command: unwrapUndefinable(args[0]), | 
|  | args: args.slice(1), | 
|  | }; | 
|  | options = { | 
|  | cwd: runnableArgs.workspaceRoot || ".", | 
|  | env: prepareEnv( | 
|  | true, | 
|  | runnableArgs.environment, | 
|  | config.runnablesExtraEnv(runnable.label), | 
|  | ), | 
|  | }; | 
|  | } else { | 
|  | const runnableArgs = runnable.args; | 
|  | definition = { | 
|  | type: tasks.SHELL_TASK_TYPE, | 
|  | command: runnableArgs.program, | 
|  | args: runnableArgs.args, | 
|  | }; | 
|  | options = { | 
|  | cwd: runnableArgs.cwd, | 
|  | env: prepareBaseEnv(true), | 
|  | }; | 
|  | } | 
|  |  | 
|  | const exec = await tasks.targetToExecution(definition, options, cargo); | 
|  | const task = await tasks.buildRustTask( | 
|  | target, | 
|  | definition, | 
|  | runnable.label, | 
|  | config.problemMatcher, | 
|  | exec, | 
|  | ); | 
|  |  | 
|  | task.presentationOptions.clear = true; | 
|  | // Sadly, this doesn't prevent focus stealing if the terminal is currently | 
|  | // hidden, and will become revealed due to task execution. | 
|  | task.presentationOptions.focus = false; | 
|  |  | 
|  | return task; | 
|  | } | 
|  |  | 
|  | export function createCargoArgs(runnableArgs: ra.CargoRunnableArgs): string[] { | 
|  | const args = [...runnableArgs.cargoArgs]; // should be a copy! | 
|  | if (runnableArgs.executableArgs.length > 0) { | 
|  | args.push("--", ...runnableArgs.executableArgs); | 
|  | } | 
|  | return args; | 
|  | } | 
|  |  | 
|  | async function getRunnables( | 
|  | client: LanguageClient, | 
|  | editor: RustEditor, | 
|  | prevRunnable?: RunnableQuickPick, | 
|  | debuggeeOnly = false, | 
|  | ): Promise<RunnableQuickPick[]> { | 
|  | const textDocument: lc.TextDocumentIdentifier = { | 
|  | uri: editor.document.uri.toString(), | 
|  | }; | 
|  |  | 
|  | const runnables = await client | 
|  | .sendRequest(ra.runnables, { | 
|  | textDocument, | 
|  | position: client.code2ProtocolConverter.asPosition(editor.selection.active), | 
|  | }) | 
|  | .catch((err) => { | 
|  | // If this command is run for a virtual manifest at the workspace root, then this request | 
|  | // will fail as we do not watch this file. | 
|  | log.error(`${err}`); | 
|  | return []; | 
|  | }); | 
|  | const items: RunnableQuickPick[] = []; | 
|  | if (prevRunnable) { | 
|  | items.push(prevRunnable); | 
|  | } | 
|  | for (const r of runnables) { | 
|  | if (prevRunnable && JSON.stringify(prevRunnable.runnable) === JSON.stringify(r)) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (debuggeeOnly && r.label.startsWith("doctest")) { | 
|  | continue; | 
|  | } | 
|  | items.push(new RunnableQuickPick(r)); | 
|  | } | 
|  |  | 
|  | return items; | 
|  | } | 
|  |  | 
|  | async function populateAndGetSelection( | 
|  | quickPick: vscode.QuickPick<RunnableQuickPick>, | 
|  | runnables: RunnableQuickPick[], | 
|  | ctx: CtxInit, | 
|  | showButtons: boolean, | 
|  | ): Promise<RunnableQuickPick | undefined> { | 
|  | return new Promise((resolve) => { | 
|  | const disposables: vscode.Disposable[] = []; | 
|  | const close = (result?: RunnableQuickPick) => { | 
|  | resolve(result); | 
|  | disposables.forEach((d) => d.dispose()); | 
|  | }; | 
|  | disposables.push( | 
|  | quickPick.onDidHide(() => close()), | 
|  | quickPick.onDidAccept(() => close(quickPick.selectedItems[0] as RunnableQuickPick)), | 
|  | quickPick.onDidTriggerButton(async (_button) => { | 
|  | const runnable = unwrapUndefinable( | 
|  | quickPick.activeItems[0] as RunnableQuickPick, | 
|  | ).runnable; | 
|  | await makeDebugConfig(ctx, runnable); | 
|  | close(); | 
|  | }), | 
|  | quickPick.onDidChangeActive((activeList) => { | 
|  | if (showButtons && activeList.length > 0) { | 
|  | const active = unwrapUndefinable(activeList[0]); | 
|  | if (active.label.startsWith("cargo")) { | 
|  | // save button makes no sense for `cargo test` or `cargo check` | 
|  | quickPick.buttons = []; | 
|  | } else if (quickPick.buttons.length === 0) { | 
|  | quickPick.buttons = quickPickButtons; | 
|  | } | 
|  | } | 
|  | }), | 
|  | quickPick, | 
|  | ); | 
|  | // populate the list with the actual runnables | 
|  | quickPick.items = runnables; | 
|  | }); | 
|  | } |