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