diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 5cec2c61a5..5048f820e4 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -9,7 +9,12 @@ import { applySnippetTextEdits, type SnippetTextDocumentEdit, } from "./snippets"; -import { type RunnableQuickPick, selectRunnable, createTask, createCargoArgs } from "./run"; +import { + type RunnableQuickPick, + selectRunnable, + createTaskFromRunnable, + createCargoArgs, +} from "./run"; import { AstInspector } from "./ast_inspector"; import { isRustDocument, @@ -1096,7 +1101,7 @@ export function run(ctx: CtxInit): Cmd { item.detail = "rerun"; prevRunnable = item; - const task = await createTask(item.runnable, ctx.config); + const task = await createTaskFromRunnable(item.runnable, ctx.config); return await vscode.tasks.executeTask(task); }; } @@ -1139,7 +1144,7 @@ export function runSingle(ctx: CtxInit): Cmd { const editor = ctx.activeRustEditor; if (!editor) return; - const task = await createTask(runnable, ctx.config); + const task = await createTaskFromRunnable(runnable, ctx.config); task.group = vscode.TaskGroup.Build; task.presentationOptions = { reveal: vscode.TaskRevealKind.Always, diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 2462f06f18..699052e4d4 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -223,8 +223,16 @@ export type OpenCargoTomlParams = { export type Runnable = { label: string; location?: lc.LocationLink; - kind: "cargo" | "shell"; - args: CargoRunnableArgs | ShellRunnableArgs; +} & (RunnableCargo | RunnableShell); + +type RunnableCargo = { + kind: "cargo"; + args: CargoRunnableArgs; +}; + +type RunnableShell = { + kind: "shell"; + args: ShellRunnableArgs; }; export type ShellRunnableArgs = { diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index 52117a442a..1206137b6f 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts @@ -110,10 +110,13 @@ export function prepareEnv( return env; } -export async function createTask(runnable: ra.Runnable, config: Config): Promise { +export async function createTaskFromRunnable( + runnable: ra.Runnable, + config: Config, +): Promise { let definition: tasks.RustTargetDefinition; if (runnable.kind === "cargo") { - const runnableArgs = runnable.args as ra.CargoRunnableArgs; + const runnableArgs = runnable.args; let args = createCargoArgs(runnableArgs); let program: string; @@ -128,17 +131,16 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise } definition = { - type: tasks.TASK_TYPE, + type: tasks.CARGO_TASK_TYPE, command: program, args, cwd: runnableArgs.workspaceRoot || ".", env: prepareEnv(runnable.label, runnableArgs, config.runnablesExtraEnv), }; } else { - const runnableArgs = runnable.args as ra.ShellRunnableArgs; - + const runnableArgs = runnable.args; definition = { - type: "shell", + type: tasks.SHELL_TASK_TYPE, command: runnableArgs.program, args: runnableArgs.args, cwd: runnableArgs.cwd, @@ -148,13 +150,13 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate() + const exec = await tasks.targetToExecution(definition, config.cargoRunner, true); const task = await tasks.buildRustTask( target, definition, runnable.label, config.problemMatcher, - config.cargoRunner, - true, + exec, ); task.presentationOptions.clear = true; diff --git a/editors/code/src/tasks.ts b/editors/code/src/tasks.ts index c14f7a1175..870b1ffb71 100644 --- a/editors/code/src/tasks.ts +++ b/editors/code/src/tasks.ts @@ -1,27 +1,30 @@ import * as vscode from "vscode"; import type { Config } from "./config"; import { log } from "./util"; -import { expectNotUndefined, unwrapUndefinable } from "./undefinable"; +import { unwrapUndefinable } from "./undefinable"; +import * as toolchain from "./toolchain"; // This ends up as the `type` key in tasks.json. RLS also uses `cargo` and // our configuration should be compatible with it so use the same key. -export const TASK_TYPE = "cargo"; +export const CARGO_TASK_TYPE = "cargo"; +export const SHELL_TASK_TYPE = "shell"; -export const TASK_SOURCE = "rust"; +export const RUST_TASK_SOURCE = "rust"; -export interface RustTargetDefinition extends vscode.TaskDefinition { - // The cargo command, such as "run" or "check". +export type RustTargetDefinition = { + readonly type: typeof CARGO_TASK_TYPE | typeof SHELL_TASK_TYPE; +} & vscode.TaskDefinition & + RustTarget; +export type RustTarget = { + // The command to run, usually `cargo`. command: string; - // Additional arguments passed to the cargo command. + // Additional arguments passed to the command. args?: string[]; - // The working directory to run the cargo command in. + // The working directory to run the command in. cwd?: string; // The shell environment. env?: { [key: string]: string }; - // Override the cargo executable name, such as - // "my_custom_cargo_bin". - overrideCargo?: string; -} +}; class RustTaskProvider implements vscode.TaskProvider { private readonly config: Config; @@ -31,6 +34,10 @@ class RustTaskProvider implements vscode.TaskProvider { } async provideTasks(): Promise { + if (!vscode.workspace.workspaceFolders) { + return []; + } + // Detect Rust tasks. Currently we do not do any actual detection // of tasks (e.g. aliases in .cargo/config) and just return a fixed // set of tasks that always exist. These tasks cannot be removed in @@ -45,15 +52,23 @@ class RustTaskProvider implements vscode.TaskProvider { { command: "run", group: undefined }, ]; + // FIXME: The server should provide this + const cargo = await toolchain.cargoPath(); + const tasks: vscode.Task[] = []; - for (const workspaceTarget of vscode.workspace.workspaceFolders || []) { + for (const workspaceTarget of vscode.workspace.workspaceFolders) { for (const def of defs) { + const definition = { + command: cargo, + args: [def.command], + }; + const exec = await targetToExecution(definition, this.config.cargoRunner); const vscodeTask = await buildRustTask( workspaceTarget, - { type: TASK_TYPE, command: def.command }, + { ...definition, type: CARGO_TASK_TYPE }, `cargo ${def.command}`, this.config.problemMatcher, - this.config.cargoRunner, + exec, ); vscodeTask.group = def.group; tasks.push(vscodeTask); @@ -67,16 +82,24 @@ class RustTaskProvider implements vscode.TaskProvider { // VSCode calls this for every cargo task in the user's tasks.json, // we need to inform VSCode how to execute that command by creating // a ShellExecution for it. - - const definition = task.definition as RustTargetDefinition; - - if (definition.type === TASK_TYPE) { + if (task.definition.type === CARGO_TASK_TYPE) { + const taskDefinition = task.definition as RustTargetDefinition; + const cargo = await toolchain.cargoPath(); + const exec = await targetToExecution( + { + command: cargo, + args: [taskDefinition.command].concat(taskDefinition.args || []), + cwd: taskDefinition.cwd, + env: taskDefinition.env, + }, + this.config.cargoRunner, + ); return await buildRustTask( task.scope, - definition, + taskDefinition, task.name, this.config.problemMatcher, - this.config.cargoRunner, + exec, ); } @@ -89,34 +112,31 @@ export async function buildRustTask( definition: RustTargetDefinition, name: string, problemMatcher: string[], - customRunner?: string, - throwOnError: boolean = false, + exec: vscode.ProcessExecution | vscode.ShellExecution, ): Promise { - const exec = await cargoToExecution(definition, customRunner, throwOnError); - return new vscode.Task( definition, // scope can sometimes be undefined. in these situations we default to the workspace taskscope as // recommended by the official docs: https://code.visualstudio.com/api/extension-guides/task-provider#task-provider) scope ?? vscode.TaskScope.Workspace, name, - TASK_SOURCE, + RUST_TASK_SOURCE, exec, problemMatcher, ); } -async function cargoToExecution( - definition: RustTargetDefinition, - customRunner: string | undefined, - throwOnError: boolean, +export async function targetToExecution( + definition: RustTarget, + customRunner?: string, + throwOnError: boolean = false, ): Promise { if (customRunner) { const runnerCommand = `${customRunner}.buildShellExecution`; try { const runnerArgs = { - kind: TASK_TYPE, + kind: CARGO_TASK_TYPE, args: definition.args, cwd: definition.cwd, env: definition.env, @@ -136,37 +156,14 @@ async function cargoToExecution( // fallback to default processing } } - - // this is a cargo task; do Cargo-esque processing - if (definition.type === TASK_TYPE) { - // Check whether we must use a user-defined substitute for cargo. - // Split on spaces to allow overrides like "wrapper cargo". - const cargoCommand = definition.overrideCargo?.split(" ") ?? [definition.command]; - - const definitionArgs = expectNotUndefined( - definition.args, - "args were not provided via runnables; this is a bug.", - ); - const args = [...cargoCommand.slice(1), ...definitionArgs]; - const processName = unwrapUndefinable(cargoCommand[0]); - - return new vscode.ProcessExecution(processName, args, { - cwd: definition.cwd, - env: definition.env, - }); - } else { - // we've been handed a process definition by rust-analyzer, trust all its inputs - // and make a shell execution. - const args = unwrapUndefinable(definition.args); - - return new vscode.ProcessExecution(definition.command, args, { - cwd: definition.cwd, - env: definition.env, - }); - } + const args = unwrapUndefinable(definition.args); + return new vscode.ProcessExecution(definition.command, args, { + cwd: definition.cwd, + env: definition.env, + }); } export function activateTaskProvider(config: Config): vscode.Disposable { const provider = new RustTaskProvider(config); - return vscode.tasks.registerTaskProvider(TASK_TYPE, provider); + return vscode.tasks.registerTaskProvider(CARGO_TASK_TYPE, provider); } diff --git a/editors/code/src/toolchain.ts b/editors/code/src/toolchain.ts index 58e5fc747a..060b0245d2 100644 --- a/editors/code/src/toolchain.ts +++ b/editors/code/src/toolchain.ts @@ -151,6 +151,7 @@ export async function getRustcId(dir: string): Promise { } /** Mirrors `toolchain::cargo()` implementation */ +// FIXME: The server should provide this export function cargoPath(): Promise { return getPathForExecutable("cargo"); }