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 tcp_connection = config.tcp_connection.clone().unwrap_or_default();
82 let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
83
84 Ok(DebugAdapterBinary {
85 command: delegate
86 .node_runtime()
87 .binary_path()
88 .await?
89 .to_string_lossy()
90 .into_owned(),
91 arguments: Some(vec![
92 adapter_path.join(Self::ADAPTER_PATH).into(),
93 port.to_string().into(),
94 host.to_string().into(),
95 ]),
96 cwd: None,
97 envs: None,
98 connection: Some(adapters::TcpArguments {
99 host,
100 port,
101 timeout,
102 }),
103 })
104 }
105
106 async fn install_binary(
107 &self,
108 version: AdapterVersion,
109 delegate: &dyn DapDelegate,
110 ) -> Result<()> {
111 adapters::download_adapter_from_github(
112 self.name(),
113 version,
114 adapters::DownloadedFileType::GzipTar,
115 delegate,
116 )
117 .await?;
118
119 return Ok(());
120 }
121
122 fn request_args(&self, config: &DebugTaskDefinition) -> Value {
123 let mut args = json!({
124 "type": "pwa-node",
125 "request": match config.request {
126 DebugRequestType::Launch(_) => "launch",
127 DebugRequestType::Attach(_) => "attach",
128 },
129 });
130 let map = args.as_object_mut().unwrap();
131 match &config.request {
132 DebugRequestType::Attach(attach) => {
133 map.insert("processId".into(), attach.process_id.into());
134 }
135 DebugRequestType::Launch(launch) => {
136 map.insert("program".into(), launch.program.clone().into());
137
138 if !launch.args.is_empty() {
139 map.insert("args".into(), launch.args.clone().into());
140 }
141
142 if let Some(stop_on_entry) = config.stop_on_entry {
143 map.insert("stopOnEntry".into(), stop_on_entry.into());
144 }
145 if let Some(cwd) = launch.cwd.as_ref() {
146 map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
147 }
148 }
149 }
150 args
151 }
152
153 fn attach_processes_filter(&self) -> Regex {
154 self.attach_processes.clone()
155 }
156}