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