From 200b2bf70a548b2eec75897220d3f3e82691b499 Mon Sep 17 00:00:00 2001 From: Roman Zipp Date: Sun, 6 Oct 2024 16:11:21 +0200 Subject: [PATCH] php: Add syntax highlighting for Intelephense completions (#18774) Release Notes: - N/A This PR introduces syntax highlighting for intelephense autocomple. The styling was selected to roughly match PHPStorm's default scheme. Please note that I'm not very familiar with writing Rust, but I'm happy to adapt to any requested changes! ## Examples ### Object attributes, methods and constants ![Screenshot 2024-10-06 at 13 38 03](https://github.com/user-attachments/assets/a91634ff-0f2e-41f0-b548-ecb09c40947c) ![Screenshot 2024-10-06 at 13 38 11](https://github.com/user-attachments/assets/b6f179f4-898b-4d82-9d36-a3e82328325c) ### Typed enum members ![Screenshot 2024-10-06 at 13 38 53](https://github.com/user-attachments/assets/7133b981-4f68-4210-b233-403cdf3ec9bb) ![Screenshot 2024-10-06 at 13 38 41](https://github.com/user-attachments/assets/2e806f3d-3538-45f2-b075-b8be5902b786) ### Variables Includes altered highlighting for [reserved variable names](https://www.php.net/manual/en/reserved.variables.php). ![Screenshot 2024-10-06 at 13 39 30](https://github.com/user-attachments/assets/be426eb8-5879-432d-b302-391c2c68a7cb) --------- Co-authored-by: Marshall Bowers --- .../php/src/language_servers/intelephense.rs | 102 ++++++++++++++++++ extensions/php/src/php.rs | 14 +++ 2 files changed, 116 insertions(+) diff --git a/extensions/php/src/language_servers/intelephense.rs b/extensions/php/src/language_servers/intelephense.rs index 7bd66b24abfaff0196e7bd252ce928483534bc75..23f47ac5c06344b641a88a0c46900fed36a12bfa 100644 --- a/extensions/php/src/language_servers/intelephense.rs +++ b/extensions/php/src/language_servers/intelephense.rs @@ -1,5 +1,6 @@ use std::{env, fs}; +use zed::{CodeLabel, CodeLabelSpan}; use zed_extension_api::settings::LspSettings; use zed_extension_api::{self as zed, serde_json, LanguageServerId, Result}; @@ -104,4 +105,105 @@ impl Intelephense { "intelephense": settings }))) } + + pub fn label_for_completion(&self, completion: zed::lsp::Completion) -> Option { + let label = &completion.label; + + match completion.kind? { + zed::lsp::CompletionKind::Method => { + // __construct method doesn't have a detail + if let Some(ref detail) = completion.detail { + if detail.is_empty() { + return Some(CodeLabel { + spans: vec![ + CodeLabelSpan::literal(label, Some("function.method".to_string())), + CodeLabelSpan::literal("()", None), + ], + filter_range: (0..label.len()).into(), + code: completion.label, + }); + } + } + + let mut parts = completion.detail.as_ref()?.split(":"); + // E.g., `foo(string $var)` + let name_and_params = parts.next()?; + let return_type = parts.next()?.trim(); + + let (_, params) = name_and_params.split_once("(")?; + let params = params.trim_end_matches(")"); + + Some(CodeLabel { + spans: vec![ + CodeLabelSpan::literal(label, Some("function.method".to_string())), + CodeLabelSpan::literal("(", None), + CodeLabelSpan::literal(params, Some("comment".to_string())), + CodeLabelSpan::literal("): ", None), + CodeLabelSpan::literal(return_type, Some("type".to_string())), + ], + filter_range: (0..label.len()).into(), + code: completion.label, + }) + } + zed::lsp::CompletionKind::Constant | zed::lsp::CompletionKind::EnumMember => { + if let Some(ref detail) = completion.detail { + if !detail.is_empty() { + return Some(CodeLabel { + spans: vec![ + CodeLabelSpan::literal(label, Some("constant".to_string())), + CodeLabelSpan::literal(" ", None), + CodeLabelSpan::literal(detail, Some("comment".to_string())), + ], + filter_range: (0..label.len()).into(), + code: completion.label, + }); + } + } + + Some(CodeLabel { + spans: vec![CodeLabelSpan::literal(label, Some("constant".to_string()))], + filter_range: (0..label.len()).into(), + code: completion.label, + }) + } + zed::lsp::CompletionKind::Property => { + let return_type = completion.detail?; + Some(CodeLabel { + spans: vec![ + CodeLabelSpan::literal(label, Some("attribute".to_string())), + CodeLabelSpan::literal(": ", None), + CodeLabelSpan::literal(return_type, Some("type".to_string())), + ], + filter_range: (0..label.len()).into(), + code: completion.label, + }) + } + zed::lsp::CompletionKind::Variable => { + // See https://www.php.net/manual/en/reserved.variables.php + const SYSTEM_VAR_NAMES: &[&str] = + &["argc", "argv", "php_errormsg", "http_response_header"]; + + let var_name = completion.label.trim_start_matches("$"); + let is_uppercase = var_name + .chars() + .filter(|c| c.is_alphabetic()) + .all(|c| c.is_uppercase()); + let is_system_constant = var_name.starts_with("_"); + let is_reserved = SYSTEM_VAR_NAMES.contains(&var_name); + + let highlight = if is_uppercase || is_system_constant || is_reserved { + Some("comment".to_string()) + } else { + None + }; + + Some(CodeLabel { + spans: vec![CodeLabelSpan::literal(label, highlight)], + filter_range: (0..label.len()).into(), + code: completion.label, + }) + } + _ => None, + } + } } diff --git a/extensions/php/src/php.rs b/extensions/php/src/php.rs index 7157bef07432f8b14e5cc0cde034f8bffabe34d6..53b4c299516241e28cebcd6d8bbd9d423055105b 100644 --- a/extensions/php/src/php.rs +++ b/extensions/php/src/php.rs @@ -1,5 +1,6 @@ mod language_servers; +use zed::CodeLabel; use zed_extension_api::{self as zed, serde_json, LanguageServerId, Result}; use crate::language_servers::{Intelephense, Phpactor}; @@ -53,6 +54,19 @@ impl zed::Extension for PhpExtension { Ok(None) } + + fn label_for_completion( + &self, + language_server_id: &zed::LanguageServerId, + completion: zed::lsp::Completion, + ) -> Option { + match language_server_id.as_ref() { + Intelephense::LANGUAGE_SERVER_ID => { + self.intelephense.as_ref()?.label_for_completion(completion) + } + _ => None, + } + } } zed::register_extension!(PhpExtension);