Skip to content

Commit d51c1f6

Browse files
bors[bot]vsrs
andauthored
Merge #4448
4448: Generate configuration for launch.json r=vsrs a=vsrs This PR adds two new commands: `"rust-analyzer.debug"` and `"rust-analyzer.newDebugConfig"`. The former is a supplement to the existing `"rust-analyzer.run"` command and works the same way: asks for a runnable and starts new debug session. The latter allows adding a new configuration to **launch.json** (or to update an existing one). If the new option `"rust-analyzer.debug.useLaunchJson"` is set to true then `"rust-analyzer.debug"` and Debug Lens will first look for existing debug configuration in **launch.json**. That is, it has become possible to specify startup arguments, env variables, etc. `"rust-analyzer.debug.useLaunchJson"` is false by default, but it might be worth making true the default value. Personally I prefer true, but I'm not sure if it is good for all value. ---- I think that this PR also solves #3441. Both methods to update launch.json mentioned in the issue do not work: 1. Menu. It is only possible to add a launch.json configuration template via a debug adapter. And anyway it's only a template and it is impossible to specify arguments from an extension. 2. DebugConfigurationProvider. The exact opposite situation: it is possible to specify all debug session settings, but it is impossible to export these settings to launch.json. Separate `"rust-analyzer.newDebugConfig"` command looks better for me. ---- Fixes #4450 Fixes #3441 Co-authored-by: vsrs <[email protected]> Co-authored-by: vsrs <[email protected]>
2 parents 982b92f + a4ecaa7 commit d51c1f6

File tree

7 files changed

+258
-101
lines changed

7 files changed

+258
-101
lines changed

