1use std::{borrow::Cow, collections::HashMap, ffi::OsStr, sync::LazyLock};
2
3use anyhow::{Context as _, Result, bail};
4use async_trait::async_trait;
5use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
6use gpui::AsyncApp;
7use task::{DebugScenario, ZedDebugConfig};
8
9use crate::*;
10
11#[derive(Default)]
12pub(crate) struct GdbDebugAdapter;
13
14impl GdbDebugAdapter {
15 const ADAPTER_NAME: &'static str = "GDB";
16}
17
18#[async_trait(?Send)]
19impl DebugAdapter for GdbDebugAdapter {
20 fn name(&self) -> DebugAdapterName {
21 DebugAdapterName(Self::ADAPTER_NAME.into())
22 }
23
24 async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
25 let mut obj = serde_json::Map::default();
26
27 match &zed_scenario.request {
28 dap::DebugRequest::Attach(attach) => {
29 obj.insert("request".into(), "attach".into());
30 obj.insert("pid".into(), attach.process_id.into());
31 }
32
33 dap::DebugRequest::Launch(launch) => {
34 obj.insert("request".into(), "launch".into());
35 obj.insert("program".into(), launch.program.clone().into());
36
37 if !launch.args.is_empty() {
38 obj.insert("args".into(), launch.args.clone().into());
39 }
40
41 if !launch.env.is_empty() {
42 obj.insert("env".into(), launch.env_json());
43 }
44
45 if let Some(stop_on_entry) = zed_scenario.stop_on_entry {
46 obj.insert(
47 "stopAtBeginningOfMainSubprogram".into(),
48 stop_on_entry.into(),
49 );
50 }
51 if let Some(cwd) = launch.cwd.as_ref() {
52 obj.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
53 }
54 }
55 }
56
57 Ok(DebugScenario {
58 adapter: zed_scenario.adapter,
59 label: zed_scenario.label,
60 build: None,
61 config: serde_json::Value::Object(obj),
62 tcp_connection: None,
63 })
64 }
65
66 fn dap_schema(&self) -> Cow<'static, serde_json::Value> {
67 static SCHEMA: LazyLock<serde_json::Value> = LazyLock::new(|| {
68 const RAW_SCHEMA: &str = include_str!("../schemas/GDB.json");
69 serde_json::from_str(RAW_SCHEMA).unwrap()
70 });
71 Cow::Borrowed(&*SCHEMA)
72 }
73
74 async fn get_binary(
75 &self,
76 delegate: &Arc<dyn DapDelegate>,
77 config: &DebugTaskDefinition,
78 user_installed_path: Option<std::path::PathBuf>,
79 user_args: Option<Vec<String>>,
80 _: &mut AsyncApp,
81 ) -> Result<DebugAdapterBinary> {
82 let user_setting_path = user_installed_path
83 .filter(|p| p.exists())
84 .and_then(|p| p.to_str().map(|s| s.to_string()));
85
86 let gdb_path = delegate
87 .which(OsStr::new("gdb"))
88 .await
89 .and_then(|p| p.to_str().map(|s| s.to_string()))
90 .context("Could not find gdb in path");
91
92 if gdb_path.is_err() && user_setting_path.is_none() {
93 bail!("Could not find gdb path or it's not installed");
94 }
95
96 let gdb_path = user_setting_path.unwrap_or(gdb_path?);
97
98 let mut configuration = config.config.clone();
99 if let Some(configuration) = configuration.as_object_mut() {
100 configuration
101 .entry("cwd")
102 .or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into());
103 }
104
105 Ok(DebugAdapterBinary {
106 command: Some(gdb_path),
107 arguments: user_args.unwrap_or_else(|| vec!["-i=dap".into()]),
108 envs: HashMap::default(),
109 cwd: Some(delegate.worktree_root_path().to_path_buf()),
110 connection: None,
111 request_args: StartDebuggingRequestArguments {
112 request: self.request_kind(&config.config).await?,
113 configuration,
114 },
115 })
116 }
117}