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