1 import * as os from "os";
2 import * as vscode from 'vscode';
3 import * as path from 'path';
4 import * as ra from './lsp_ext';
6 import { Cargo, getRustcId, getSysroot } from './toolchain';
7 import { Ctx } from "./ctx";
8 import { prepareEnv } from "./run";
10 const debugOutput = vscode.window.createOutputChannel("Debug");
11 type DebugConfigProvider = (config: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration;
13 export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise<void> {
14 const scope = ctx.activeRustEditor?.document.uri;
17 const debugConfig = await getDebugConfiguration(ctx, runnable);
18 if (!debugConfig) return;
20 const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope);
21 const configurations = wsLaunchSection.get<any[]>("configurations") || [];
23 const index = configurations.findIndex(c => c.name === debugConfig.name);
25 const answer = await vscode.window.showErrorMessage(`Launch configuration '${debugConfig.name}' already exists!`, 'Cancel', 'Update');
26 if (answer === "Cancel") return;
28 configurations[index] = debugConfig;
30 configurations.push(debugConfig);
33 await wsLaunchSection.update("configurations", configurations);
36 export async function startDebugSession(ctx: Ctx, runnable: ra.Runnable): Promise<boolean> {
37 let debugConfig: vscode.DebugConfiguration | undefined = undefined;
40 const wsLaunchSection = vscode.workspace.getConfiguration("launch");
41 const configurations = wsLaunchSection.get<any[]>("configurations") || [];
43 const index = configurations.findIndex(c => c.name === runnable.label);
45 debugConfig = configurations[index];
46 message = " (from launch.json)";
49 debugConfig = await getDebugConfiguration(ctx, runnable);
52 if (!debugConfig) return false;
54 debugOutput.appendLine(`Launching debug configuration${message}:`);
55 debugOutput.appendLine(JSON.stringify(debugConfig, null, 2));
56 return vscode.debug.startDebugging(undefined, debugConfig);
59 async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<vscode.DebugConfiguration | undefined> {
60 const editor = ctx.activeRustEditor;
63 const knownEngines: Record<string, DebugConfigProvider> = {
64 "vadimcn.vscode-lldb": getLldbDebugConfig,
65 "ms-vscode.cpptools": getCppvsDebugConfig
67 const debugOptions = ctx.config.debug;
69 let debugEngine = null;
70 if (debugOptions.engine === "auto") {
71 for (var engineId in knownEngines) {
72 debugEngine = vscode.extensions.getExtension(engineId);
73 if (debugEngine) break;
76 debugEngine = vscode.extensions.getExtension(debugOptions.engine);
80 await vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)`
81 + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`);
86 if (ctx.config.debug.openDebugPane) {
87 debugOutput.show(true);
89 // folder exists or RA is not active.
90 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
91 const workspaceFolders = vscode.workspace.workspaceFolders!;
92 const isMultiFolderWorkspace = workspaceFolders.length > 1;
93 const firstWorkspace = workspaceFolders[0];
94 const workspace = !isMultiFolderWorkspace || !runnable.args.workspaceRoot ?
96 workspaceFolders.find(w => runnable.args.workspaceRoot?.includes(w.uri.fsPath)) || firstWorkspace;
98 const wsFolder = path.normalize(workspace.uri.fsPath);
99 const workspaceQualifier = isMultiFolderWorkspace ? `:${workspace.name}` : '';
100 function simplifyPath(p: string): string {
101 // see https://github.com/rust-analyzer/rust-analyzer/pull/5513#issuecomment-663458818 for why this is needed
102 return path.normalize(p).replace(wsFolder, '${workspaceFolder' + workspaceQualifier + '}');
105 const executable = await getDebugExecutable(runnable);
106 const env = prepareEnv(runnable, ctx.config.runnableEnv);
107 let sourceFileMap = debugOptions.sourceFileMap;
108 if (sourceFileMap === "auto") {
109 // let's try to use the default toolchain
110 const commitHash = await getRustcId(wsFolder);
111 const sysroot = await getSysroot(wsFolder);
112 const rustlib = path.normalize(sysroot + "/lib/rustlib/src/rust");
114 sourceFileMap[`/rustc/${commitHash}/`] = rustlib;
117 const debugConfig = knownEngines[debugEngine.id](runnable, simplifyPath(executable), env, sourceFileMap);
118 if (debugConfig.type in debugOptions.engineSettings) {
119 const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type];
120 for (var key in settingsMap) {
121 debugConfig[key] = settingsMap[key];
125 if (debugConfig.name === "run binary") {
126 // The LSP side: crates\rust-analyzer\src\main_loop\handlers.rs,
127 // fn to_lsp_runnable(...) with RunnableKind::Bin
128 debugConfig.name = `run ${path.basename(executable)}`;
131 if (debugConfig.cwd) {
132 debugConfig.cwd = simplifyPath(debugConfig.cwd);
138 async function getDebugExecutable(runnable: ra.Runnable): Promise<string> {
139 const cargo = new Cargo(runnable.args.workspaceRoot || '.', debugOutput);
140 const executable = await cargo.executableFromArgs(runnable.args.cargoArgs);
142 // if we are here, there were no compilation errors.
146 function getLldbDebugConfig(runnable: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
150 name: runnable.label,
152 args: runnable.args.executableArgs,
153 cwd: runnable.args.workspaceRoot,
154 sourceMap: sourceFileMap,
155 sourceLanguages: ["rust"],
160 function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
162 type: (os.platform() === "win32") ? "cppvsdbg" : "cppdbg",
164 name: runnable.label,
166 args: runnable.args.executableArgs,
167 cwd: runnable.args.workspaceRoot,