Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 44 additions & 12 deletions docs/user/manual.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -109,18 +109,6 @@ Here are some useful self-diagnostic commands:
* To log all LSP requests, add `"rust-analyzer.trace.server": "verbose"` to the settings and look for `Server Trace` in the panel.
* To enable client-side logging, add `"rust-analyzer.trace.extension": true` to the settings and open the `Console` tab of VS Code developer tools.

==== Special `when` clause context for keybindings.
You may use `inRustProject` context to configure keybindings for rust projects only. For example:
[source,json]
----
{
"key": "ctrl+i",
"command": "rust-analyzer.toggleInlayHints",
"when": "inRustProject"
}
----
More about `when` clause contexts https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts[here].

=== rust-analyzer Language Server Binary

Other editors generally require the `rust-analyzer` binary to be in `$PATH`.
Expand Down Expand Up @@ -337,3 +325,47 @@ They are usually triggered by a shortcut or by clicking a light bulb icon in the
Cursor position or selection is signified by `┃` character.

include::./generated_assists.adoc[]

== Editor Features
=== VS Code
==== Special `when` clause context for keybindings.
You may use `inRustProject` context to configure keybindings for rust projects only. For example:
[source,json]
----
{
"key": "ctrl+i",
"command": "rust-analyzer.toggleInlayHints",
"when": "inRustProject"
}
----
More about `when` clause contexts https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts[here].

==== Setting runnable environment variables
You can use "rust-analyzer.runnableEnv" setting to define runnable environment-specific substitution variables.
The simplest way for all runnables in a bunch:
```jsonc
"rust-analyzer.runnableEnv": {
"RUN_SLOW_TESTS": "1"
}
```

Or it is possible to specify vars more granularly:
```jsonc
"rust-analyzer.runnableEnv": [
{
// "mask": null, // null mask means that this rule will be applied for all runnables
env: {
"APP_ID": "1",
"APP_DATA": "asdf"
}
},
{
"mask": "test_name",
"env": {
"APP_ID": "2", // overwrites only APP_ID
}
}
]
```

