use anyhow::{Context as _, Result, bail};use async_trait::async_trait;use collections::HashMap;use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};use gpui::AsyncApp;use std::ffi::OsStr;use task::{DebugScenario, ZedDebugConfig}; use crate::*; #[derive(Default)]pub(crate) struct GdbDebugAdapter; impl GdbDebugAdapter { const ADAPTER_NAME: &'static str = "GDB";} /// Ensures that "-i=dap" is present in the GDB argument list.fn ensure_dap_interface(mut gdb_args: Vec<String>) -> Vec<String> { if !gdb_args.iter().any(|arg| arg.trim() == "-i=dap") { gdb_args.insert(0, "-i=dap".to_string()); } gdb_args} #[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." }, "gdb_path": { "type": "string", "description": "Alternative path to the GDB executable, if the one in standard path is not desirable" }, "gdb_args": { "type": "array", "items": { "type":"string" }, "description": "additional arguments given to GDB at startup, not the program debugged", "default": [] }, "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>>, user_env: Option<HashMap<String, String>>, _: &mut AsyncApp, ) -> Result<DebugAdapterBinary> { // Try to get gdb_path from config let gdb_path_from_config = config .config .get("gdb_path") .and_then(|v| v.as_str()) .map(|s| s.to_string()); let gdb_path = if let Some(path) = gdb_path_from_config { path } else { // Original logic: use user_installed_path or search in system path 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_result = 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_result.is_err() && user_setting_path.is_none() { bail!("Could not find gdb path or it's not installed"); } user_setting_path.unwrap_or_else(|| gdb_path_result.unwrap()) }; // Arguments: use gdb_args from config if present, else user_args, else default let gdb_args = { let args = config .config .get("gdb_args") .and_then(|v| v.as_array()) .map(|arr| { arr.iter() .filter_map(|v| v.as_str().map(|s| s.to_string())) .collect::<Vec<_>>() }) .or(user_args.clone()) .unwrap_or_else(|| vec!["-i=dap".into()]); ensure_dap_interface(args) }; 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()); } let mut base_env = delegate.shell_env().await; base_env.extend(user_env.unwrap_or_default()); let config_env: HashMap<String, String> = config .config .get("env") .and_then(|v| v.as_object()) .map(|obj| { obj.iter() .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string()))) .collect::<HashMap<String, String>>() }) .unwrap_or_else(HashMap::default); base_env.extend(config_env); Ok(DebugAdapterBinary { command: Some(gdb_path), arguments: gdb_args, envs: base_env, cwd: Some(delegate.worktree_root_path().to_path_buf()), connection: None, request_args: StartDebuggingRequestArguments { request: self.request_kind(&config.config).await?, configuration, }, }) }}
use anyhow::{Context as _, Result, bail};
use async_trait::async_trait;
use collections::HashMap;
use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
use gpui::AsyncApp;
use std::ffi::OsStr;
use task::{DebugScenario, ZedDebugConfig};
use crate::*;
#[derive(Default)]
pub(crate) struct GdbDebugAdapter;
impl GdbDebugAdapter {
const ADAPTER_NAME: &'static str = "GDB";
}
/// Ensures that "-i=dap" is present in the GDB argument list.
fn ensure_dap_interface(mut gdb_args: Vec<String>) -> Vec<String> {
if !gdb_args.iter().any(|arg| arg.trim() == "-i=dap") {
gdb_args.insert(0, "-i=dap".to_string());
}
gdb_args
}
#[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."
},
"gdb_path": {
"type": "string",
"description": "Alternative path to the GDB executable, if the one in standard path is not desirable"
},
"gdb_args": {
"type": "array",
"items": {
"type":"string"
},
"description": "additional arguments given to GDB at startup, not the program debugged",
"default": []
},
"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>>,
user_env: Option<HashMap<String, String>>,
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
// Try to get gdb_path from config
let gdb_path_from_config = config
.config
.get("gdb_path")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let gdb_path = if let Some(path) = gdb_path_from_config {
path
} else {
// Original logic: use user_installed_path or search in system path
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_result = 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_result.is_err() && user_setting_path.is_none() {
bail!("Could not find gdb path or it's not installed");
}
user_setting_path.unwrap_or_else(|| gdb_path_result.unwrap())
};
// Arguments: use gdb_args from config if present, else user_args, else default
let gdb_args = {
let args = config
.config
.get("gdb_args")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect::<Vec<_>>()
})
.or(user_args.clone())
.unwrap_or_else(|| vec!["-i=dap".into()]);
ensure_dap_interface(args)
};
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());
}
let mut base_env = delegate.shell_env().await;
base_env.extend(user_env.unwrap_or_default());
let config_env: HashMap<String, String> = config
.config
.get("env")
.and_then(|v| v.as_object())
.map(|obj| {
obj.iter()
.filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
.collect::<HashMap<String, String>>()
})
.unwrap_or_else(HashMap::default);
base_env.extend(config_env);
Ok(DebugAdapterBinary {
command: Some(gdb_path),
arguments: gdb_args,
envs: base_env,
cwd: Some(delegate.worktree_root_path().to_path_buf()),
connection: None,
request_args: StartDebuggingRequestArguments {
request: self.request_kind(&config.config).await?,
configuration,
},
})
}
}