Skip to content

Commit 8c3d841

Browse files
Wilfreddavidbarsky
andcommitted
Define a runnable args kind for arbitrary shell commands
Co-authored-by: David Barsky <[email protected]>
1 parent b92854c commit 8c3d841

File tree

7 files changed

+121
-64
lines changed

7 files changed

+121
-64
lines changed

docs/dev/lsp-extensions.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!---
2-
lsp/ext.rs hash: d5febcbf63650753
2+
lsp/ext.rs hash: 219a5cf0b54d9153
33
44
If you need to change the above hash to make the test pass, please check if you
55
need to adjust this doc as well and ping this issue:
@@ -372,7 +372,7 @@ interface Runnable {
372372
}
373373
```
374374

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

377377
```typescript
378378
{
@@ -385,6 +385,17 @@ rust-analyzer supports only one `kind`, `"cargo"`. The `args` for `"cargo"` look
385385
}
386386
```
387387

388+
The args for `"shell"` look like this:
389+
390+
```typescript
391+
{
392+
kind: string;
393+
program: string;
394+
args: string[];
395+
cwd: string;
396+
}
397+
```
398+
388399
## Test explorer
389400

390401
**Experimental Client Capability:** `{ "testExplorer": boolean }`

editors/code/src/commands.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ import {
1010
type SnippetTextDocumentEdit,
1111
} from "./snippets";
1212
import { spawnSync } from "child_process";
13-
import { type RunnableQuickPick, selectRunnable, createTask, createArgs } from "./run";
13+
import { type RunnableQuickPick, selectRunnable, createTask, createCargoArgs } from "./run";
1414
import { AstInspector } from "./ast_inspector";
1515
import {
1616
isRustDocument,
17+
isCargoRunnableArgs,
1718
isCargoTomlDocument,
1819
sleep,
1920
isRustEditor,
@@ -1156,8 +1157,8 @@ export function copyRunCommandLine(ctx: CtxInit) {
11561157
let prevRunnable: RunnableQuickPick | undefined;
11571158
return async () => {
11581159
const item = await selectRunnable(ctx, prevRunnable);
1159-
if (!item) return;
1160-
const args = createArgs(item.runnable);
1160+
if (!item || !isCargoRunnableArgs(item.runnable.args)) return;
1161+
const args = createCargoArgs(item.runnable.args);
11611162
const commandLine = ["cargo", ...args].join(" ");
11621163
await vscode.env.clipboard.writeText(commandLine);
11631164
await vscode.window.showInformationMessage("Cargo invocation copied to the clipboard.");

editors/code/src/debug.ts

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import { Cargo, type ExecutableInfo, getRustcId, getSysroot } from "./toolchain"
77
import type { Ctx } from "./ctx";
88
import { prepareEnv } from "./run";
99
import { unwrapUndefinable } from "./undefinable";
10+
import { isCargoRunnableArgs } from "./util";
1011

1112
const debugOutput = vscode.window.createOutputChannel("Debug");
1213
type DebugConfigProvider = (
13-
config: ra.Runnable,
14+
runnable: ra.Runnable,
15+
runnableArgs: ra.CargoRunnableArgs,
1416
executable: string,
1517
cargoWorkspace: string,
1618
env: Record<string, string>,
@@ -77,6 +79,11 @@ async function getDebugConfiguration(
7779
ctx: Ctx,
7880
runnable: ra.Runnable,
7981
): Promise<vscode.DebugConfiguration | undefined> {
82+
if (!isCargoRunnableArgs(runnable.args)) {
83+
return;
84+
}
85+
const runnableArgs: ra.CargoRunnableArgs = runnable.args;
86+
8087
const editor = ctx.activeRustEditor;
8188
if (!editor) return;
8289

@@ -120,9 +127,9 @@ async function getDebugConfiguration(
120127
const isMultiFolderWorkspace = workspaceFolders.length > 1;
121128
const firstWorkspace = workspaceFolders[0];
122129
const maybeWorkspace =
123-
!isMultiFolderWorkspace || !runnable.args.workspaceRoot
130+
!isMultiFolderWorkspace || !runnableArgs.workspaceRoot
124131
? firstWorkspace
125-
: workspaceFolders.find((w) => runnable.args.workspaceRoot?.includes(w.uri.fsPath)) ||
132+
: workspaceFolders.find((w) => runnableArgs.workspaceRoot?.includes(w.uri.fsPath)) ||
126133
firstWorkspace;
127134

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

136-
const env = prepareEnv(runnable, ctx.config.runnablesExtraEnv);
137-
const { executable, workspace: cargoWorkspace } = await getDebugExecutableInfo(runnable, env);
143+
const env = prepareEnv(runnable.label, runnableArgs, ctx.config.runnablesExtraEnv);
144+
const { executable, workspace: cargoWorkspace } = await getDebugExecutableInfo(
145+
runnableArgs,
146+
env,
147+
);
138148
let sourceFileMap = debugOptions.sourceFileMap;
139149
if (sourceFileMap === "auto") {
140150
// let's try to use the default toolchain
@@ -150,6 +160,7 @@ async function getDebugConfiguration(
150160
const provider = unwrapUndefinable(knownEngines[debugEngine.id]);
151161
const debugConfig = provider(
152162
runnable,
163+
runnableArgs,
153164
simplifyPath(executable),
154165
cargoWorkspace,
155166
env,
@@ -177,18 +188,19 @@ async function getDebugConfiguration(
177188
}
178189

179190
async function getDebugExecutableInfo(
180-
runnable: ra.Runnable,
191+
runnableArgs: ra.CargoRunnableArgs,
181192
env: Record<string, string>,
182193
): Promise<ExecutableInfo> {
183-
const cargo = new Cargo(runnable.args.workspaceRoot || ".", debugOutput, env);
184-
const executableInfo = await cargo.executableInfoFromArgs(runnable.args.cargoArgs);
194+
const cargo = new Cargo(runnableArgs.workspaceRoot || ".", debugOutput, env);
195+
const executableInfo = await cargo.executableInfoFromArgs(runnableArgs.cargoArgs);
185196

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

190201
function getCCppDebugConfig(
191202
runnable: ra.Runnable,
203+
runnableArgs: ra.CargoRunnableArgs,
192204
executable: string,
193205
cargoWorkspace: string,
194206
env: Record<string, string>,
@@ -199,15 +211,16 @@ function getCCppDebugConfig(
199211
request: "launch",
200212
name: runnable.label,
201213
program: executable,
202-
args: runnable.args.executableArgs,
203-
cwd: cargoWorkspace || runnable.args.workspaceRoot,
214+
args: runnableArgs.executableArgs,
215+
cwd: cargoWorkspace || runnableArgs.workspaceRoot,
204216
sourceFileMap,
205217
env,
206218
};
207219
}
208220

209221
function getCodeLldbDebugConfig(
210222
runnable: ra.Runnable,
223+
runnableArgs: ra.CargoRunnableArgs,
211224
executable: string,
212225
cargoWorkspace: string,
213226
env: Record<string, string>,
@@ -218,8 +231,8 @@ function getCodeLldbDebugConfig(
218231
request: "launch",
219232
name: runnable.label,
220233
program: executable,
221-
args: runnable.args.executableArgs,
222-
cwd: cargoWorkspace || runnable.args.workspaceRoot,
234+
args: runnableArgs.executableArgs,
235+
cwd: cargoWorkspace || runnableArgs.workspaceRoot,
223236
sourceMap: sourceFileMap,
224237
sourceLanguages: ["rust"],
225238
env,
@@ -228,6 +241,7 @@ function getCodeLldbDebugConfig(
228241

229242
function getNativeDebugConfig(
230243
runnable: ra.Runnable,
244+
runnableArgs: ra.CargoRunnableArgs,
231245
executable: string,
232246
cargoWorkspace: string,
233247
env: Record<string, string>,
@@ -239,8 +253,8 @@ function getNativeDebugConfig(
239253
name: runnable.label,
240254
target: executable,
241255
// See https://github.com/WebFreak001/code-debug/issues/359
242-
arguments: quote(runnable.args.executableArgs),
243-
cwd: cargoWorkspace || runnable.args.workspaceRoot,
256+
arguments: quote(runnableArgs.executableArgs),
257+
cwd: cargoWorkspace || runnableArgs.workspaceRoot,
244258
env,
245259
valuesFormatting: "prettyPrinters",
246260
};

editors/code/src/lsp_ext.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -219,16 +219,26 @@ export type OpenCargoTomlParams = {
219219
export type Runnable = {
220220
label: string;
221221
location?: lc.LocationLink;
222-
kind: "cargo";
223-
args: {
224-
workspaceRoot?: string;
225-
cargoArgs: string[];
226-
cargoExtraArgs: string[];
227-
executableArgs: string[];
228-
expectTest?: boolean;
229-
overrideCargo?: string;
230-
};
222+
kind: "cargo" | "shell";
223+
args: CargoRunnableArgs | ShellRunnableArgs;
231224
};
225+
226+
export type ShellRunnableArgs = {
227+
kind: string;
228+
program: string;
229+
args: string[];
230+
cwd: string;
231+
};
232+
233+
export type CargoRunnableArgs = {
234+
workspaceRoot?: string;
235+
cargoArgs: string[];
236+
cargoExtraArgs: string[];
237+
executableArgs: string[];
238+
expectTest?: boolean;
239+
overrideCargo?: string;
240+
};
241+
232242
export type RunnablesParams = {
233243
textDocument: lc.TextDocumentIdentifier;
234244
position: lc.Position | null;

editors/code/src/run.ts

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,23 @@ export class RunnableQuickPick implements vscode.QuickPickItem {
6767
}
6868
}
6969

70+
export function prepareBaseEnv(): Record<string, string> {
71+
const env: Record<string, string> = { RUST_BACKTRACE: "short" };
72+
Object.assign(env, process.env as { [key: string]: string });
73+
return env;
74+
}
75+
7076
export function prepareEnv(
71-
runnable: ra.Runnable,
77+
label: string,
78+
runnableArgs: ra.CargoRunnableArgs,
7279
runnableEnvCfg: RunnableEnvCfg,
7380
): Record<string, string> {
74-
const env: Record<string, string> = { RUST_BACKTRACE: "short" };
81+
const env = prepareBaseEnv();
7582

76-
if (runnable.args.expectTest) {
83+
if (runnableArgs.expectTest) {
7784
env["UPDATE_EXPECT"] = "1";
7885
}
7986

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

8389
const checkPlatform = (it: RunnableEnvCfgItem) => {
@@ -91,7 +97,7 @@ export function prepareEnv(
9197
if (runnableEnvCfg) {
9298
if (Array.isArray(runnableEnvCfg)) {
9399
for (const it of runnableEnvCfg) {
94-
const masked = !it.mask || new RegExp(it.mask).test(runnable.label);
100+
const masked = !it.mask || new RegExp(it.mask).test(label);
95101
if (masked && checkPlatform(it)) {
96102
Object.assign(env, it.env);
97103
}
@@ -105,34 +111,41 @@ export function prepareEnv(
105111
}
106112

107113
export async function createTask(runnable: ra.Runnable, config: Config): Promise<vscode.Task> {
108-
if (runnable.kind !== "cargo") {
109-
// rust-analyzer supports only one kind, "cargo"
110-
// do not use tasks.TASK_TYPE here, these are completely different meanings.
111-
112-
throw `Unexpected runnable kind: ${runnable.kind}`;
113-
}
114-
115-
let program: string;
116-
let args = createArgs(runnable);
117-
if (runnable.args.overrideCargo) {
118-
// Split on spaces to allow overrides like "wrapper cargo".
119-
const cargoParts = runnable.args.overrideCargo.split(" ");
114+
let definition: tasks.RustTargetDefinition;
115+
if (runnable.kind === "cargo") {
116+
const runnableArgs = runnable.args as ra.CargoRunnableArgs;
117+
let args = createCargoArgs(runnableArgs);
118+
119+
let program: string;
120+
if (runnableArgs.overrideCargo) {
121+
// Split on spaces to allow overrides like "wrapper cargo".
122+
const cargoParts = runnableArgs.overrideCargo.split(" ");
123+
124+
program = unwrapUndefinable(cargoParts[0]);
125+
args = [...cargoParts.slice(1), ...args];
126+
} else {
127+
program = await toolchain.cargoPath();
128+
}
120129

121-
program = unwrapUndefinable(cargoParts[0]);
122-
args = [...cargoParts.slice(1), ...args];
130+
definition = {
131+
type: tasks.TASK_TYPE,
132+
program,
133+
args,
134+
cwd: runnableArgs.workspaceRoot || ".",
135+
env: prepareEnv(runnable.label, runnableArgs, config.runnablesExtraEnv),
136+
};
123137
} else {
124-
program = await toolchain.cargoPath();
138+
const runnableArgs = runnable.args as ra.ShellRunnableArgs;
139+
140+
definition = {
141+
type: tasks.TASK_TYPE,
142+
program: runnableArgs.program,
143+
args: runnableArgs.args,
144+
cwd: runnableArgs.cwd,
145+
env: prepareBaseEnv(),
146+
};
125147
}
126148

127-
const definition: tasks.RustTargetDefinition = {
128-
type: tasks.TASK_TYPE,
129-
program,
130-
args,
131-
cwd: runnable.args.workspaceRoot || ".",
132-
env: prepareEnv(runnable, config.runnablesExtraEnv),
133-
overrideCargo: runnable.args.overrideCargo,
134-
};
135-
136149
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
137150
const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate()
138151
const task = await tasks.buildRustTask(
@@ -152,13 +165,13 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
152165
return task;
153166
}
154167

155-
export function createArgs(runnable: ra.Runnable): string[] {
156-
const args = [...runnable.args.cargoArgs]; // should be a copy!
157-
if (runnable.args.cargoExtraArgs) {
158-
args.push(...runnable.args.cargoExtraArgs); // Append user-specified cargo options.
168+
export function createCargoArgs(runnableArgs: ra.CargoRunnableArgs): string[] {
169+
const args = [...runnableArgs.cargoArgs]; // should be a copy!
170+
if (runnableArgs.cargoExtraArgs) {
171+
args.push(...runnableArgs.cargoExtraArgs); // Append user-specified cargo options.
159172
}
160-
if (runnable.args.executableArgs.length > 0) {
161-
args.push("--", ...runnable.args.executableArgs);
173+
if (runnableArgs.executableArgs.length > 0) {
174+
args.push("--", ...runnableArgs.executableArgs);
162175
}
163176
return args;
164177
}

editors/code/src/util.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as vscode from "vscode";
22
import { strict as nativeAssert } from "assert";
33
import { exec, type ExecOptions, spawnSync } from "child_process";
44
import { inspect } from "util";
5+
import type { CargoRunnableArgs, ShellRunnableArgs } from "./lsp_ext";
56
import type { Env } from "./client";
67

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

81+
export function isCargoRunnableArgs(
82+
args: CargoRunnableArgs | ShellRunnableArgs,
83+
): args is CargoRunnableArgs {
84+
return (args as CargoRunnableArgs).executableArgs !== undefined;
85+
}
86+
8087
export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor {
8188
return isRustDocument(editor.document);
8289
}

editors/code/tests/unit/runnable_env.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ function makeRunnable(label: string): ra.Runnable {
1818

1919
function fakePrepareEnv(runnableName: string, config: RunnableEnvCfg): Record<string, string> {
2020
const runnable = makeRunnable(runnableName);
21-
return prepareEnv(runnable, config);
21+
const runnableArgs = runnable.args as ra.CargoRunnableArgs;
22+
return prepareEnv(runnable.label, runnableArgs, config);
2223
}
2324

2425
export async function getTests(ctx: Context) {

0 commit comments

Comments
 (0)