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