Skip to content

Commit

Permalink
Define a runnable args kind for arbitrary shell commands
Browse files Browse the repository at this point in the history
Co-authored-by: David Barsky <me@davidbarsky.com>
  • Loading branch information
Wilfred and davidbarsky committed Mar 15, 2024
1 parent e493fd4 commit b1ba4a4
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 64 deletions.
15 changes: 13 additions & 2 deletions docs/dev/lsp-extensions.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!---
lsp/ext.rs hash: 61f485497d6e8e88
lsp/ext.rs hash: 469dd2fb4858466d
If you need to change the above hash to make the test pass, please check if you
need to adjust this doc as well and ping this issue:
Expand Down Expand Up @@ -372,7 +372,7 @@ interface Runnable {
}
```

rust-analyzer supports only one `kind`, `"cargo"`. The `args` for `"cargo"` look like this:
rust-analyzer supports two `kind`s of runnables, `"cargo"` and `"shell"`. The `args` for `"cargo"` look like this:

```typescript
{
Expand All @@ -385,6 +385,17 @@ rust-analyzer supports only one `kind`, `"cargo"`. The `args` for `"cargo"` look
}
```

The args for `"shell"` look like this:

```typescript
{
kind: string;
program: string;
args: string[];
cwd: string;
}
```

## Test explorer

**Experimental Client Capability:** `{ "testExplorer": boolean }`
Expand Down
7 changes: 4 additions & 3 deletions editors/code/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import {
type SnippetTextDocumentEdit,
} from "./snippets";
import { spawnSync } from "child_process";
import { type RunnableQuickPick, selectRunnable, createTask, createArgs } from "./run";
import { type RunnableQuickPick, selectRunnable, createTask, createCargoArgs } from "./run";
import { AstInspector } from "./ast_inspector";
import {
isRustDocument,
isCargoRunnableArgs,
isCargoTomlDocument,
sleep,
isRustEditor,
Expand Down Expand Up @@ -1156,8 +1157,8 @@ export function copyRunCommandLine(ctx: CtxInit) {
let prevRunnable: RunnableQuickPick | undefined;
return async () => {
const item = await selectRunnable(ctx, prevRunnable);
if (!item) return;
const args = createArgs(item.runnable);
if (!item || !isCargoRunnableArgs(item.runnable.args)) return;
const args = createCargoArgs(item.runnable.args);
const commandLine = ["cargo", ...args].join(" ");
await vscode.env.clipboard.writeText(commandLine);
await vscode.window.showInformationMessage("Cargo invocation copied to the clipboard.");
Expand Down
42 changes: 28 additions & 14 deletions editors/code/src/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import { Cargo, type ExecutableInfo, getRustcId, getSysroot } from "./toolchain"
import type { Ctx } from "./ctx";
import { prepareEnv } from "./run";
import { unwrapUndefinable } from "./undefinable";
import { isCargoRunnableArgs } from "./util";

const debugOutput = vscode.window.createOutputChannel("Debug");
type DebugConfigProvider = (
config: ra.Runnable,
runnable: ra.Runnable,
runnableArgs: ra.CargoRunnableArgs,
executable: string,
cargoWorkspace: string,
env: Record<string, string>,
Expand Down Expand Up @@ -77,6 +79,11 @@ async function getDebugConfiguration(
ctx: Ctx,
runnable: ra.Runnable,
): Promise<vscode.DebugConfiguration | undefined> {
if (!isCargoRunnableArgs(runnable.args)) {
return;
}
const runnableArgs: ra.CargoRunnableArgs = runnable.args;

const editor = ctx.activeRustEditor;
if (!editor) return;

Expand Down Expand Up @@ -120,9 +127,9 @@ async function getDebugConfiguration(
const isMultiFolderWorkspace = workspaceFolders.length > 1;
const firstWorkspace = workspaceFolders[0];
const maybeWorkspace =
!isMultiFolderWorkspace || !runnable.args.workspaceRoot
!isMultiFolderWorkspace || !runnableArgs.workspaceRoot
? firstWorkspace
: workspaceFolders.find((w) => runnable.args.workspaceRoot?.includes(w.uri.fsPath)) ||
: workspaceFolders.find((w) => runnableArgs.workspaceRoot?.includes(w.uri.fsPath)) ||
firstWorkspace;

const workspace = unwrapUndefinable(maybeWorkspace);
Expand All @@ -133,8 +140,11 @@ async function getDebugConfiguration(
return path.normalize(p).replace(wsFolder, "${workspaceFolder" + workspaceQualifier + "}");
}

const env = prepareEnv(runnable, ctx.config.runnablesExtraEnv);
const { executable, workspace: cargoWorkspace } = await getDebugExecutableInfo(runnable, env);
const env = prepareEnv(runnable.label, runnableArgs, ctx.config.runnablesExtraEnv);
const { executable, workspace: cargoWorkspace } = await getDebugExecutableInfo(
runnableArgs,
env,
);
let sourceFileMap = debugOptions.sourceFileMap;
if (sourceFileMap === "auto") {
// let's try to use the default toolchain
Expand All @@ -150,6 +160,7 @@ async function getDebugConfiguration(
const provider = unwrapUndefinable(knownEngines[debugEngine.id]);
const debugConfig = provider(
runnable,
runnableArgs,
simplifyPath(executable),
cargoWorkspace,
env,
Expand Down Expand Up @@ -177,18 +188,19 @@ async function getDebugConfiguration(
}

async function getDebugExecutableInfo(
runnable: ra.Runnable,
runnableArgs: ra.CargoRunnableArgs,
env: Record<string, string>,
): Promise<ExecutableInfo> {
const cargo = new Cargo(runnable.args.workspaceRoot || ".", debugOutput, env);
const executableInfo = await cargo.executableInfoFromArgs(runnable.args.cargoArgs);
const cargo = new Cargo(runnableArgs.workspaceRoot || ".", debugOutput, env);
const executableInfo = await cargo.executableInfoFromArgs(runnableArgs.cargoArgs);

// if we are here, there were no compilation errors.
return executableInfo;
}

function getCCppDebugConfig(
runnable: ra.Runnable,
runnableArgs: ra.CargoRunnableArgs,
executable: string,
cargoWorkspace: string,
env: Record<string, string>,
Expand All @@ -199,15 +211,16 @@ function getCCppDebugConfig(
request: "launch",
name: runnable.label,
program: executable,
args: runnable.args.executableArgs,
cwd: cargoWorkspace || runnable.args.workspaceRoot,
args: runnableArgs.executableArgs,
cwd: cargoWorkspace || runnableArgs.workspaceRoot,
sourceFileMap,
env,
};
}

function getCodeLldbDebugConfig(
runnable: ra.Runnable,
runnableArgs: ra.CargoRunnableArgs,
executable: string,
cargoWorkspace: string,
env: Record<string, string>,
Expand All @@ -218,8 +231,8 @@ function getCodeLldbDebugConfig(
request: "launch",
name: runnable.label,
program: executable,
args: runnable.args.executableArgs,
cwd: cargoWorkspace || runnable.args.workspaceRoot,
args: runnableArgs.executableArgs,
cwd: cargoWorkspace || runnableArgs.workspaceRoot,
sourceMap: sourceFileMap,
sourceLanguages: ["rust"],
env,
Expand All @@ -228,6 +241,7 @@ function getCodeLldbDebugConfig(

function getNativeDebugConfig(
runnable: ra.Runnable,
runnableArgs: ra.CargoRunnableArgs,
executable: string,
cargoWorkspace: string,
env: Record<string, string>,
Expand All @@ -239,8 +253,8 @@ function getNativeDebugConfig(
name: runnable.label,
target: executable,
// See https://github.com/WebFreak001/code-debug/issues/359
arguments: quote(runnable.args.executableArgs),
cwd: cargoWorkspace || runnable.args.workspaceRoot,
arguments: quote(runnableArgs.executableArgs),
cwd: cargoWorkspace || runnableArgs.workspaceRoot,
env,
valuesFormatting: "prettyPrinters",
};
Expand Down
28 changes: 19 additions & 9 deletions editors/code/src/lsp_ext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,16 +219,26 @@ export type OpenCargoTomlParams = {
export type Runnable = {
label: string;
location?: lc.LocationLink;
kind: "cargo";
args: {
workspaceRoot?: string;
cargoArgs: string[];
cargoExtraArgs: string[];
executableArgs: string[];
expectTest?: boolean;
overrideCargo?: string;
};
kind: "cargo" | "shell";
args: CargoRunnableArgs | ShellRunnableArgs;
};

export type ShellRunnableArgs = {
kind: string;
program: string;
args: string[];
cwd: string;
};

export type CargoRunnableArgs = {
workspaceRoot?: string;
cargoArgs: string[];
cargoExtraArgs: string[];
executableArgs: string[];
expectTest?: boolean;
overrideCargo?: string;
};

export type RunnablesParams = {
textDocument: lc.TextDocumentIdentifier;
position: lc.Position | null;
Expand Down
83 changes: 48 additions & 35 deletions editors/code/src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,17 +67,23 @@ export class RunnableQuickPick implements vscode.QuickPickItem {
}
}

export function prepareBaseEnv(): Record<string, string> {
const env: Record<string, string> = { RUST_BACKTRACE: "short" };
Object.assign(env, process.env as { [key: string]: string });
return env;
}

export function prepareEnv(
runnable: ra.Runnable,
label: string,
runnableArgs: ra.CargoRunnableArgs,
runnableEnvCfg: RunnableEnvCfg,
): Record<string, string> {
const env: Record<string, string> = { RUST_BACKTRACE: "short" };
const env = prepareBaseEnv();

if (runnable.args.expectTest) {
if (runnableArgs.expectTest) {
env["UPDATE_EXPECT"] = "1";
}

Object.assign(env, process.env as { [key: string]: string });
const platform = process.platform;

const checkPlatform = (it: RunnableEnvCfgItem) => {
Expand All @@ -91,7 +97,7 @@ export function prepareEnv(
if (runnableEnvCfg) {
if (Array.isArray(runnableEnvCfg)) {
for (const it of runnableEnvCfg) {
const masked = !it.mask || new RegExp(it.mask).test(runnable.label);
const masked = !it.mask || new RegExp(it.mask).test(label);
if (masked && checkPlatform(it)) {
Object.assign(env, it.env);
}
Expand All @@ -105,34 +111,41 @@ export function prepareEnv(
}

export async function createTask(runnable: ra.Runnable, config: Config): Promise<vscode.Task> {
if (runnable.kind !== "cargo") {
// rust-analyzer supports only one kind, "cargo"
// do not use tasks.TASK_TYPE here, these are completely different meanings.

throw `Unexpected runnable kind: ${runnable.kind}`;
}

let program: string;
let args = createArgs(runnable);
if (runnable.args.overrideCargo) {
// Split on spaces to allow overrides like "wrapper cargo".
const cargoParts = runnable.args.overrideCargo.split(" ");
let definition: tasks.RustTargetDefinition;
if (runnable.kind === "cargo") {
const runnableArgs = runnable.args as ra.CargoRunnableArgs;
let args = createCargoArgs(runnableArgs);

let program: string;
if (runnableArgs.overrideCargo) {
// Split on spaces to allow overrides like "wrapper cargo".
const cargoParts = runnableArgs.overrideCargo.split(" ");

program = unwrapUndefinable(cargoParts[0]);
args = [...cargoParts.slice(1), ...args];
} else {
program = await toolchain.cargoPath();
}

program = unwrapUndefinable(cargoParts[0]);
args = [...cargoParts.slice(1), ...args];
definition = {
type: tasks.TASK_TYPE,
program,
args,
cwd: runnableArgs.workspaceRoot || ".",
env: prepareEnv(runnable.label, runnableArgs, config.runnablesExtraEnv),
};
} else {
program = await toolchain.cargoPath();
const runnableArgs = runnable.args as ra.ShellRunnableArgs;

definition = {
type: tasks.TASK_TYPE,
program: runnableArgs.program,
args: runnableArgs.args,
cwd: runnableArgs.cwd,
env: prepareBaseEnv(),
};
}

const definition: tasks.RustTargetDefinition = {
type: tasks.TASK_TYPE,
program,
args,
cwd: runnable.args.workspaceRoot || ".",
env: prepareEnv(runnable, config.runnablesExtraEnv),
overrideCargo: runnable.args.overrideCargo,
};

// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate()
const task = await tasks.buildRustTask(
Expand All @@ -152,13 +165,13 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
return task;
}

export function createArgs(runnable: ra.Runnable): string[] {
const args = [...runnable.args.cargoArgs]; // should be a copy!
if (runnable.args.cargoExtraArgs) {
args.push(...runnable.args.cargoExtraArgs); // Append user-specified cargo options.
export function createCargoArgs(runnableArgs: ra.CargoRunnableArgs): string[] {
const args = [...runnableArgs.cargoArgs]; // should be a copy!
if (runnableArgs.cargoExtraArgs) {
args.push(...runnableArgs.cargoExtraArgs); // Append user-specified cargo options.
}
if (runnable.args.executableArgs.length > 0) {
args.push("--", ...runnable.args.executableArgs);
if (runnableArgs.executableArgs.length > 0) {
args.push("--", ...runnableArgs.executableArgs);
}
return args;
}
Expand Down
7 changes: 7 additions & 0 deletions editors/code/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as vscode from "vscode";
import { strict as nativeAssert } from "assert";
import { exec, type ExecOptions, spawnSync } from "child_process";
import { inspect } from "util";
import type { CargoRunnableArgs, ShellRunnableArgs } from "./lsp_ext";
import type { Env } from "./client";

export function assert(condition: boolean, explanation: string): asserts condition {
Expand Down Expand Up @@ -77,6 +78,12 @@ export function isCargoTomlDocument(document: vscode.TextDocument): document is
return document.uri.scheme === "file" && document.fileName.endsWith("Cargo.toml");
}

export function isCargoRunnableArgs(
args: CargoRunnableArgs | ShellRunnableArgs,
): args is CargoRunnableArgs {
return (args as CargoRunnableArgs).executableArgs !== undefined;
}

export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor {
return isRustDocument(editor.document);
}
Expand Down
3 changes: 2 additions & 1 deletion editors/code/tests/unit/runnable_env.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ function makeRunnable(label: string): ra.Runnable {

function fakePrepareEnv(runnableName: string, config: RunnableEnvCfg): Record<string, string> {
const runnable = makeRunnable(runnableName);
return prepareEnv(runnable, config);
const runnableArgs = runnable.args as ra.CargoRunnableArgs;
return prepareEnv(runnable.label, runnableArgs, config);
}

export async function getTests(ctx: Context) {
Expand Down

0 comments on commit b1ba4a4

Please sign in to comment.