codelldb.rs

  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                if !launch.env.is_empty() {
 46                    map.insert("env".into(), launch.env_json());
 47                }
 48                if let Some(stop_on_entry) = config.stop_on_entry {
 49                    map.insert("stopOnEntry".into(), stop_on_entry.into());
 50                }
 51                if let Some(cwd) = launch.cwd.as_ref() {
 52                    map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
 53                }
 54            }
 55        }
 56        dap::StartDebuggingRequestArguments {
 57            request,
 58            configuration,
 59        }
 60    }
 61
 62    async fn fetch_latest_adapter_version(
 63        &self,
 64        delegate: &dyn DapDelegate,
 65    ) -> Result<AdapterVersion> {
 66        let release =
 67            latest_github_release("vadimcn/codelldb", true, false, delegate.http_client()).await?;
 68
 69        let arch = match std::env::consts::ARCH {
 70            "aarch64" => "arm64",
 71            "x86_64" => "x64",
 72            _ => {
 73                return Err(anyhow!(
 74                    "unsupported architecture {}",
 75                    std::env::consts::ARCH
 76                ));
 77            }
 78        };
 79        let platform = match std::env::consts::OS {
 80            "macos" => "darwin",
 81            "linux" => "linux",
 82            "windows" => "win32",
 83            _ => {
 84                return Err(anyhow!(
 85                    "unsupported operating system {}",
 86                    std::env::consts::OS
 87                ));
 88            }
 89        };
 90        let asset_name = format!("codelldb-{platform}-{arch}.vsix");
 91        let ret = AdapterVersion {
 92            tag_name: release.tag_name,
 93            url: release
 94                .assets
 95                .iter()
 96                .find(|asset| asset.name == asset_name)
 97                .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?
 98                .browser_download_url
 99                .clone(),
100        };
101
102        Ok(ret)
103    }
104}
105
106#[async_trait(?Send)]
107impl DebugAdapter for CodeLldbDebugAdapter {
108    fn name(&self) -> DebugAdapterName {
109        DebugAdapterName(Self::ADAPTER_NAME.into())
110    }
111
112    async fn get_binary(
113        &self,
114        delegate: &dyn DapDelegate,
115        config: &DebugTaskDefinition,
116        user_installed_path: Option<PathBuf>,
117        _: &mut AsyncApp,
118    ) -> Result<DebugAdapterBinary> {
119        let mut command = user_installed_path
120            .map(|p| p.to_string_lossy().to_string())
121            .or(self.path_to_codelldb.get().cloned());
122
123        if command.is_none() {
124            delegate.output_to_console(format!("Checking latest version of {}...", self.name()));
125            let adapter_path = paths::debug_adapters_dir().join(&Self::ADAPTER_NAME);
126            let version_path =
127                if let Ok(version) = self.fetch_latest_adapter_version(delegate).await {
128                    adapters::download_adapter_from_github(
129                        self.name(),
130                        version.clone(),
131                        adapters::DownloadedFileType::Vsix,
132                        delegate,
133                    )
134                    .await?;
135                    let version_path =
136                        adapter_path.join(format!("{}_{}", Self::ADAPTER_NAME, version.tag_name));
137                    remove_matching(&adapter_path, |entry| entry != version_path).await;
138                    version_path
139                } else {
140                    let mut paths = delegate.fs().read_dir(&adapter_path).await?;
141                    paths
142                        .next()
143                        .await
144                        .ok_or_else(|| anyhow!("No adapter found"))??
145                };
146            let adapter_dir = version_path.join("extension").join("adapter");
147            let path = adapter_dir.join("codelldb").to_string_lossy().to_string();
148            self.path_to_codelldb.set(path.clone()).ok();
149            command = Some(path);
150        };
151
152        Ok(DebugAdapterBinary {
153            command: command.unwrap(),
154            cwd: None,
155            arguments: vec![
156                "--settings".into(),
157                json!({"sourceLanguages": ["cpp", "rust"]}).to_string(),
158            ],
159            request_args: self.request_args(config),
160            envs: HashMap::default(),
161            connection: None,
162        })
163    }
164}