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 <elliott.codes@gmail.com>
This commit is contained in:
Roman Zipp 2024-10-06 16:11:21 +02:00 committed by GitHub
parent 8376dd2011
commit 200b2bf70a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 116 additions and 0 deletions

View file

@ -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<CodeLabel> {
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,
}
}
}

View file

@ -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<CodeLabel> {
match language_server_id.as_ref() {
Intelephense::LANGUAGE_SERVER_ID => {
self.intelephense.as_ref()?.label_for_completion(completion)
}
_ => None,
}
}
}
zed::register_extension!(PhpExtension);