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 ..Default::default()
109 })
110 }
111
112 fn request_args(&self, config: &DebugTaskDefinition) -> Value {
113 let mut args = json!({
114 "request": match config.request {
115 DebugRequestType::Launch(_) => "launch",
116 DebugRequestType::Attach(_) => "attach",
117 },
118 });
119 let map = args.as_object_mut().unwrap();
120 match &config.request {
121 DebugRequestType::Attach(attach) => {
122 map.insert("pid".into(), attach.process_id.into());
123 }
124 DebugRequestType::Launch(launch) => {
125 map.insert("program".into(), launch.program.clone().into());
126
127 if !launch.args.is_empty() {
128 map.insert("args".into(), launch.args.clone().into());
129 }
130
131 if let Some(stop_on_entry) = config.stop_on_entry {
132 map.insert("stopOnEntry".into(), stop_on_entry.into());
133 }
134 if let Some(cwd) = launch.cwd.as_ref() {
135 map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
136 }
137 }
138 }
139 args
140 }
141}