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