diff --git a/Cargo.lock b/Cargo.lock index ecf5751..b8b88a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -762,7 +762,7 @@ dependencies = [ [[package]] name = "zed_php" -version = "0.4.10" +version = "0.4.11" dependencies = [ "zed_extension_api", ] diff --git a/Cargo.toml b/Cargo.toml index c662249..57612ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zed_php" -version = "0.4.10" +version = "0.4.11" edition = "2021" publish = false license = "Apache-2.0" diff --git a/extension.toml b/extension.toml index 787d098..5ae4a11 100644 --- a/extension.toml +++ b/extension.toml @@ -1,7 +1,7 @@ id = "php" name = "PHP" description = "PHP support." -version = "0.4.10" +version = "0.4.11" schema_version = 1 authors = ["Piotr Osiewicz "] repository = "https://github.com/zed-extensions/php" @@ -20,6 +20,11 @@ language_ids = { PHP = "php" } name = "Phpactor" language = "PHP" +[language_servers.phpantom] +name = "PHPantom" +language = "PHP" +language_ids = { PHP = "php" } + [debug_adapters.Xdebug] [grammars.php] diff --git a/src/language_servers.rs b/src/language_servers.rs index 2cf1b6a..e66e177 100644 --- a/src/language_servers.rs +++ b/src/language_servers.rs @@ -1,7 +1,9 @@ mod intelephense; mod phpactor; +mod phpantom; mod phptools; pub use intelephense::*; pub use phpactor::*; +pub use phpantom::*; pub use phptools::*; diff --git a/src/language_servers/phpantom.rs b/src/language_servers/phpantom.rs new file mode 100644 index 0000000..179ff44 --- /dev/null +++ b/src/language_servers/phpantom.rs @@ -0,0 +1,140 @@ +use std::fs; + +use zed_extension_api::settings::LspSettings; +use zed_extension_api::{self as zed, LanguageServerId, Result}; + +const REPO: &str = "AJenbo/phpantom_lsp"; +const BINARY_NAME: &str = "phpantom_lsp"; + +pub struct Phpantom { + cached_binary_path: Option, +} + +impl Phpantom { + pub const LANGUAGE_SERVER_ID: &'static str = "phpantom"; + + pub fn new() -> Self { + Self { + cached_binary_path: None, + } + } + + pub fn language_server_command( + &mut self, + language_server_id: &LanguageServerId, + worktree: &zed::Worktree, + ) -> Result { + // Allow users to point at their own build via + // `lsp.phpantom.binary.{path,arguments}` in the settings. + if let Some(binary) = LspSettings::for_worktree("phpantom", worktree) + .ok() + .and_then(|settings| settings.binary) + { + if let Some(path) = binary.path { + return Ok(zed::Command { + command: path, + args: binary.arguments.unwrap_or_default(), + env: Default::default(), + }); + } + } + + Ok(zed::Command { + command: self.language_server_binary_path(language_server_id, worktree)?, + args: vec![], + env: Default::default(), + }) + } + + fn language_server_binary_path( + &mut self, + language_server_id: &LanguageServerId, + worktree: &zed::Worktree, + ) -> Result { + if let Some(path) = worktree.which(BINARY_NAME) { + return Ok(path); + } + + if let Some(path) = &self.cached_binary_path { + if fs::metadata(path).is_ok_and(|stat| stat.is_file()) { + return Ok(path.clone()); + } + } + + zed::set_language_server_installation_status( + language_server_id, + &zed::LanguageServerInstallationStatus::CheckingForUpdate, + ); + let release = zed::latest_github_release( + REPO, + zed::GithubReleaseOptions { + require_assets: true, + pre_release: false, + }, + )?; + + let (platform, arch) = zed::current_platform(); + + let (os_str, ext) = match platform { + zed::Os::Mac => ("apple-darwin", "tar.gz"), + zed::Os::Linux => ("unknown-linux-gnu", "tar.gz"), + zed::Os::Windows => ("pc-windows-msvc", "zip"), + }; + + let arch_str = match arch { + zed::Architecture::Aarch64 => "aarch64", + zed::Architecture::X8664 => "x86_64", + _ => return Err(format!("unsupported architecture: {arch:?}")), + }; + + let asset_name = format!("{BINARY_NAME}-{arch_str}-{os_str}.{ext}"); + let asset = release + .assets + .iter() + .find(|asset| asset.name == asset_name) + .ok_or_else(|| { + format!( + "no release asset found matching {asset_name:?} — you may need to build \ + {BINARY_NAME} from source for your platform" + ) + })?; + + let version_dir = format!("{BINARY_NAME}-{}", release.version); + fs::create_dir_all(&version_dir).map_err(|e| format!("failed to create directory: {e}"))?; + + let binary_path = match platform { + zed::Os::Windows => format!("{version_dir}/{BINARY_NAME}.exe"), + _ => format!("{version_dir}/{BINARY_NAME}"), + }; + + if !fs::metadata(&binary_path).is_ok_and(|stat| stat.is_file()) { + zed::set_language_server_installation_status( + language_server_id, + &zed::LanguageServerInstallationStatus::Downloading, + ); + + let file_type = match ext { + "tar.gz" => zed::DownloadedFileType::GzipTar, + "zip" => zed::DownloadedFileType::Zip, + _ => unreachable!(), + }; + + zed::download_file(&asset.download_url, &version_dir, file_type) + .map_err(|e| format!("failed to download file: {e}"))?; + + zed::make_file_executable(&binary_path)?; + + let entries = + fs::read_dir(".").map_err(|e| format!("failed to list working directory: {e}"))?; + for entry in entries { + let entry = entry.map_err(|e| format!("failed to load directory entry: {e}"))?; + if entry.file_name().to_str() != Some(&version_dir) { + fs::remove_dir_all(entry.path()).ok(); + } + } + } + + self.cached_binary_path = Some(binary_path.clone()); + Ok(binary_path) + } +} diff --git a/src/php.rs b/src/php.rs index 9a591f4..f27a313 100644 --- a/src/php.rs +++ b/src/php.rs @@ -9,7 +9,7 @@ use zed_extension_api::{ }; use crate::{ - language_servers::{Intelephense, PhpTools, Phpactor}, + language_servers::{Intelephense, PhpTools, Phpactor, Phpantom}, xdebug::XDebug, }; @@ -17,6 +17,7 @@ struct PhpExtension { phptools: Option, intelephense: Option, phpactor: Option, + phpantom: Option, xdebug: XDebug, } @@ -26,6 +27,7 @@ impl zed::Extension for PhpExtension { phptools: None, intelephense: None, phpactor: None, + phpantom: None, xdebug: XDebug::new(), } } @@ -85,6 +87,10 @@ impl zed::Extension for PhpExtension { }) } } + Phpantom::LANGUAGE_SERVER_ID => { + let phpantom = self.phpantom.get_or_insert_with(Phpantom::new); + phpantom.language_server_command(language_server_id, worktree) + } language_server_id => Err(format!("unknown language server: {language_server_id}")), } }