javascript.rs

  1use adapters::latest_github_release;
  2use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
  3use gpui::AsyncApp;
  4use std::{collections::HashMap, path::PathBuf, sync::OnceLock};
  5use task::DebugRequest;
  6use util::ResultExt;
  7
  8use crate::*;
  9
 10#[derive(Debug, Default)]
 11pub(crate) struct JsDebugAdapter {
 12    checked: OnceLock<()>,
 13}
 14
 15impl JsDebugAdapter {
 16    const ADAPTER_NAME: &'static str = "JavaScript";
 17    const ADAPTER_NPM_NAME: &'static str = "vscode-js-debug";
 18    const ADAPTER_PATH: &'static str = "js-debug/src/dapDebugServer.js";
 19
 20    fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments {
 21        let mut args = json!({
 22            "type": "pwa-node",
 23            "request": match config.request {
 24                DebugRequest::Launch(_) => "launch",
 25                DebugRequest::Attach(_) => "attach",
 26            },
 27        });
 28        let map = args.as_object_mut().unwrap();
 29        match &config.request {
 30            DebugRequest::Attach(attach) => {
 31                map.insert("processId".into(), attach.process_id.into());
 32            }
 33            DebugRequest::Launch(launch) => {
 34                map.insert("program".into(), launch.program.clone().into());
 35
 36                if !launch.args.is_empty() {
 37                    map.insert("args".into(), launch.args.clone().into());
 38                }
 39
 40                if let Some(stop_on_entry) = config.stop_on_entry {
 41                    map.insert("stopOnEntry".into(), stop_on_entry.into());
 42                }
 43                if let Some(cwd) = launch.cwd.as_ref() {
 44                    map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
 45                }
 46            }
 47        }
 48        StartDebuggingRequestArguments {
 49            configuration: args,
 50            request: config.request.to_dap(),
 51        }
 52    }
 53
 54    async fn fetch_latest_adapter_version(
 55        &self,
 56        delegate: &dyn DapDelegate,
 57    ) -> Result<AdapterVersion> {
 58        let release = latest_github_release(
 59            &format!("{}/{}", "microsoft", Self::ADAPTER_NPM_NAME),
 60            true,
 61            false,
 62            delegate.http_client(),
 63        )
 64        .await?;
 65
 66        let asset_name = format!("js-debug-dap-{}.tar.gz", release.tag_name);
 67
 68        Ok(AdapterVersion {
 69            tag_name: release.tag_name,
 70            url: release
 71                .assets
 72                .iter()
 73                .find(|asset| asset.name == asset_name)
 74                .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?
 75                .browser_download_url
 76                .clone(),
 77        })
 78    }
 79
 80    async fn get_installed_binary(
 81        &self,
 82        delegate: &dyn DapDelegate,
 83        config: &DebugTaskDefinition,
 84        user_installed_path: Option<PathBuf>,
 85        _: &mut AsyncApp,
 86    ) -> Result<DebugAdapterBinary> {
 87        let adapter_path = if let Some(user_installed_path) = user_installed_path {
 88            user_installed_path
 89        } else {
 90            let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref());
 91
 92            let file_name_prefix = format!("{}_", self.name());
 93
 94            util::fs::find_file_name_in_dir(adapter_path.as_path(), |file_name| {
 95                file_name.starts_with(&file_name_prefix)
 96            })
 97            .await
 98            .ok_or_else(|| anyhow!("Couldn't find JavaScript dap directory"))?
 99        };
100
101        let tcp_connection = config.tcp_connection.clone().unwrap_or_default();
102        let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
103
104        Ok(DebugAdapterBinary {
105            command: delegate
106                .node_runtime()
107                .binary_path()
108                .await?
109                .to_string_lossy()
110                .into_owned(),
111            arguments: vec![
112                adapter_path
113                    .join(Self::ADAPTER_PATH)
114                    .to_string_lossy()
115                    .to_string(),
116                port.to_string(),
117                host.to_string(),
118            ],
119            cwd: None,
120            envs: HashMap::default(),
121            connection: Some(adapters::TcpArguments {
122                host,
123                port,
124                timeout,
125            }),
126            request_args: self.request_args(config),
127        })
128    }
129}
130
131#[async_trait(?Send)]
132impl DebugAdapter for JsDebugAdapter {
133    fn name(&self) -> DebugAdapterName {
134        DebugAdapterName(Self::ADAPTER_NAME.into())
135    }
136
137    async fn get_binary(
138        &self,
139        delegate: &dyn DapDelegate,
140        config: &DebugTaskDefinition,
141        user_installed_path: Option<PathBuf>,
142        cx: &mut AsyncApp,
143    ) -> Result<DebugAdapterBinary> {
144        if self.checked.set(()).is_ok() {
145            delegate.output_to_console(format!("Checking latest version of {}...", self.name()));
146            if let Some(version) = self.fetch_latest_adapter_version(delegate).await.log_err() {
147                adapters::download_adapter_from_github(
148                    self.name(),
149                    version,
150                    adapters::DownloadedFileType::GzipTar,
151                    delegate,
152                )
153                .await?;
154            }
155        }
156
157        self.get_installed_binary(delegate, &config, user_installed_path, cx)
158            .await
159    }
160}