php.rs

  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                    "env": launch_config.env_json(),
 33                    "stopOnEntry": config.stop_on_entry.unwrap_or_default(),
 34                }),
 35                request: config.request.to_dap(),
 36            }),
 37        }
 38    }
 39
 40    async fn fetch_latest_adapter_version(
 41        &self,
 42        delegate: &dyn DapDelegate,
 43    ) -> Result<AdapterVersion> {
 44        let release = latest_github_release(
 45            &format!("{}/{}", "xdebug", Self::ADAPTER_PACKAGE_NAME),
 46            true,
 47            false,
 48            delegate.http_client(),
 49        )
 50        .await?;
 51
 52        let asset_name = format!("php-debug-{}.vsix", release.tag_name.replace("v", ""));
 53
 54        Ok(AdapterVersion {
 55            tag_name: release.tag_name,
 56            url: release
 57                .assets
 58                .iter()
 59                .find(|asset| asset.name == asset_name)
 60                .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?
 61                .browser_download_url
 62                .clone(),
 63        })
 64    }
 65
 66    async fn get_installed_binary(
 67        &self,
 68        delegate: &dyn DapDelegate,
 69        config: &DebugTaskDefinition,
 70        user_installed_path: Option<PathBuf>,
 71        _: &mut AsyncApp,
 72    ) -> Result<DebugAdapterBinary> {
 73        let adapter_path = if let Some(user_installed_path) = user_installed_path {
 74            user_installed_path
 75        } else {
 76            let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref());
 77
 78            let file_name_prefix = format!("{}_", self.name());
 79
 80            util::fs::find_file_name_in_dir(adapter_path.as_path(), |file_name| {
 81                file_name.starts_with(&file_name_prefix)
 82            })
 83            .await
 84            .ok_or_else(|| anyhow!("Couldn't find PHP dap directory"))?
 85        };
 86
 87        let tcp_connection = config.tcp_connection.clone().unwrap_or_default();
 88        let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
 89
 90        Ok(DebugAdapterBinary {
 91            command: delegate
 92                .node_runtime()
 93                .binary_path()
 94                .await?
 95                .to_string_lossy()
 96                .into_owned(),
 97            arguments: vec![
 98                adapter_path
 99                    .join(Self::ADAPTER_PATH)
100                    .to_string_lossy()
101                    .to_string(),
102                format!("--server={}", port),
103            ],
104            connection: Some(TcpArguments {
105                port,
106                host,
107                timeout,
108            }),
109            cwd: None,
110            envs: HashMap::default(),
111            request_args: self.request_args(config)?,
112        })
113    }
114}
115
116#[async_trait(?Send)]
117impl DebugAdapter for PhpDebugAdapter {
118    fn name(&self) -> DebugAdapterName {
119        DebugAdapterName(Self::ADAPTER_NAME.into())
120    }
121
122    async fn get_binary(
123        &self,
124        delegate: &dyn DapDelegate,
125        config: &DebugTaskDefinition,
126        user_installed_path: Option<PathBuf>,
127        cx: &mut AsyncApp,
128    ) -> Result<DebugAdapterBinary> {
129        if self.checked.set(()).is_ok() {
130            delegate.output_to_console(format!("Checking latest version of {}...", self.name()));
131            if let Some(version) = self.fetch_latest_adapter_version(delegate).await.log_err() {
132                adapters::download_adapter_from_github(
133                    self.name(),
134                    version,
135                    adapters::DownloadedFileType::Vsix,
136                    delegate,
137                )
138                .await?;
139            }
140        }
141
142        self.get_installed_binary(delegate, &config, user_installed_path, cx)
143            .await
144    }
145}