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