php.rs

  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}