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 7bd66b24ab..23f47ac5c0 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 7157bef074..53b4c29951 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);