You 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.
29 changes: 29 additions & 0 deletions editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,35 @@
"default": null,
"description": "Custom cargo runner extension ID."
},
"rust-analyzer.runnableEnv": {
"anyOf": [
{
"type": "null"
},
{
"type": "array",
"items": {
"type": "object",
"properties": {
"mask": {
"type": "string",
"description": "Runnable name mask"
},
"env": {
"type": "object",
"description": "Variables in form of { \"key\": \"value\"}"
}
}
}
},
{
"type": "object",
"description": "Variables in form of { \"key\": \"value\"}"
}
],
"default": null,
"description": "Environment variables passed to the runnable launched using `Test ` or `Debug` lens or `rust-analyzer.run` command."
},
"rust-analyzer.inlayHints.enable": {
"type": "boolean",
"default": true,
Expand Down
6 changes: 6 additions & 0 deletions editors/code/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export type UpdatesChannel = "stable" | "nightly";

export const NIGHTLY_TAG = "nightly";

export type RunnableEnvCfg = undefined | Record<string, string> | { mask?: string; env: Record<string, string> }[];

export class Config {
readonly extensionId = "matklad.rust-analyzer";

Expand Down Expand Up @@ -114,6 +116,10 @@ export class Config {
return this.get<string | undefined>("cargoRunner");
}

get runnableEnv() {
return this.get<RunnableEnvCfg>("runnableEnv");
}

get debug() {
// "/rustc/<id>" used by suggestions only.
const { ["/rustc/<id>"]: _, ...sourceFileMap } = this.get<Record<string, string>>("debug.sourceFileMap");
Expand Down
16 changes: 10 additions & 6 deletions editors/code/src/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import * as ra from './lsp_ext';

import { Cargo } from './toolchain';
import { Ctx } from "./ctx";
import { prepareEnv } from "./run";

const debugOutput = vscode.window.createOutputChannel("Debug");
type DebugConfigProvider = (config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration;
type DebugConfigProvider = (config: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration;

export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise<void> {
const scope = ctx.activeRustEditor?.document.uri;
Expand Down Expand Up @@ -92,7 +93,8 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<v
}

const executable = await getDebugExecutable(runnable);
const debugConfig = knownEngines[debugEngine.id](runnable, simplifyPath(executable), debugOptions.sourceFileMap);
const env = prepareEnv(runnable, ctx.config.runnableEnv);
const debugConfig = knownEngines[debugEngine.id](runnable, simplifyPath(executable), env, debugOptions.sourceFileMap);
if (debugConfig.type in debugOptions.engineSettings) {
const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type];
for (var key in settingsMap) {
Expand Down Expand Up @@ -121,7 +123,7 @@ async function getDebugExecutable(runnable: ra.Runnable): Promise<string> {
return executable;
}

function getLldbDebugConfig(runnable: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
function getLldbDebugConfig(runnable: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
return {
type: "lldb",
request: "launch",
Expand All @@ -130,18 +132,20 @@ function getLldbDebugConfig(runnable: ra.Runnable, executable: string, sourceFil
args: runnable.args.executableArgs,
cwd: runnable.args.workspaceRoot,
sourceMap: sourceFileMap,
sourceLanguages: ["rust"]
sourceLanguages: ["rust"],
env
};
}

function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
return {
type: (os.platform() === "win32") ? "cppvsdbg" : "cppdbg",
request: "launch",
name: runnable.label,
program: executable,
args: runnable.args.executableArgs,
cwd: runnable.args.workspaceRoot,
sourceFileMap: sourceFileMap,
sourceFileMap,
env,
};
}
35 changes: 28 additions & 7 deletions editors/code/src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as tasks from './tasks';

import { Ctx } from './ctx';
import { makeDebugConfig } from './debug';
import { Config } from './config';
import { Config, RunnableEnvCfg } from './config';

const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }];

Expand Down Expand Up @@ -96,6 +96,30 @@ export class RunnableQuickPick implements vscode.QuickPickItem {
}
}

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

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

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

if (runnableEnvCfg) {
if (Array.isArray(runnableEnvCfg)) {
for (const it of runnableEnvCfg) {
if (!it.mask || new RegExp(it.mask).test(runnable.label)) {
Object.assign(env, it.env);
}
}
} else {
Object.assign(env, runnableEnvCfg);
}
}

return env;
}

export async function createTask(runnable: ra.Runnable, config: Config): Promise<vscode.Task> {
if (runnable.kind !== "cargo") {
// rust-analyzer supports only one kind, "cargo"
Expand All @@ -108,16 +132,13 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
if (runnable.args.executableArgs.length > 0) {
args.push('--', ...runnable.args.executableArgs);
}
const env: { [key: string]: string } = { "RUST_BACKTRACE": "short" };
if (runnable.args.expectTest) {
env["UPDATE_EXPECT"] = "1";
}

const definition: tasks.CargoTaskDefinition = {
type: tasks.TASK_TYPE,
command: args[0], // run, test, etc...
args: args.slice(1),
cwd: runnable.args.workspaceRoot,
env: Object.assign({}, process.env as { [key: string]: string }, env),
cwd: runnable.args.workspaceRoot || ".",
env: prepareEnv(runnable, config.runnableEnv),
};

const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate()
Expand Down
118 changes: 118 additions & 0 deletions editors/code/tests/unit/runnable_env.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import * as assert from 'assert';
import { prepareEnv } from '../../src/run';
import { RunnableEnvCfg } from '../../src/config';
import * as ra from '../../src/lsp_ext';

function makeRunnable(label: string): ra.Runnable {
return {
label,
kind: "cargo",
args: {
cargoArgs: [],
executableArgs: []
}
};
}

function fakePrepareEnv(runnableName: string, config: RunnableEnvCfg): Record<string, string> {
const runnable = makeRunnable(runnableName);
return prepareEnv(runnable, config);
}

suite('Runnable env', () => {
test('Global config works', () => {
const binEnv = fakePrepareEnv("run project_name", { "GLOBAL": "g" });
assert.equal(binEnv["GLOBAL"], "g");

const testEnv = fakePrepareEnv("test some::mod::test_name", { "GLOBAL": "g" });
assert.equal(testEnv["GLOBAL"], "g");
});

test('null mask works', () => {
const config = [
{
env: { DATA: "data" }
}
];
const binEnv = fakePrepareEnv("run project_name", config);
assert.equal(binEnv["DATA"], "data");

const testEnv = fakePrepareEnv("test some::mod::test_name", config);
assert.equal(testEnv["DATA"], "data");
});

test('order works', () => {
const config = [
{
env: { DATA: "data" }
},
{
env: { DATA: "newdata" }
}
];
const binEnv = fakePrepareEnv("run project_name", config);
assert.equal(binEnv["DATA"], "newdata");

const testEnv = fakePrepareEnv("test some::mod::test_name", config);
assert.equal(testEnv["DATA"], "newdata");
});

test('mask works', () => {
const config = [
{
env: { DATA: "data" }
},
{
mask: "^run",
env: { DATA: "rundata" }
},
{
mask: "special_test$",
env: { DATA: "special_test" }
}
];
const binEnv = fakePrepareEnv("run project_name", config);
assert.equal(binEnv["DATA"], "rundata");

const testEnv = fakePrepareEnv("test some::mod::test_name", config);
assert.equal(testEnv["DATA"], "data");

const specialTestEnv = fakePrepareEnv("test some::mod::special_test", config);
assert.equal(specialTestEnv["DATA"], "special_test");
});

test('exact test name works', () => {
const config = [
{
env: { DATA: "data" }
},
{
mask: "some::mod::test_name",
env: { DATA: "test special" }
}
];
const testEnv = fakePrepareEnv("test some::mod::test_name", config);
assert.equal(testEnv["DATA"], "test special");

const specialTestEnv = fakePrepareEnv("test some::mod::another_test", config);
assert.equal(specialTestEnv["DATA"], "data");
});

test('test mod name works', () => {
const config = [
{
env: { DATA: "data" }
},
{
mask: "some::mod",
env: { DATA: "mod special" }
}
];
const testEnv = fakePrepareEnv("test some::mod::test_name", config);
assert.equal(testEnv["DATA"], "mod special");

const specialTestEnv = fakePrepareEnv("test some::mod::another_test", config);
assert.equal(specialTestEnv["DATA"], "mod special");
});

});