aboutsummaryrefslogtreecommitdiff
path: root/editors
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-05-15 15:29:01 +0100
committerGitHub <[email protected]>2020-05-15 15:29:01 +0100
commitd51c1f62178c383363a2d95e865131d9a7b969d0 (patch)
tree5235615134ab3798f21a167c71ce175795fc6798 /editors
parent982b92f966518a0e24632fafdc18d7b5ab6928b4 (diff)
parenta4ecaa70969067c1149711dbf1f40a8a95cb5b72 (diff)
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 https://github.com/rust-analyzer/rust-analyzer/issues/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]>
Diffstat (limited to 'editors')
-rw-r--r--editors/code/package.json10
-rw-r--r--editors/code/src/cargo.ts15
-rw-r--r--editors/code/src/commands/runnables.ts201
-rw-r--r--editors/code/src/config.ts2
-rw-r--r--editors/code/src/debug.ts124
-rw-r--r--editors/code/src/main.ts2
6 files changed, 254 insertions, 100 deletions
diff --git a/editors/code/package.json b/editors/code/package.json
index b8a2182f0..4e7e3faf7 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -121,6 +121,16 @@
121 "category": "Rust Analyzer" 121 "category": "Rust Analyzer"
122 }, 122 },
123 { 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 },
133 {
124 "command": "rust-analyzer.analyzerStatus", 134 "command": "rust-analyzer.analyzerStatus",
125 "title": "Status", 135 "title": "Status",
126 "category": "Rust Analyzer" 136 "category": "Rust Analyzer"
diff --git a/editors/code/src/cargo.ts b/editors/code/src/cargo.ts
index 2a2c2e0e1..28c7de992 100644
--- a/editors/code/src/cargo.ts
+++ b/editors/code/src/cargo.ts
@@ -49,7 +49,20 @@ export class Cargo {
49 async executableFromArgs(args: readonly string[]): Promise<string> { 49 async executableFromArgs(args: readonly string[]): Promise<string> {
50 const cargoArgs = [...args, "--message-format=json"]; 50 const cargoArgs = [...args, "--message-format=json"];
51 51
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 }
53 66
54 if (artifacts.length === 0) { 67 if (artifacts.length === 0) {
55 throw new Error('No compilation artifacts'); 68 throw new Error('No compilation artifacts');
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts
index ae328d2a4..b1d93fc34 100644
--- a/editors/code/src/commands/runnables.ts
+++ b/editors/code/src/commands/runnables.ts
@@ -1,43 +1,82 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
3import * as ra from '../rust-analyzer-api'; 3import * as ra from '../rust-analyzer-api';
4import * as os from "os";
5 4
6import { Ctx, Cmd } from '../ctx'; 5import { Ctx, Cmd } from '../ctx';
7import { Cargo } from '../cargo'; 6import { startDebugSession, getDebugConfiguration } from '../debug';
8 7
9export function run(ctx: Ctx): Cmd { 8const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }];
10 let prevRunnable: RunnableQuickPick | undefined;
11 9
12 return async () => { 10async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, showButtons: boolean = true): Promise<RunnableQuickPick | undefined> {
13 const editor = ctx.activeRustEditor; 11 const editor = ctx.activeRustEditor;
14 const client = ctx.client; 12 const client = ctx.client;
15 if (!editor || !client) return; 13 if (!editor || !client) return;
16 14
17 const textDocument: lc.TextDocumentIdentifier = { 15 const textDocument: lc.TextDocumentIdentifier = {
18 uri: editor.document.uri.toString(), 16 uri: editor.document.uri.toString(),
19 }; 17 };
20 18
21 const runnables = await client.sendRequest(ra.runnables, { 19 const runnables = await client.sendRequest(ra.runnables, {
22 textDocument, 20 textDocument,
23 position: client.code2ProtocolConverter.asPosition( 21 position: client.code2ProtocolConverter.asPosition(
24 editor.selection.active, 22 editor.selection.active,
25 ), 23 ),
26 }); 24 });
27 const items: RunnableQuickPick[] = []; 25 const items: RunnableQuickPick[] = [];
28 if (prevRunnable) { 26 if (prevRunnable) {
29 items.push(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;
30 } 35 }
31 for (const r of runnables) { 36 items.push(new RunnableQuickPick(r));
32 if ( 37 }
33 prevRunnable && 38
34 JSON.stringify(prevRunnable.runnable) === JSON.stringify(r) 39 return await new Promise((resolve) => {
35 ) { 40 const disposables: vscode.Disposable[] = [];
36 continue; 41 const close = (result?: RunnableQuickPick) => {
37 } 42 resolve(result);
38 items.push(new RunnableQuickPick(r)); 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;
39 } 51 }
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
75export function run(ctx: Ctx): Cmd {
76 let prevRunnable: RunnableQuickPick | undefined;
77
78 return async () => {
79 const item = await selectRunnable(ctx, prevRunnable);
41 if (!item) return; 80 if (!item) return;
42 81
43 item.detail = 'rerun'; 82 item.detail = 'rerun';
@@ -64,88 +103,54 @@ export function runSingle(ctx: Ctx): Cmd {
64 }; 103 };
65} 104}
66 105
67function getLldbDebugConfig(config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration { 106export function debug(ctx: Ctx): Cmd {
68 return { 107 let prevDebuggee: RunnableQuickPick | undefined;
69 type: "lldb", 108
70 request: "launch", 109 return async () => {
71 name: config.label, 110 const item = await selectRunnable(ctx, prevDebuggee);
72 program: executable, 111 if (!item) return;
73 args: config.extraArgs, 112
74 cwd: config.cwd, 113 item.detail = 'restart';
75 sourceMap: sourceFileMap, 114 prevDebuggee = item;
76 sourceLanguages: ["rust"] 115 return await startDebugSession(ctx, item.runnable);
77 }; 116 };
78} 117}
79 118
80function getCppvsDebugConfig(config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration { 119export function debugSingle(ctx: Ctx): Cmd {
81 return { 120 return async (config: ra.Runnable) => {
82 type: (os.platform() === "win32") ? "cppvsdbg" : 'cppdbg', 121 await startDebugSession(ctx, config);
83 request: "launch",
84 name: config.label,
85 program: executable,
86 args: config.extraArgs,
87 cwd: config.cwd,
88 sourceFileMap: sourceFileMap,
89 }; 122 };
90} 123}
91 124
92const debugOutput = vscode.window.createOutputChannel("Debug"); 125async function makeDebugConfig(ctx: Ctx, item: RunnableQuickPick): Promise<void> {
93 126 const scope = ctx.activeRustEditor?.document.uri;
94async function getDebugExecutable(config: ra.Runnable): Promise<string> { 127 if (!scope) return;
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}
101 128
102type DebugConfigProvider = (config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration; 129 const debugConfig = await getDebugConfiguration(ctx, item.runnable);
130 if (!debugConfig) return;
103 131
104export function debugSingle(ctx: Ctx): Cmd { 132 const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope);
105 return async (config: ra.Runnable) => { 133 const configurations = wsLaunchSection.get<any[]>("configurations") || [];
106 const editor = ctx.activeRustEditor;
107 if (!editor) return;
108 134
109 const knownEngines: Record<string, DebugConfigProvider> = { 135 const index = configurations.findIndex(c => c.name === debugConfig.name);
110 "vadimcn.vscode-lldb": getLldbDebugConfig, 136 if (index !== -1) {
111 "ms-vscode.cpptools": getCppvsDebugConfig 137 const answer = await vscode.window.showErrorMessage(`Launch configuration '${debugConfig.name}' already exists!`, 'Cancel', 'Update');
112 }; 138 if (answer === "Cancel") return;
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 }
125 139
126 if (!debugEngine) { 140 configurations[index] = debugConfig;
127 vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)` 141 } else {
128 + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`); 142 configurations.push(debugConfig);
129 return; 143 }
130 }
131 144
132 debugOutput.clear(); 145 await wsLaunchSection.update("configurations", configurations);
133 if (ctx.config.debug.openUpDebugPane) { 146}
134 debugOutput.show(true);
135 }
136 147
137 const executable = await getDebugExecutable(config); 148export function newDebugConfig(ctx: Ctx): Cmd {
138 const debugConfig = knownEngines[debugEngine.id](config, executable, debugOptions.sourceFileMap); 149 return async () => {
139 if (debugConfig.type in debugOptions.engineSettings) { 150 const item = await selectRunnable(ctx, undefined, false);
140 const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type]; 151 if (!item) return;
141 for (var key in settingsMap) {
142 debugConfig[key] = settingsMap[key];
143 }
144 }
145 152
146 debugOutput.appendLine("Launching debug configuration:"); 153 await makeDebugConfig(ctx, item);
147 debugOutput.appendLine(JSON.stringify(debugConfig, null, 2));
148 return vscode.debug.startDebugging(undefined, debugConfig);
149 }; 154 };
150} 155}
151 156
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index be2e27aec..1652827c3 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -116,7 +116,7 @@ export class Config {
116 engine: this.get<string>("debug.engine"), 116 engine: this.get<string>("debug.engine"),
117 engineSettings: this.get<object>("debug.engineSettings"), 117 engineSettings: this.get<object>("debug.engineSettings"),
118 openUpDebugPane: this.get<boolean>("debug.openUpDebugPane"), 118 openUpDebugPane: this.get<boolean>("debug.openUpDebugPane"),
119 sourceFileMap: sourceFileMap, 119 sourceFileMap: sourceFileMap
120 }; 120 };
121 } 121 }
122} 122}
diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts
new file mode 100644
index 000000000..d3fe588e8
--- /dev/null
+++ b/editors/code/src/debug.ts
@@ -0,0 +1,124 @@
1import * as os from "os";
2import * as vscode from 'vscode';
3import * as path from 'path';
4import * as ra from './rust-analyzer-api';
5
6import { Cargo } from './cargo';
7import { Ctx } from "./ctx";
8
9const debugOutput = vscode.window.createOutputChannel("Debug");
10type DebugConfigProvider = (config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration;
11
12function getLldbDebugConfig(config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
13 return {
14 type: "lldb",
15 request: "launch",
16 name: config.label,
17 program: executable,
18 args: config.extraArgs,
19 cwd: config.cwd,
20 sourceMap: sourceFileMap,
21 sourceLanguages: ["rust"]
22 };
23}
24
25function getCppvsDebugConfig(config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
26 return {
27 type: (os.platform() === "win32") ? "cppvsdbg" : "cppdbg",
28 request: "launch",
29 name: config.label,
30 program: executable,
31 args: config.extraArgs,
32 cwd: config.cwd,
33 sourceFileMap: sourceFileMap,
34 };
35}
36
37async function getDebugExecutable(config: ra.Runnable): Promise<string> {
38 const cargo = new Cargo(config.cwd || '.', debugOutput);
39 const executable = await cargo.executableFromArgs(config.args);
40
41 // if we are here, there were no compilation errors.
42 return executable;
43}
44
45export async function getDebugConfiguration(ctx: Ctx, config: ra.Runnable): Promise<vscode.DebugConfiguration | undefined> {
46 const editor = ctx.activeRustEditor;
47 if (!editor) return;
48
49 const knownEngines: Record<string, DebugConfigProvider> = {
50 "vadimcn.vscode-lldb": getLldbDebugConfig,
51 "ms-vscode.cpptools": getCppvsDebugConfig
52 };
53 const debugOptions = ctx.config.debug;
54
55 let debugEngine = null;
56 if (debugOptions.engine === "auto") {
57 for (var engineId in knownEngines) {
58 debugEngine = vscode.extensions.getExtension(engineId);
59 if (debugEngine) break;
60 }
61 } else {
62 debugEngine = vscode.extensions.getExtension(debugOptions.engine);
63 }
64
65 if (!debugEngine) {
66 vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)`
67 + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`);
68 return;
69 }
70
71 debugOutput.clear();
72 if (ctx.config.debug.openUpDebugPane) {
73 debugOutput.show(true);
74 }
75
76 const wsFolder = path.normalize(vscode.workspace.workspaceFolders![0].uri.fsPath); // folder exists or RA is not active.
77 function simplifyPath(p: string): string {
78 return path.normalize(p).replace(wsFolder, '${workspaceRoot}');
79 }
80
81 const executable = await getDebugExecutable(config);
82 const debugConfig = knownEngines[debugEngine.id](config, simplifyPath(executable), debugOptions.sourceFileMap);
83 if (debugConfig.type in debugOptions.engineSettings) {
84 const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type];
85 for (var key in settingsMap) {
86 debugConfig[key] = settingsMap[key];
87 }
88 }
89
90 if (debugConfig.name === "run binary") {
91 // The LSP side: crates\rust-analyzer\src\main_loop\handlers.rs,
92 // fn to_lsp_runnable(...) with RunnableKind::Bin
93 debugConfig.name = `run ${path.basename(executable)}`;
94 }
95
96 if (debugConfig.cwd) {
97 debugConfig.cwd = simplifyPath(debugConfig.cwd);
98 }
99
100 return debugConfig;
101}
102
103export async function startDebugSession(ctx: Ctx, config: ra.Runnable): Promise<boolean> {
104 let debugConfig: vscode.DebugConfiguration | undefined = undefined;
105 let message = "";
106
107 const wsLaunchSection = vscode.workspace.getConfiguration("launch");
108 const configurations = wsLaunchSection.get<any[]>("configurations") || [];
109
110 const index = configurations.findIndex(c => c.name === config.label);
111 if (-1 !== index) {
112 debugConfig = configurations[index];
113 message = " (from launch.json)";
114 debugOutput.clear();
115 } else {
116 debugConfig = await getDebugConfiguration(ctx, config);
117 }
118
119 if (!debugConfig) return false;
120
121 debugOutput.appendLine(`Launching debug configuration${message}:`);
122 debugOutput.appendLine(JSON.stringify(debugConfig, null, 2));
123 return vscode.debug.startDebugging(undefined, debugConfig);
124}
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 9b020d001..c015460b8 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -77,6 +77,8 @@ export async function activate(context: vscode.ExtensionContext) {
77 ctx.registerCommand('syntaxTree', commands.syntaxTree); 77 ctx.registerCommand('syntaxTree', commands.syntaxTree);
78 ctx.registerCommand('expandMacro', commands.expandMacro); 78 ctx.registerCommand('expandMacro', commands.expandMacro);
79 ctx.registerCommand('run', commands.run); 79 ctx.registerCommand('run', commands.run);
80 ctx.registerCommand('debug', commands.debug);
81 ctx.registerCommand('newDebugConfig', commands.newDebugConfig);
80 82
81 defaultOnEnter.dispose(); 83 defaultOnEnter.dispose();
82 ctx.registerCommand('onEnter', commands.onEnter); 84 ctx.registerCommand('onEnter', commands.onEnter);