codelldb.rs

  1use std::{collections::HashMap, path::PathBuf, sync::OnceLock};
  2
  3use anyhow::{Result, bail};
  4use async_trait::async_trait;
  5use dap::adapters::{DebugTaskDefinition, InlineValueProvider, latest_github_release};
  6use gpui::AsyncApp;
  7use task::DebugRequest;
  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    fn request_args(&self, config: &DebugTaskDefinition) -> dap::StartDebuggingRequestArguments {
 20        let mut configuration = json!({
 21            "request": match config.request {
 22                DebugRequest::Launch(_) => "launch",
 23                DebugRequest::Attach(_) => "attach",
 24            },
 25        });
 26        let map = configuration.as_object_mut().unwrap();
 27        // CodeLLDB uses `name` for a terminal label.
 28        map.insert(
 29            "name".into(),
 30            Value::String(String::from(config.label.as_ref())),
 31        );
 32        let request = config.request.to_dap();
 33        match &config.request {
 34            DebugRequest::Attach(attach) => {
 35                map.insert("pid".into(), attach.process_id.into());
 36            }
 37            DebugRequest::Launch(launch) => {
 38                map.insert("program".into(), launch.program.clone().into());
 39
 40                if !launch.args.is_empty() {
 41                    map.insert("args".into(), launch.args.clone().into());
 42                }
 43
 44                if let Some(stop_on_entry) = config.stop_on_entry {
 45                    map.insert("stopOnEntry".into(), stop_on_entry.into());
 46                }
 47                if let Some(cwd) = launch.cwd.as_ref() {
 48                    map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
 49                }
 50            }
 51        }
 52        dap::StartDebuggingRequestArguments {
 53            request,
 54            configuration,
 55        }
 56    }
 57}
 58
 59#[async_trait(?Send)]
 60impl DebugAdapter for CodeLldbDebugAdapter {
 61    fn name(&self) -> DebugAdapterName {
 62        DebugAdapterName(Self::ADAPTER_NAME.into())
 63    }
 64
 65    async fn install_binary(
 66        &self,
 67        version: AdapterVersion,
 68        delegate: &dyn DapDelegate,
 69    ) -> Result<()> {
 70        adapters::download_adapter_from_github(
 71            self.name(),
 72            version,
 73            adapters::DownloadedFileType::Vsix,
 74            delegate,
 75        )
 76        .await?;
 77
 78        Ok(())
 79    }
 80
 81    async fn fetch_latest_adapter_version(
 82        &self,
 83        delegate: &dyn DapDelegate,
 84    ) -> Result<AdapterVersion> {
 85        let release =
 86            latest_github_release("vadimcn/codelldb", true, false, delegate.http_client()).await?;
 87
 88        let arch = match std::env::consts::ARCH {
 89            "aarch64" => "arm64",
 90            "x86_64" => "x64",
 91            _ => {
 92                return Err(anyhow!(
 93                    "unsupported architecture {}",
 94                    std::env::consts::ARCH
 95                ));
 96            }
 97        };
 98        let platform = match std::env::consts::OS {
 99            "macos" => "darwin",
100            "linux" => "linux",
101            "windows" => "win32",
102            _ => {
103                return Err(anyhow!(
104                    "unsupported operating system {}",
105                    std::env::consts::OS
106                ));
107            }
108        };
109        let asset_name = format!("codelldb-{platform}-{arch}.vsix");
110        let _ = self.last_known_version.set(release.tag_name.clone());
111        let ret = AdapterVersion {
112            tag_name: release.tag_name,
113            url: release
114                .assets
115                .iter()
116                .find(|asset| asset.name == asset_name)
117                .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?
118                .browser_download_url
119                .clone(),
120        };
121
122        Ok(ret)
123    }
124
125    async fn get_installed_binary(
126        &self,
127        _: &dyn DapDelegate,
128        config: &DebugTaskDefinition,
129        _: Option<PathBuf>,
130        _: &mut AsyncApp,
131    ) -> Result<DebugAdapterBinary> {
132        let Some(version) = self.last_known_version.get() else {
133            bail!("Could not determine latest CodeLLDB version");
134        };
135        let adapter_path = paths::debug_adapters_dir().join(&Self::ADAPTER_NAME);
136        let version_path = adapter_path.join(format!("{}_{}", Self::ADAPTER_NAME, version));
137
138        let adapter_dir = version_path.join("extension").join("adapter");
139        let command = adapter_dir.join("codelldb");
140        let command = command
141            .to_str()
142            .map(ToOwned::to_owned)
143            .ok_or_else(|| anyhow!("Adapter path is expected to be valid UTF-8"))?;
144        Ok(DebugAdapterBinary {
145            command,
146            cwd: None,
147            arguments: vec![
148                "--settings".into(),
149                json!({"sourceLanguages": ["cpp", "rust"]}).to_string(),
150            ],
151            request_args: self.request_args(config),
152            envs: HashMap::default(),
153            connection: None,
154        })
155    }
156
157    fn inline_value_provider(&self) -> Option<Box<dyn InlineValueProvider>> {
158        Some(Box::new(CodeLldbInlineValueProvider))
159    }
160}
161
162struct CodeLldbInlineValueProvider;
163
164impl InlineValueProvider for CodeLldbInlineValueProvider {
165    fn provide(&self, variables: Vec<(String, lsp_types::Range)>) -> Vec<lsp_types::InlineValue> {
166        variables
167            .into_iter()
168            .map(|(variable, range)| {
169                lsp_types::InlineValue::VariableLookup(lsp_types::InlineValueVariableLookup {
170                    range,
171                    variable_name: Some(variable),
172                    case_sensitive_lookup: true,
173                })
174            })
175            .collect()
176    }
177}