php.rs

  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}