mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 13:03:31 +00:00
Merge #4222
4222: Introduce C/C++ for Visual Studio Code extension as an alternative debug engine for Debug Code lens. r=matklad a=vsrs At the moment Debug Code Lens can use only one debug engine: lldb via [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb) extension. This PR adds support of the debug engine from the [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension, as well as the configuration option. If both extensions are installed, `CodeLLDB` will be used by default. Another new option `rust-analyzer.debug.sourceFileMap` allows, for example, to step into Rust std library during debugging. Works only with `MS C++ tools`. On Windows: ```json "rust-analyzer.debug.sourceFileMap": { "/rustc/4fb7144ed159f94491249e86d5bbd033b5d60550": "${env:USERPROFILE}/.rustup/toolchains/stable-x86_64-pc-windows-msvc/lib/rustlib/src/rust" } ``` On Linux: ```json "rust-analyzer.debug.sourceFileMap": { "/rustc/4fb7144ed159f94491249e86d5bbd033b5d60550": "~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust" } ``` Co-authored-by: vsrs <vit@conrlab.com>
This commit is contained in:
commit
972afffded
4 changed files with 194 additions and 13 deletions
|
@ -389,6 +389,28 @@
|
||||||
"description": "Enable Proc macro support, cargo.loadOutDirsFromCheck must be enabled.",
|
"description": "Enable Proc macro support, cargo.loadOutDirsFromCheck must be enabled.",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": false
|
"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/<id>": "${env:USERPROFILE}/.rustup/toolchains/<toolchain-id>/lib/rustlib/src/rust"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
106
editors/code/src/cargo.ts
Normal file
106
editors/code/src/cargo.ts
Normal file
|
@ -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<string, string>;
|
||||||
|
output: OutputChannel;
|
||||||
|
|
||||||
|
public constructor(cargoTomlFolder: string, output: OutputChannel, env: Record<string, string> | undefined = undefined) {
|
||||||
|
this.rootFolder = cargoTomlFolder;
|
||||||
|
this.output = output;
|
||||||
|
this.env = env;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async artifactsFromArgs(cargoArgs: string[]): Promise<CompilationArtifact[]> {
|
||||||
|
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<string> {
|
||||||
|
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<number> {
|
||||||
|
return new Promise<number>((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}.`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as lc from 'vscode-languageclient';
|
import * as lc from 'vscode-languageclient';
|
||||||
import * as ra from '../rust-analyzer-api';
|
import * as ra from '../rust-analyzer-api';
|
||||||
|
import * as os from "os";
|
||||||
|
|
||||||
import { Ctx, Cmd } from '../ctx';
|
import { Ctx, Cmd } from '../ctx';
|
||||||
|
import { Cargo } from '../cargo';
|
||||||
|
|
||||||
export function run(ctx: Ctx): Cmd {
|
export function run(ctx: Ctx): Cmd {
|
||||||
let prevRunnable: RunnableQuickPick | undefined;
|
let prevRunnable: RunnableQuickPick | undefined;
|
||||||
|
@ -62,25 +64,69 @@ export function runSingle(ctx: Ctx): Cmd {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLldbDebugConfig(config: ra.Runnable, sourceFileMap: Record<string, string>): 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<string, string>): Promise<vscode.DebugConfiguration> {
|
||||||
|
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 {
|
export function debugSingle(ctx: Ctx): Cmd {
|
||||||
return async (config: ra.Runnable) => {
|
return async (config: ra.Runnable) => {
|
||||||
const editor = ctx.activeRustEditor;
|
const editor = ctx.activeRustEditor;
|
||||||
if (!editor) return;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const debugConfig = {
|
const debugConfig = lldbId === debugEngine.id
|
||||||
type: "lldb",
|
? getLldbDebugConfig(config, ctx.config.debug.sourceFileMap)
|
||||||
request: "launch",
|
: await getCppvsDebugConfig(config, ctx.config.debug.sourceFileMap);
|
||||||
name: config.label,
|
|
||||||
cargo: {
|
|
||||||
args: config.args,
|
|
||||||
},
|
|
||||||
args: config.extraArgs,
|
|
||||||
cwd: config.cwd
|
|
||||||
};
|
|
||||||
|
|
||||||
return vscode.debug.startDebugging(undefined, debugConfig);
|
return vscode.debug.startDebugging(undefined, debugConfig);
|
||||||
};
|
};
|
||||||
|
|
|
@ -92,7 +92,6 @@ export class Config {
|
||||||
get askBeforeDownload() { return this.get<boolean>("updates.askBeforeDownload"); }
|
get askBeforeDownload() { return this.get<boolean>("updates.askBeforeDownload"); }
|
||||||
get traceExtension() { return this.get<boolean>("trace.extension"); }
|
get traceExtension() { return this.get<boolean>("trace.extension"); }
|
||||||
|
|
||||||
|
|
||||||
get inlayHints() {
|
get inlayHints() {
|
||||||
return {
|
return {
|
||||||
typeHints: this.get<boolean>("inlayHints.typeHints"),
|
typeHints: this.get<boolean>("inlayHints.typeHints"),
|
||||||
|
@ -107,4 +106,12 @@ export class Config {
|
||||||
command: this.get<string>("checkOnSave.command"),
|
command: this.get<string>("checkOnSave.command"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get debug() {
|
||||||
|
return {
|
||||||
|
engine: this.get<string>("debug.engine"),
|
||||||
|
sourceFileMap: this.get<Record<string, string>>("debug.sourceFileMap"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue