codelldb.rs

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