diff --git a/editors/code/package.json b/editors/code/package.json index c4dfa7e13d..d30673791f 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -389,6 +389,28 @@ "description": "Enable Proc macro support, cargo.loadOutDirsFromCheck must be enabled.", "type": "boolean", "default": false + }, + "rust-analyzer.debug.engine": { + "type": "string", + "enum": [ + "auto", + "vadimcn.vscode-lldb", + "ms-vscode.cpptools" + ], + "default": "auto", + "description": "Preffered debug engine.", + "markdownEnumDescriptions": [ + "First try to use [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb), if it's not installed try to use [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools).", + "Use [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)", + "Use [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools)" + ] + }, + "rust-analyzer.debug.sourceFileMap": { + "type": "object", + "description": "Optional source file mappings passed to the debug engine.", + "default": { + "/rustc/": "${env:USERPROFILE}/.rustup/toolchains//lib/rustlib/src/rust" + } } } }, diff --git a/editors/code/src/cargo.ts b/editors/code/src/cargo.ts new file mode 100644 index 0000000000..a328ba9bd0 --- /dev/null +++ b/editors/code/src/cargo.ts @@ -0,0 +1,106 @@ +import * as cp from 'child_process'; +import * as readline from 'readline'; +import { OutputChannel } from 'vscode'; + +interface CompilationArtifact { + fileName: string; + name: string; + kind: string; + isTest: boolean; +} + +export class Cargo { + rootFolder: string; + env?: Record; + output: OutputChannel; + + public constructor(cargoTomlFolder: string, output: OutputChannel, env: Record | undefined = undefined) { + this.rootFolder = cargoTomlFolder; + this.output = output; + this.env = env; + } + + public async artifactsFromArgs(cargoArgs: string[]): Promise { + const artifacts: CompilationArtifact[] = []; + + try { + await this.runCargo(cargoArgs, + message => { + if (message.reason === 'compiler-artifact' && message.executable) { + const isBinary = message.target.crate_types.includes('bin'); + const isBuildScript = message.target.kind.includes('custom-build'); + if ((isBinary && !isBuildScript) || message.profile.test) { + artifacts.push({ + fileName: message.executable, + name: message.target.name, + kind: message.target.kind[0], + isTest: message.profile.test + }); + } + } + else if (message.reason === 'compiler-message') { + this.output.append(message.message.rendered); + } + }, + stderr => { + this.output.append(stderr); + } + ); + } + catch (err) { + this.output.show(true); + throw new Error(`Cargo invocation has failed: ${err}`); + } + + return artifacts; + } + + public async executableFromArgs(args: string[]): Promise { + const cargoArgs = [...args]; // to remain args unchanged + cargoArgs.push("--message-format=json"); + + const artifacts = await this.artifactsFromArgs(cargoArgs); + + if (artifacts.length === 0) { + throw new Error('No compilation artifacts'); + } else if (artifacts.length > 1) { + throw new Error('Multiple compilation artifacts are not supported.'); + } + + return artifacts[0].fileName; + } + + runCargo( + cargoArgs: string[], + onStdoutJson: (obj: any) => void, + onStderrString: (data: string) => void + ): Promise { + return new Promise((resolve, reject) => { + const cargo = cp.spawn('cargo', cargoArgs, { + stdio: ['ignore', 'pipe', 'pipe'], + cwd: this.rootFolder, + env: this.env, + }); + + cargo.on('error', err => { + reject(new Error(`could not launch cargo: ${err}`)); + }); + cargo.stderr.on('data', chunk => { + onStderrString(chunk.toString()); + }); + + const rl = readline.createInterface({ input: cargo.stdout }); + rl.on('line', line => { + const message = JSON.parse(line); + onStdoutJson(message); + }); + + cargo.on('exit', (exitCode, _) => { + if (exitCode === 0) + resolve(exitCode); + else + reject(new Error(`exit code: ${exitCode}.`)); + }); + }); + } +} \ No newline at end of file diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts index 2635a14408..d77e8188c7 100644 --- a/editors/code/src/commands/runnables.ts +++ b/editors/code/src/commands/runnables.ts @@ -1,8 +1,10 @@ import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; import * as ra from '../rust-analyzer-api'; +import * as os from "os"; import { Ctx, Cmd } from '../ctx'; +import { Cargo } from '../cargo'; export function run(ctx: Ctx): Cmd { let prevRunnable: RunnableQuickPick | undefined; @@ -62,25 +64,69 @@ export function runSingle(ctx: Ctx): Cmd { }; } +function getLldbDebugConfig(config: ra.Runnable, sourceFileMap: Record): vscode.DebugConfiguration { + return { + type: "lldb", + request: "launch", + name: config.label, + cargo: { + args: config.args, + }, + args: config.extraArgs, + cwd: config.cwd, + sourceMap: sourceFileMap + }; +} + +const debugOutput = vscode.window.createOutputChannel("Debug"); + +async function getCppvsDebugConfig(config: ra.Runnable, sourceFileMap: Record): Promise { + debugOutput.clear(); + + const cargo = new Cargo(config.cwd || '.', debugOutput); + const executable = await cargo.executableFromArgs(config.args); + + // if we are here, there were no compilation errors. + return { + type: (os.platform() === "win32") ? "cppvsdbg" : 'cppdbg', + request: "launch", + name: config.label, + program: executable, + args: config.extraArgs, + cwd: config.cwd, + sourceFileMap: sourceFileMap, + }; +} + export function debugSingle(ctx: Ctx): Cmd { return async (config: ra.Runnable) => { const editor = ctx.activeRustEditor; if (!editor) return; - if (!vscode.extensions.getExtension("vadimcn.vscode-lldb")) { - vscode.window.showErrorMessage("Install `vadimcn.vscode-lldb` extension for debugging"); + + const lldbId = "vadimcn.vscode-lldb"; + const cpptoolsId = "ms-vscode.cpptools"; + + const debugEngineId = ctx.config.debug.engine; + let debugEngine = null; + if (debugEngineId === "auto") { + debugEngine = vscode.extensions.getExtension(lldbId); + if (!debugEngine) { + debugEngine = vscode.extensions.getExtension(cpptoolsId); + } + } + else { + debugEngine = vscode.extensions.getExtension(debugEngineId); + } + + if (!debugEngine) { + vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=${lldbId})` + + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=${cpptoolsId}) extension for debugging.`); return; } - const debugConfig = { - type: "lldb", - request: "launch", - name: config.label, - cargo: { - args: config.args, - }, - args: config.extraArgs, - cwd: config.cwd - }; + const debugConfig = lldbId === debugEngine.id + ? getLldbDebugConfig(config, ctx.config.debug.sourceFileMap) + : await getCppvsDebugConfig(config, ctx.config.debug.sourceFileMap); return vscode.debug.startDebugging(undefined, debugConfig); }; diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 3b2eec8baa..110e541800 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -92,7 +92,6 @@ export class Config { get askBeforeDownload() { return this.get("updates.askBeforeDownload"); } get traceExtension() { return this.get("trace.extension"); } - get inlayHints() { return { typeHints: this.get("inlayHints.typeHints"), @@ -107,4 +106,12 @@ export class Config { command: this.get("checkOnSave.command"), }; } + + get debug() { + return { + engine: this.get("debug.engine"), + sourceFileMap: this.get>("debug.sourceFileMap"), + }; + } + }