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