crates/rust-analyzer/src/main_loop/handlers.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1011,14 +1011,17 @@ fn to_lsp_runnable(
10111011
runnable: Runnable,
10121012
) -> Result<lsp_ext::Runnable> {
10131013
let spec = CargoTargetSpec::for_file(world, file_id)?;
1014+
let target = spec.as_ref().map(|s| s.target.clone());
10141015
let (args, extra_args) = CargoTargetSpec::runnable_args(spec, &runnable.kind)?;
10151016
let line_index = world.analysis().file_line_index(file_id)?;
10161017
let label = match &runnable.kind {
10171018
RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
10181019
RunnableKind::TestMod { path } => format!("test-mod {}", path),
10191020
RunnableKind::Bench { test_id } => format!("bench {}", test_id),
10201021
RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id),
1021-
RunnableKind::Bin => "run binary".to_string(),
1022+
RunnableKind::Bin => {
1023+
target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t))
1024+
}
10221025
};
10231026
Ok(lsp_ext::Runnable {
10241027
range: to_proto::range(&line_index, runnable.range),

editors/code/package.json

+10
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,16 @@
120120
"title": "Run",
121121
"category": "Rust Analyzer"
122122
},
123+
{
124+
"command": "rust-analyzer.debug",
125+
"title": "Debug",
126+
"category": "Rust Analyzer"
127+
},
128+
{
129+
"command": "rust-analyzer.newDebugConfig",
130+
"title": "Generate launch configuration",
131+
"category": "Rust Analyzer"
132+
},
123133
{
124134
"command": "rust-analyzer.analyzerStatus",
125135
"title": "Status",

editors/code/src/cargo.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,20 @@ export class Cargo {
4949
async executableFromArgs(args: readonly string[]): Promise<string> {
5050
const cargoArgs = [...args, "--message-format=json"];
5151

52-
const artifacts = await this.artifactsFromArgs(cargoArgs);
52+
// arguments for a runnable from the quick pick should be updated.
53+
// see crates\rust-analyzer\src\main_loop\handlers.rs, handle_code_lens
54+
if (cargoArgs[0] === "run") {
55+
cargoArgs[0] = "build";
56+
} else if (cargoArgs.indexOf("--no-run") === -1) {
57+
cargoArgs.push("--no-run");
58+
}
59+
60+
let artifacts = await this.artifactsFromArgs(cargoArgs);
61+
if (cargoArgs[0] === "test") {
62+
// for instance, `crates\rust-analyzer\tests\heavy_tests\main.rs` tests
63+
// produce 2 artifacts: {"kind": "bin"} and {"kind": "test"}
64+
artifacts = artifacts.filter(a => a.isTest);
65+
}
5366

5467
if (artifacts.length === 0) {
5568
throw new Error('No compilation artifacts');

editors/code/src/commands/runnables.ts

+103-98
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,82 @@
11
import * as vscode from 'vscode';
22
import * as lc from 'vscode-languageclient';
33
import * as ra from '../rust-analyzer-api';
4-
import * as os from "os";
54

65
import { Ctx, Cmd } from '../ctx';
7-
import { Cargo } from '../cargo';
6+
import { startDebugSession, getDebugConfiguration } from '../debug';
87

9-
export function run(ctx: Ctx): Cmd {
10-
let prevRunnable: RunnableQuickPick | undefined;
8+
const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }];
119

12-
return async () => {
13-
const editor = ctx.activeRustEditor;
14-
const client = ctx.client;
15-
if (!editor || !client) return;
10+
async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, showButtons: boolean = true): Promise<RunnableQuickPick | undefined> {
11+
const editor = ctx.activeRustEditor;
12+
const client = ctx.client;
13+
if (!editor || !client) return;
1614

17-
const textDocument: lc.TextDocumentIdentifier = {
18-
uri: editor.document.uri.toString(),
19-
};
15+
const textDocument: lc.TextDocumentIdentifier = {
16+
uri: editor.document.uri.toString(),
17+
};
2018

21-
const runnables = await client.sendRequest(ra.runnables, {
22-
textDocument,
23-
position: client.code2ProtocolConverter.asPosition(
24-
editor.selection.active,
25-
),
26-
});
27-
const items: RunnableQuickPick[] = [];
28-
if (prevRunnable) {
29-
items.push(prevRunnable);
19+
const runnables = await client.sendRequest(ra.runnables, {
20+
textDocument,
21+
position: client.code2ProtocolConverter.asPosition(
22+
editor.selection.active,
23+
),
24+
});
25+
const items: RunnableQuickPick[] = [];
26+
if (prevRunnable) {
27+
items.push(prevRunnable);
28+
}
29+
for (const r of runnables) {
30+
if (
31+
prevRunnable &&
32+
JSON.stringify(prevRunnable.runnable) === JSON.stringify(r)
33+
) {
34+
continue;
3035
}
31-
for (const r of runnables) {
32-
if (
33-
prevRunnable &&
34-
JSON.stringify(prevRunnable.runnable) === JSON.stringify(r)
35-
) {
36-
continue;
37-
}
38-
items.push(new RunnableQuickPick(r));
36+
items.push(new RunnableQuickPick(r));
37+
}
38+
39+
return await new Promise((resolve) => {
40+
const disposables: vscode.Disposable[] = [];
41+
const close = (result?: RunnableQuickPick) => {
42+
resolve(result);
43+
disposables.forEach(d => d.dispose());
44+
};
45+
46+
const quickPick = vscode.window.createQuickPick<RunnableQuickPick>();
47+
quickPick.items = items;
48+
quickPick.title = "Select Runnable";
49+
if (showButtons) {
50+
quickPick.buttons = quickPickButtons;
3951
}
40-
const item = await vscode.window.showQuickPick(items);
52+
disposables.push(
53+
quickPick.onDidHide(() => close()),
54+
quickPick.onDidAccept(() => close(quickPick.selectedItems[0])),
55+
quickPick.onDidTriggerButton((_button) => {
56+
(async () => await makeDebugConfig(ctx, quickPick.activeItems[0]))();
57+
close();
58+
}),
59+
quickPick.onDidChangeActive((active) => {
60+
if (showButtons && active.length > 0) {
61+
if (active[0].label.startsWith('cargo')) {
62+
// save button makes no sense for `cargo test` or `cargo check`
63+
quickPick.buttons = [];
64+
} else if (quickPick.buttons.length === 0) {
65+
quickPick.buttons = quickPickButtons;
66+
}
67+
}
68+
}),
69+
quickPick
70+
);
71+
quickPick.show();
72+
});
73+
}
74+
75+
export function run(ctx: Ctx): Cmd {
76+
let prevRunnable: RunnableQuickPick | undefined;
77+
78+
return async () => {
79+
const item = await selectRunnable(ctx, prevRunnable);
4180
if (!item) return;
4281

4382
item.detail = 'rerun';
@@ -64,88 +103,54 @@ export function runSingle(ctx: Ctx): Cmd {
64103
};
65104
}
66105

67-
function getLldbDebugConfig(config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
68-
return {
69-
type: "lldb",
70-
request: "launch",
71-
name: config.label,
72-
program: executable,
73-
args: config.extraArgs,
74-
cwd: config.cwd,
75-
sourceMap: sourceFileMap,
76-
sourceLanguages: ["rust"]
106+
export function debug(ctx: Ctx): Cmd {
107+
let prevDebuggee: RunnableQuickPick | undefined;
108+
109+
return async () => {
110+
const item = await selectRunnable(ctx, prevDebuggee);
111+
if (!item) return;
112+
113+
item.detail = 'restart';
114+
prevDebuggee = item;
115+
return await startDebugSession(ctx, item.runnable);
77116
};
78117
}
79118

80-
function getCppvsDebugConfig(config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
81-
return {
82-
type: (os.platform() === "win32") ? "cppvsdbg" : 'cppdbg',
83-
request: "launch",
84-
name: config.label,
85-
program: executable,
86-
args: config.extraArgs,
87-
cwd: config.cwd,
88-
sourceFileMap: sourceFileMap,
119+
export function debugSingle(ctx: Ctx): Cmd {
120+
return async (config: ra.Runnable) => {
121+
await startDebugSession(ctx, config);
89122
};
90123
}
91124

92-
const debugOutput = vscode.window.createOutputChannel("Debug");
93-
94-
async function getDebugExecutable(config: ra.Runnable): Promise<string> {
95-
const cargo = new Cargo(config.cwd || '.', debugOutput);
96-
const executable = await cargo.executableFromArgs(config.args);
97-
98-
// if we are here, there were no compilation errors.
99-
return executable;
100-
}
125+
async function makeDebugConfig(ctx: Ctx, item: RunnableQuickPick): Promise<void> {
126+
const scope = ctx.activeRustEditor?.document.uri;
127+
if (!scope) return;
101128

102-
type DebugConfigProvider = (config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration;
129+
const debugConfig = await getDebugConfiguration(ctx, item.runnable);
130+
if (!debugConfig) return;
103131

104-
export function debugSingle(ctx: Ctx): Cmd {
105-
return async (config: ra.Runnable) => {
106-
const editor = ctx.activeRustEditor;
107-
if (!editor) return;
132+
const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope);
133+
const configurations = wsLaunchSection.get<any[]>("configurations") || [];
108134

109-
const knownEngines: Record<string, DebugConfigProvider> = {
110-
"vadimcn.vscode-lldb": getLldbDebugConfig,
111-
"ms-vscode.cpptools": getCppvsDebugConfig
112-
};
113-
const debugOptions = ctx.config.debug;
114-
115-
let debugEngine = null;
116-
if (debugOptions.engine === "auto") {
117-
for (var engineId in knownEngines) {
118-
debugEngine = vscode.extensions.getExtension(engineId);
119-
if (debugEngine) break;
120-
}
121-
}
122-
else {
123-
debugEngine = vscode.extensions.getExtension(debugOptions.engine);
124-
}
135+
const index = configurations.findIndex(c => c.name === debugConfig.name);
136+
if (index !== -1) {
137+
const answer = await vscode.window.showErrorMessage(`Launch configuration '${debugConfig.name}' already exists!`, 'Cancel', 'Update');
138+
if (answer === "Cancel") return;
125139

126-
if (!debugEngine) {
127-
vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)`
128-
+ ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`);
129-
return;
130-
}
140+
configurations[index] = debugConfig;
141+
} else {
142+
configurations.push(debugConfig);
143+
}
131144

132-
debugOutput.clear();
133-
if (ctx.config.debug.openUpDebugPane) {
134-
debugOutput.show(true);
135-
}
145+
await wsLaunchSection.update("configurations", configurations);
146+
}
136147

137-
const executable = await getDebugExecutable(config);
138-
const debugConfig = knownEngines[debugEngine.id](config, executable, debugOptions.sourceFileMap);
139-
if (debugConfig.type in debugOptions.engineSettings) {
140-
const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type];
141-
for (var key in settingsMap) {
142-
debugConfig[key] = settingsMap[key];
143-
}
144-
}
148+
export function newDebugConfig(ctx: Ctx): Cmd {
149+
return async () => {
150+
const item = await selectRunnable(ctx, undefined, false);
151+
if (!item) return;
145152

146-
debugOutput.appendLine("Launching debug configuration:");
147-
debugOutput.appendLine(JSON.stringify(debugConfig, null, 2));
148-
return vscode.debug.startDebugging(undefined, debugConfig);
153+
await makeDebugConfig(ctx, item);
149154
};
150155
}
151156

editors/code/src/config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export class Config {
116116
engine: this.get<string>("debug.engine"),
117117
engineSettings: this.get<object>("debug.engineSettings"),
118118
openUpDebugPane: this.get<boolean>("debug.openUpDebugPane"),
119-
sourceFileMap: sourceFileMap,
119+
sourceFileMap: sourceFileMap
120120
};
121121
}
122122
}

0 commit comments

Comments
 (0)