diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-05-15 15:29:01 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-05-15 15:29:01 +0100 |
commit | d51c1f62178c383363a2d95e865131d9a7b969d0 (patch) | |
tree | 5235615134ab3798f21a167c71ce175795fc6798 /editors/code/src | |
parent | 982b92f966518a0e24632fafdc18d7b5ab6928b4 (diff) | |
parent | a4ecaa70969067c1149711dbf1f40a8a95cb5b72 (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/code/src')
-rw-r--r-- | editors/code/src/cargo.ts | 15 | ||||
-rw-r--r-- | editors/code/src/commands/runnables.ts | 201 | ||||
-rw-r--r-- | editors/code/src/config.ts | 2 | ||||
-rw-r--r-- | editors/code/src/debug.ts | 124 | ||||
-rw-r--r-- | editors/code/src/main.ts | 2 |
5 files changed, 244 insertions, 100 deletions
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 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as lc from 'vscode-languageclient'; |
3 | import * as ra from '../rust-analyzer-api'; | 3 | import * as ra from '../rust-analyzer-api'; |
4 | import * as os from "os"; | ||
5 | 4 | ||
6 | import { Ctx, Cmd } from '../ctx'; | 5 | import { Ctx, Cmd } from '../ctx'; |
7 | import { Cargo } from '../cargo'; | 6 | import { startDebugSession, getDebugConfiguration } from '../debug'; |
8 | 7 | ||
9 | export function run(ctx: Ctx): Cmd { | 8 | const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }]; |
10 | let prevRunnable: RunnableQuickPick | undefined; | ||
11 | 9 | ||
12 | return async () => { | 10 | async 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 | |||
75 | export 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 | ||
67 | function getLldbDebugConfig(config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration { | 106 | export 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 | ||
80 | function getCppvsDebugConfig(config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration { | 119 | export 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 | ||
92 | const debugOutput = vscode.window.createOutputChannel("Debug"); | 125 | async function makeDebugConfig(ctx: Ctx, item: RunnableQuickPick): Promise<void> { |
93 | 126 | const scope = ctx.activeRustEditor?.document.uri; | |
94 | async 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 | ||
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; | ||
103 | 131 | ||
104 | export 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); | 148 | export 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 @@ | |||
1 | import * as os from "os"; | ||
2 | import * as vscode from 'vscode'; | ||
3 | import * as path from 'path'; | ||
4 | import * as ra from './rust-analyzer-api'; | ||
5 | |||
6 | import { Cargo } from './cargo'; | ||
7 | import { Ctx } from "./ctx"; | ||
8 | |||
9 | const debugOutput = vscode.window.createOutputChannel("Debug"); | ||
10 | type DebugConfigProvider = (config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration; | ||
11 | |||
12 | function 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 | |||
25 | function 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 | |||
37 | async 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 | |||
45 | export 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 | |||
103 | export 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); |