aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/user/manual.adoc56
-rw-r--r--editors/code/package.json29
-rw-r--r--editors/code/src/config.ts6
-rw-r--r--editors/code/src/debug.ts16
-rw-r--r--editors/code/src/run.ts35
-rw-r--r--editors/code/tests/unit/runnable_env.test.ts118
6 files changed, 235 insertions, 25 deletions
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index b763958fe..7816287e4 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -109,18 +109,6 @@ Here are some useful self-diagnostic commands:
109* To log all LSP requests, add `"rust-analyzer.trace.server": "verbose"` to the settings and look for `Server Trace` in the panel. 109* To log all LSP requests, add `"rust-analyzer.trace.server": "verbose"` to the settings and look for `Server Trace` in the panel.
110* To enable client-side logging, add `"rust-analyzer.trace.extension": true` to the settings and open the `Console` tab of VS Code developer tools. 110* To enable client-side logging, add `"rust-analyzer.trace.extension": true` to the settings and open the `Console` tab of VS Code developer tools.
111 111
112==== Special `when` clause context for keybindings.
113You may use `inRustProject` context to configure keybindings for rust projects only. For example:
114[source,json]
115----
116{
117 "key": "ctrl+i",
118 "command": "rust-analyzer.toggleInlayHints",
119 "when": "inRustProject"
120}
121----
122More about `when` clause contexts https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts[here].
123
124=== rust-analyzer Language Server Binary 112=== rust-analyzer Language Server Binary
125 113
126Other editors generally require the `rust-analyzer` binary to be in `$PATH`. 114Other editors generally require the `rust-analyzer` binary to be in `$PATH`.
@@ -337,3 +325,47 @@ They are usually triggered by a shortcut or by clicking a light bulb icon in the
337Cursor position or selection is signified by `┃` character. 325Cursor position or selection is signified by `┃` character.
338 326
339include::./generated_assists.adoc[] 327include::./generated_assists.adoc[]
328
329== Editor Features
330=== VS Code
331==== Special `when` clause context for keybindings.
332You may use `inRustProject` context to configure keybindings for rust projects only. For example:
333[source,json]
334----
335{
336 "key": "ctrl+i",
337 "command": "rust-analyzer.toggleInlayHints",
338 "when": "inRustProject"
339}
340----
341More about `when` clause contexts https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts[here].
342
343==== Setting runnable environment variables
344You can use "rust-analyzer.runnableEnv" setting to define runnable environment-specific substitution variables.
345The simplest way for all runnables in a bunch:
346```jsonc
347"rust-analyzer.runnableEnv": {
348 "RUN_SLOW_TESTS": "1"
349}
350```
351
352Or it is possible to specify vars more granularly:
353```jsonc
354"rust-analyzer.runnableEnv": [
355 {
356 // "mask": null, // null mask means that this rule will be applied for all runnables
357 env: {
358 "APP_ID": "1",
359 "APP_DATA": "asdf"
360 }
361 },
362 {
363 "mask": "test_name",
364 "env": {
365 "APP_ID": "2", // overwrites only APP_ID
366 }
367 }
368]
369```
370
371You can use any valid RegExp as a mask. Also note that a full runnable name is something like *run bin_or_example_name*, *test some::mod::test_name* or *test-mod some::mod*, so it is possible to distinguish binaries, single tests, and test modules with this masks: `"^run"`, `"^test "` (the trailing space matters!), and `"^test-mod"` respectively.
diff --git a/editors/code/package.json b/editors/code/package.json
index af0a5c851..7c8b2fbec 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -344,6 +344,35 @@
344 "default": null, 344 "default": null,
345 "description": "Custom cargo runner extension ID." 345 "description": "Custom cargo runner extension ID."
346 }, 346 },
347 "rust-analyzer.runnableEnv": {
348 "anyOf": [
349 {
350 "type": "null"
351 },
352 {
353 "type": "array",
354 "items": {
355 "type": "object",
356 "properties": {
357 "mask": {
358 "type": "string",
359 "description": "Runnable name mask"
360 },
361 "env": {
362 "type": "object",
363 "description": "Variables in form of { \"key\": \"value\"}"
364 }
365 }
366 }
367 },
368 {
369 "type": "object",
370 "description": "Variables in form of { \"key\": \"value\"}"
371 }
372 ],
373 "default": null,
374 "description": "Environment variables passed to the runnable launched using `Test ` or `Debug` lens or `rust-analyzer.run` command."
375 },
347 "rust-analyzer.inlayHints.enable": { 376 "rust-analyzer.inlayHints.enable": {
348 "type": "boolean", 377 "type": "boolean",
349 "default": true, 378 "default": true,
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index fc95a7de6..23975c726 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -5,6 +5,8 @@ export type UpdatesChannel = "stable" | "nightly";
5 5
6export const NIGHTLY_TAG = "nightly"; 6export const NIGHTLY_TAG = "nightly";
7 7
8export type RunnableEnvCfg = undefined | Record<string, string> | { mask?: string; env: Record<string, string> }[];
9
8export class Config { 10export class Config {
9 readonly extensionId = "matklad.rust-analyzer"; 11 readonly extensionId = "matklad.rust-analyzer";
10 12
@@ -114,6 +116,10 @@ export class Config {
114 return this.get<string | undefined>("cargoRunner"); 116 return this.get<string | undefined>("cargoRunner");
115 } 117 }
116 118
119 get runnableEnv() {
120 return this.get<RunnableEnvCfg>("runnableEnv");
121 }
122
117 get debug() { 123 get debug() {
118 // "/rustc/<id>" used by suggestions only. 124 // "/rustc/<id>" used by suggestions only.
119 const { ["/rustc/<id>"]: _, ...sourceFileMap } = this.get<Record<string, string>>("debug.sourceFileMap"); 125 const { ["/rustc/<id>"]: _, ...sourceFileMap } = this.get<Record<string, string>>("debug.sourceFileMap");
diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts
index 61c12dbe0..bd92c5b6d 100644
--- a/editors/code/src/debug.ts
+++ b/editors/code/src/debug.ts
@@ -5,9 +5,10 @@ import * as ra from './lsp_ext';
5 5
6import { Cargo } from './toolchain'; 6import { Cargo } from './toolchain';
7import { Ctx } from "./ctx"; 7import { Ctx } from "./ctx";
8import { prepareEnv } from "./run";
8 9
9const debugOutput = vscode.window.createOutputChannel("Debug"); 10const debugOutput = vscode.window.createOutputChannel("Debug");
10type DebugConfigProvider = (config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration; 11type DebugConfigProvider = (config: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration;
11 12
12export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise<void> { 13export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise<void> {
13 const scope = ctx.activeRustEditor?.document.uri; 14 const scope = ctx.activeRustEditor?.document.uri;
@@ -92,7 +93,8 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<v
92 } 93 }
93 94
94 const executable = await getDebugExecutable(runnable); 95 const executable = await getDebugExecutable(runnable);
95 const debugConfig = knownEngines[debugEngine.id](runnable, simplifyPath(executable), debugOptions.sourceFileMap); 96 const env = prepareEnv(runnable, ctx.config.runnableEnv);
97 const debugConfig = knownEngines[debugEngine.id](runnable, simplifyPath(executable), env, debugOptions.sourceFileMap);
96 if (debugConfig.type in debugOptions.engineSettings) { 98 if (debugConfig.type in debugOptions.engineSettings) {
97 const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type]; 99 const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type];
98 for (var key in settingsMap) { 100 for (var key in settingsMap) {
@@ -121,7 +123,7 @@ async function getDebugExecutable(runnable: ra.Runnable): Promise<string> {
121 return executable; 123 return executable;
122} 124}
123 125
124function getLldbDebugConfig(runnable: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration { 126function getLldbDebugConfig(runnable: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
125 return { 127 return {
126 type: "lldb", 128 type: "lldb",
127 request: "launch", 129 request: "launch",
@@ -130,11 +132,12 @@ function getLldbDebugConfig(runnable: ra.Runnable, executable: string, sourceFil
130 args: runnable.args.executableArgs, 132 args: runnable.args.executableArgs,
131 cwd: runnable.args.workspaceRoot, 133 cwd: runnable.args.workspaceRoot,
132 sourceMap: sourceFileMap, 134 sourceMap: sourceFileMap,
133 sourceLanguages: ["rust"] 135 sourceLanguages: ["rust"],
136 env
134 }; 137 };
135} 138}
136 139
137function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration { 140function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
138 return { 141 return {
139 type: (os.platform() === "win32") ? "cppvsdbg" : "cppdbg", 142 type: (os.platform() === "win32") ? "cppvsdbg" : "cppdbg",
140 request: "launch", 143 request: "launch",
@@ -142,6 +145,7 @@ function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, sourceFi
142 program: executable, 145 program: executable,
143 args: runnable.args.executableArgs, 146 args: runnable.args.executableArgs,
144 cwd: runnable.args.workspaceRoot, 147 cwd: runnable.args.workspaceRoot,
145 sourceFileMap: sourceFileMap, 148 sourceFileMap,
149 env,
146 }; 150 };
147} 151}
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts
index e1430e31f..de68f27ae 100644
--- a/editors/code/src/run.ts
+++ b/editors/code/src/run.ts
@@ -5,7 +5,7 @@ import * as tasks from './tasks';
5 5
6import { Ctx } from './ctx'; 6import { Ctx } from './ctx';
7import { makeDebugConfig } from './debug'; 7import { makeDebugConfig } from './debug';
8import { Config } from './config'; 8import { Config, RunnableEnvCfg } from './config';
9 9
10const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }]; 10const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }];
11 11
@@ -96,6 +96,30 @@ export class RunnableQuickPick implements vscode.QuickPickItem {
96 } 96 }
97} 97}
98 98
99export function prepareEnv(runnable: ra.Runnable, runnableEnvCfg: RunnableEnvCfg): Record<string, string> {
100 const env: Record<string, string> = { "RUST_BACKTRACE": "short" };
101
102 if (runnable.args.expectTest) {
103 env["UPDATE_EXPECT"] = "1";
104 }
105
106 Object.assign(env, process.env as { [key: string]: string });
107
108 if (runnableEnvCfg) {
109 if (Array.isArray(runnableEnvCfg)) {
110 for (const it of runnableEnvCfg) {
111 if (!it.mask || new RegExp(it.mask).test(runnable.label)) {
112 Object.assign(env, it.env);
113 }
114 }
115 } else {
116 Object.assign(env, runnableEnvCfg);
117 }
118 }
119
120 return env;
121}
122
99export async function createTask(runnable: ra.Runnable, config: Config): Promise<vscode.Task> { 123export async function createTask(runnable: ra.Runnable, config: Config): Promise<vscode.Task> {
100 if (runnable.kind !== "cargo") { 124 if (runnable.kind !== "cargo") {
101 // rust-analyzer supports only one kind, "cargo" 125 // rust-analyzer supports only one kind, "cargo"
@@ -108,16 +132,13 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
108 if (runnable.args.executableArgs.length > 0) { 132 if (runnable.args.executableArgs.length > 0) {
109 args.push('--', ...runnable.args.executableArgs); 133 args.push('--', ...runnable.args.executableArgs);
110 } 134 }
111 const env: { [key: string]: string } = { "RUST_BACKTRACE": "short" }; 135
112 if (runnable.args.expectTest) {
113 env["UPDATE_EXPECT"] = "1";
114 }
115 const definition: tasks.CargoTaskDefinition = { 136 const definition: tasks.CargoTaskDefinition = {
116 type: tasks.TASK_TYPE, 137 type: tasks.TASK_TYPE,
117 command: args[0], // run, test, etc... 138 command: args[0], // run, test, etc...
118 args: args.slice(1), 139 args: args.slice(1),
119 cwd: runnable.args.workspaceRoot, 140 cwd: runnable.args.workspaceRoot || ".",
120 env: Object.assign({}, process.env as { [key: string]: string }, env), 141 env: prepareEnv(runnable, config.runnableEnv),
121 }; 142 };
122 143
123 const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate() 144 const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate()
diff --git a/editors/code/tests/unit/runnable_env.test.ts b/editors/code/tests/unit/runnable_env.test.ts
new file mode 100644
index 000000000..f2f53e91a
--- /dev/null
+++ b/editors/code/tests/unit/runnable_env.test.ts
@@ -0,0 +1,118 @@
1import * as assert from 'assert';
2import { prepareEnv } from '../../src/run';
3import { RunnableEnvCfg } from '../../src/config';
4import * as ra from '../../src/lsp_ext';
5
6function makeRunnable(label: string): ra.Runnable {
7 return {
8 label,
9 kind: "cargo",
10 args: {
11 cargoArgs: [],
12 executableArgs: []
13 }
14 };
15}
16
17function fakePrepareEnv(runnableName: string, config: RunnableEnvCfg): Record<string, string> {
18 const runnable = makeRunnable(runnableName);
19 return prepareEnv(runnable, config);
20}
21
22suite('Runnable env', () => {
23 test('Global config works', () => {
24 const binEnv = fakePrepareEnv("run project_name", { "GLOBAL": "g" });
25 assert.equal(binEnv["GLOBAL"], "g");
26
27 const testEnv = fakePrepareEnv("test some::mod::test_name", { "GLOBAL": "g" });
28 assert.equal(testEnv["GLOBAL"], "g");
29 });
30
31 test('null mask works', () => {
32 const config = [
33 {
34 env: { DATA: "data" }
35 }
36 ];
37 const binEnv = fakePrepareEnv("run project_name", config);
38 assert.equal(binEnv["DATA"], "data");
39
40 const testEnv = fakePrepareEnv("test some::mod::test_name", config);
41 assert.equal(testEnv["DATA"], "data");
42 });
43
44 test('order works', () => {
45 const config = [
46 {
47 env: { DATA: "data" }
48 },
49 {
50 env: { DATA: "newdata" }
51 }
52 ];
53 const binEnv = fakePrepareEnv("run project_name", config);
54 assert.equal(binEnv["DATA"], "newdata");
55
56 const testEnv = fakePrepareEnv("test some::mod::test_name", config);
57 assert.equal(testEnv["DATA"], "newdata");
58 });
59
60 test('mask works', () => {
61 const config = [
62 {
63 env: { DATA: "data" }
64 },
65 {
66 mask: "^run",
67 env: { DATA: "rundata" }
68 },
69 {
70 mask: "special_test$",
71 env: { DATA: "special_test" }
72 }
73 ];
74 const binEnv = fakePrepareEnv("run project_name", config);
75 assert.equal(binEnv["DATA"], "rundata");
76
77 const testEnv = fakePrepareEnv("test some::mod::test_name", config);
78 assert.equal(testEnv["DATA"], "data");
79
80 const specialTestEnv = fakePrepareEnv("test some::mod::special_test", config);
81 assert.equal(specialTestEnv["DATA"], "special_test");
82 });
83
84 test('exact test name works', () => {
85 const config = [
86 {
87 env: { DATA: "data" }
88 },
89 {
90 mask: "some::mod::test_name",
91 env: { DATA: "test special" }
92 }
93 ];
94 const testEnv = fakePrepareEnv("test some::mod::test_name", config);
95 assert.equal(testEnv["DATA"], "test special");
96
97 const specialTestEnv = fakePrepareEnv("test some::mod::another_test", config);
98 assert.equal(specialTestEnv["DATA"], "data");
99 });
100
101 test('test mod name works', () => {
102 const config = [
103 {
104 env: { DATA: "data" }
105 },
106 {
107 mask: "some::mod",
108 env: { DATA: "mod special" }
109 }
110 ];
111 const testEnv = fakePrepareEnv("test some::mod::test_name", config);
112 assert.equal(testEnv["DATA"], "mod special");
113
114 const specialTestEnv = fakePrepareEnv("test some::mod::another_test", config);
115 assert.equal(specialTestEnv["DATA"], "mod special");
116 });
117
118});