
Custom arguments replace any arguments that we normally pass to the DAP. For interpreted languages, they are passed to the interpreter after the DAP path or module. They can be combined with a custom binary, or you can omit `dap.binary` and just customize the arguments to the DAPs we download. This doesn't take care of updating the extension API to support custom arguments. Release Notes: - debugger: Implemented support for passing custom arguments to a debug adapter binary using the `dap.args` setting. - debugger: Fixed not being able to use the `dap` setting in `.zed/settings.json`.
200 lines
8.2 KiB
Rust
200 lines
8.2 KiB
Rust
use std::{collections::HashMap, ffi::OsStr};
|
|
|
|
use anyhow::{Context as _, Result, bail};
|
|
use async_trait::async_trait;
|
|
use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
|
|
use gpui::AsyncApp;
|
|
use task::{DebugScenario, ZedDebugConfig};
|
|
|
|
use crate::*;
|
|
|
|
#[derive(Default)]
|
|
pub(crate) struct GdbDebugAdapter;
|
|
|
|
impl GdbDebugAdapter {
|
|
const ADAPTER_NAME: &'static str = "GDB";
|
|
}
|
|
|
|
#[async_trait(?Send)]
|
|
impl DebugAdapter for GdbDebugAdapter {
|
|
fn name(&self) -> DebugAdapterName {
|
|
DebugAdapterName(Self::ADAPTER_NAME.into())
|
|
}
|
|
|
|
async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
|
|
let mut obj = serde_json::Map::default();
|
|
|
|
match &zed_scenario.request {
|
|
dap::DebugRequest::Attach(attach) => {
|
|
obj.insert("request".into(), "attach".into());
|
|
obj.insert("pid".into(), attach.process_id.into());
|
|
}
|
|
|
|
dap::DebugRequest::Launch(launch) => {
|
|
obj.insert("request".into(), "launch".into());
|
|
obj.insert("program".into(), launch.program.clone().into());
|
|
|
|
if !launch.args.is_empty() {
|
|
obj.insert("args".into(), launch.args.clone().into());
|
|
}
|
|
|
|
if !launch.env.is_empty() {
|
|
obj.insert("env".into(), launch.env_json());
|
|
}
|
|
|
|
if let Some(stop_on_entry) = zed_scenario.stop_on_entry {
|
|
obj.insert(
|
|
"stopAtBeginningOfMainSubprogram".into(),
|
|
stop_on_entry.into(),
|
|
);
|
|
}
|
|
if let Some(cwd) = launch.cwd.as_ref() {
|
|
obj.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(DebugScenario {
|
|
adapter: zed_scenario.adapter,
|
|
label: zed_scenario.label,
|
|
build: None,
|
|
config: serde_json::Value::Object(obj),
|
|
tcp_connection: None,
|
|
})
|
|
}
|
|
|
|
fn dap_schema(&self) -> serde_json::Value {
|
|
json!({
|
|
"oneOf": [
|
|
{
|
|
"allOf": [
|
|
{
|
|
"type": "object",
|
|
"required": ["request"],
|
|
"properties": {
|
|
"request": {
|
|
"type": "string",
|
|
"enum": ["launch"],
|
|
"description": "Request to launch a new process"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"program": {
|
|
"type": "string",
|
|
"description": "The program to debug. This corresponds to the GDB 'file' command."
|
|
},
|
|
"args": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
},
|
|
"description": "Command line arguments passed to the program. These strings are provided as command-line arguments to the inferior.",
|
|
"default": []
|
|
},
|
|
"cwd": {
|
|
"type": "string",
|
|
"description": "Working directory for the debugged program. GDB will change its working directory to this directory."
|
|
},
|
|
"env": {
|
|
"type": "object",
|
|
"description": "Environment variables for the debugged program. Each key is the name of an environment variable; each value is the value of that variable."
|
|
},
|
|
"stopAtBeginningOfMainSubprogram": {
|
|
"type": "boolean",
|
|
"description": "When true, GDB will set a temporary breakpoint at the program's main procedure, like the 'start' command.",
|
|
"default": false
|
|
},
|
|
"stopOnEntry": {
|
|
"type": "boolean",
|
|
"description": "When true, GDB will set a temporary breakpoint at the program's first instruction, like the 'starti' command.",
|
|
"default": false
|
|
}
|
|
},
|
|
"required": ["program"]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"allOf": [
|
|
{
|
|
"type": "object",
|
|
"required": ["request"],
|
|
"properties": {
|
|
"request": {
|
|
"type": "string",
|
|
"enum": ["attach"],
|
|
"description": "Request to attach to an existing process"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"pid": {
|
|
"type": "number",
|
|
"description": "The process ID to which GDB should attach."
|
|
},
|
|
"program": {
|
|
"type": "string",
|
|
"description": "The program to debug (optional). This corresponds to the GDB 'file' command. In many cases, GDB can determine which program is running automatically."
|
|
},
|
|
"target": {
|
|
"type": "string",
|
|
"description": "The target to which GDB should connect. This is passed to the 'target remote' command."
|
|
}
|
|
},
|
|
"required": ["pid"]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
})
|
|
}
|
|
|
|
async fn get_binary(
|
|
&self,
|
|
delegate: &Arc<dyn DapDelegate>,
|
|
config: &DebugTaskDefinition,
|
|
user_installed_path: Option<std::path::PathBuf>,
|
|
user_args: Option<Vec<String>>,
|
|
_: &mut AsyncApp,
|
|
) -> Result<DebugAdapterBinary> {
|
|
let user_setting_path = user_installed_path
|
|
.filter(|p| p.exists())
|
|
.and_then(|p| p.to_str().map(|s| s.to_string()));
|
|
|
|
let gdb_path = delegate
|
|
.which(OsStr::new("gdb"))
|
|
.await
|
|
.and_then(|p| p.to_str().map(|s| s.to_string()))
|
|
.context("Could not find gdb in path");
|
|
|
|
if gdb_path.is_err() && user_setting_path.is_none() {
|
|
bail!("Could not find gdb path or it's not installed");
|
|
}
|
|
|
|
let gdb_path = user_setting_path.unwrap_or(gdb_path?);
|
|
|
|
let mut configuration = config.config.clone();
|
|
if let Some(configuration) = configuration.as_object_mut() {
|
|
configuration
|
|
.entry("cwd")
|
|
.or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into());
|
|
}
|
|
|
|
Ok(DebugAdapterBinary {
|
|
command: Some(gdb_path),
|
|
arguments: user_args.unwrap_or_else(|| vec!["-i=dap".into()]),
|
|
envs: HashMap::default(),
|
|
cwd: Some(delegate.worktree_root_path().to_path_buf()),
|
|
connection: None,
|
|
request_args: StartDebuggingRequestArguments {
|
|
request: self.request_kind(&config.config).await?,
|
|
configuration,
|
|
},
|
|
})
|
|
}
|
|
}
|