1use std::{path::PathBuf, sync::OnceLock};
2
3use anyhow::{Result, bail};
4use async_trait::async_trait;
5use dap::adapters::latest_github_release;
6use gpui::AsyncApp;
7use task::{DebugAdapterConfig, DebugRequestType, DebugTaskDefinition};
8
9use crate::*;
10
11#[derive(Default)]
12pub(crate) struct CodeLldbDebugAdapter {
13 last_known_version: OnceLock<String>,
14}
15
16impl CodeLldbDebugAdapter {
17 const ADAPTER_NAME: &'static str = "CodeLLDB";
18}
19
20#[async_trait(?Send)]
21impl DebugAdapter for CodeLldbDebugAdapter {
22 fn name(&self) -> DebugAdapterName {
23 DebugAdapterName(Self::ADAPTER_NAME.into())
24 }
25
26 async fn install_binary(
27 &self,
28 version: AdapterVersion,
29 delegate: &dyn DapDelegate,
30 ) -> Result<()> {
31 adapters::download_adapter_from_github(
32 self.name(),
33 version,
34 adapters::DownloadedFileType::Vsix,
35 delegate,
36 )
37 .await?;
38
39 Ok(())
40 }
41
42 async fn fetch_latest_adapter_version(
43 &self,
44 delegate: &dyn DapDelegate,
45 ) -> Result<AdapterVersion> {
46 let release =
47 latest_github_release("vadimcn/codelldb", true, false, delegate.http_client()).await?;
48
49 let arch = match std::env::consts::ARCH {
50 "aarch64" => "arm64",
51 "x86_64" => "x64",
52 _ => {
53 return Err(anyhow!(
54 "unsupported architecture {}",
55 std::env::consts::ARCH
56 ));
57 }
58 };
59 let platform = match std::env::consts::OS {
60 "macos" => "darwin",
61 "linux" => "linux",
62 "windows" => "win32",
63 _ => {
64 return Err(anyhow!(
65 "unsupported operating system {}",
66 std::env::consts::OS
67 ));
68 }
69 };
70 let asset_name = format!("codelldb-{platform}-{arch}.vsix");
71 let _ = self.last_known_version.set(release.tag_name.clone());
72 let ret = AdapterVersion {
73 tag_name: release.tag_name,
74 url: release
75 .assets
76 .iter()
77 .find(|asset| asset.name == asset_name)
78 .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?
79 .browser_download_url
80 .clone(),
81 };
82
83 Ok(ret)
84 }
85
86 async fn get_installed_binary(
87 &self,
88 _: &dyn DapDelegate,
89 _: &DebugAdapterConfig,
90 _: Option<PathBuf>,
91 _: &mut AsyncApp,
92 ) -> Result<DebugAdapterBinary> {
93 let Some(version) = self.last_known_version.get() else {
94 bail!("Could not determine latest CodeLLDB version");
95 };
96 let adapter_path = paths::debug_adapters_dir().join(&Self::ADAPTER_NAME);
97 let version_path = adapter_path.join(format!("{}_{}", Self::ADAPTER_NAME, version));
98
99 let adapter_dir = version_path.join("extension").join("adapter");
100 let command = adapter_dir.join("codelldb");
101 let command = command
102 .to_str()
103 .map(ToOwned::to_owned)
104 .ok_or_else(|| anyhow!("Adapter path is expected to be valid UTF-8"))?;
105 Ok(DebugAdapterBinary {
106 command,
107 cwd: Some(adapter_dir),
108 arguments: Some(vec![
109 "--settings".into(),
110 json!({"sourceLanguages": ["cpp", "rust"]})
111 .to_string()
112 .into(),
113 ]),
114 ..Default::default()
115 })
116 }
117
118 fn request_args(&self, config: &DebugTaskDefinition) -> Value {
119 let mut args = json!({
120 "request": match config.request {
121 DebugRequestType::Launch(_) => "launch",
122 DebugRequestType::Attach(_) => "attach",
123 },
124 });
125 let map = args.as_object_mut().unwrap();
126 // CodeLLDB uses `name` for a terminal label.
127 map.insert("name".into(), Value::String(config.label.clone()));
128 match &config.request {
129 DebugRequestType::Attach(attach) => {
130 map.insert("pid".into(), attach.process_id.into());
131 }
132 DebugRequestType::Launch(launch) => {
133 map.insert("program".into(), launch.program.clone().into());
134
135 if !launch.args.is_empty() {
136 map.insert("args".into(), launch.args.clone().into());
137 }
138
139 if let Some(stop_on_entry) = config.stop_on_entry {
140 map.insert("stopOnEntry".into(), stop_on_entry.into());
141 }
142 if let Some(cwd) = launch.cwd.as_ref() {
143 map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
144 }
145 }
146 }
147 args
148 }
149}