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                if !launch.env.is_empty() {
 40                    map.insert("env".into(), launch.env_json());
 41                }
 42
 43                if let Some(stop_on_entry) = config.stop_on_entry {
 44                    map.insert("stopOnEntry".into(), stop_on_entry.into());
 45                }
 46                if let Some(cwd) = launch.cwd.as_ref() {
 47                    map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
 48                }
 49            }
 50        }
 51        StartDebuggingRequestArguments {
 52            configuration: args,
 53            request: config.request.to_dap(),
 54        }
 55    }
 56
 57    async fn fetch_latest_adapter_version(
 58        &self,
 59        delegate: &dyn DapDelegate,
 60    ) -> Result<AdapterVersion> {
 61        let release = latest_github_release(
 62            &format!("{}/{}", "microsoft", Self::ADAPTER_NPM_NAME),
 63            true,
 64            false,
 65            delegate.http_client(),
 66        )
 67        .await?;
 68
 69        let asset_name = format!("js-debug-dap-{}.tar.gz", release.tag_name);
 70
 71        Ok(AdapterVersion {
 72            tag_name: release.tag_name,
 73            url: release
 74                .assets
 75                .iter()
 76                .find(|asset| asset.name == asset_name)
 77                .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?
 78                .browser_download_url
 79                .clone(),
 80        })
 81    }
 82
 83    async fn get_installed_binary(
 84        &self,
 85        delegate: &dyn DapDelegate,
 86        config: &DebugTaskDefinition,
 87        user_installed_path: Option<PathBuf>,
 88        _: &mut AsyncApp,
 89    ) -> Result<DebugAdapterBinary> {
 90        let adapter_path = if let Some(user_installed_path) = user_installed_path {
 91            user_installed_path
 92        } else {
 93            let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref());
 94
 95            let file_name_prefix = format!("{}_", self.name());
 96
 97            util::fs::find_file_name_in_dir(adapter_path.as_path(), |file_name| {
 98                file_name.starts_with(&file_name_prefix)
 99            })
100            .await
101            .ok_or_else(|| anyhow!("Couldn't find JavaScript dap directory"))?
102        };
103
104        let tcp_connection = config.tcp_connection.clone().unwrap_or_default();
105        let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
106
107        Ok(DebugAdapterBinary {
108            command: delegate
109                .node_runtime()
110                .binary_path()
111                .await?
112                .to_string_lossy()
113                .into_owned(),
114            arguments: vec![
115                adapter_path
116                    .join(Self::ADAPTER_PATH)
117                    .to_string_lossy()
118                    .to_string(),
119                port.to_string(),
120                host.to_string(),
121            ],
122            cwd: None,
123            envs: HashMap::default(),
124            connection: Some(adapters::TcpArguments {
125                host,
126                port,
127                timeout,
128            }),
129            request_args: self.request_args(config),
130        })
131    }
132}
133
134#[async_trait(?Send)]
135impl DebugAdapter for JsDebugAdapter {
136    fn name(&self) -> DebugAdapterName {
137        DebugAdapterName(Self::ADAPTER_NAME.into())
138    }
139
140    async fn get_binary(
141        &self,
142        delegate: &dyn DapDelegate,
143        config: &DebugTaskDefinition,
144        user_installed_path: Option<PathBuf>,
145        cx: &mut AsyncApp,
146    ) -> Result<DebugAdapterBinary> {
147        if self.checked.set(()).is_ok() {
148            delegate.output_to_console(format!("Checking latest version of {}...", self.name()));
149            if let Some(version) = self.fetch_latest_adapter_version(delegate).await.log_err() {
150                adapters::download_adapter_from_github(
151                    self.name(),
152                    version,
153                    adapters::DownloadedFileType::GzipTar,
154                    delegate,
155                )
156                .await?;
157            }
158        }
159
160        self.get_installed_binary(delegate, &config, user_installed_path, cx)
161            .await
162    }
163}