Skip to content

Commit 16d0455

Browse files
committed
Jiggle the colors
1 parent 83fdcb7 commit 16d0455

2 files changed

Lines changed: 76 additions & 27 deletions

File tree

src/semantic_tokens.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
//! `MemberAccess`, `PropertyAccess`, `VariableReference`, etc.) with
1111
//! byte offsets. The main work is mapping these to LSP semantic token
1212
//! types and computing the delta encoding.
13+
//!
14+
//! Language builtins (`self`, `static`, `parent`, `$this`) carry the
15+
//! `defaultLibrary` modifier so that themes can distinguish them from
16+
//! user-defined symbols.
1317
1418
use tower_lsp::lsp_types::*;
1519

@@ -46,6 +50,7 @@ const TM_READONLY: u32 = 1 << 2;
4650
const TM_DEPRECATED: u32 = 1 << 3;
4751
const TM_ABSTRACT: u32 = 1 << 4;
4852
const TM_DEFINITION: u32 = 1 << 5;
53+
const TM_DEFAULT_LIBRARY: u32 = 1 << 6;
4954

5055
/// Build the semantic token legend that is advertised in `initialize`.
5156
///
@@ -88,12 +93,13 @@ pub fn legend() -> SemanticTokensLegend {
8893
SemanticTokenType::ENUM_MEMBER, // 12
8994
],
9095
token_modifiers: vec![
91-
SemanticTokenModifier::DECLARATION, // bit 0
92-
SemanticTokenModifier::STATIC, // bit 1
93-
SemanticTokenModifier::READONLY, // bit 2
94-
SemanticTokenModifier::DEPRECATED, // bit 3
95-
SemanticTokenModifier::ABSTRACT, // bit 4
96-
SemanticTokenModifier::DEFINITION, // bit 5
96+
SemanticTokenModifier::DECLARATION, // bit 0
97+
SemanticTokenModifier::STATIC, // bit 1
98+
SemanticTokenModifier::READONLY, // bit 2
99+
SemanticTokenModifier::DEPRECATED, // bit 3
100+
SemanticTokenModifier::ABSTRACT, // bit 4
101+
SemanticTokenModifier::DEFINITION, // bit 5
102+
SemanticTokenModifier::DEFAULT_LIBRARY, // bit 6
97103
],
98104
}
99105
}
@@ -237,11 +243,15 @@ impl Backend {
237243
.get(span.start as usize..span.end as usize)
238244
.unwrap_or("");
239245
if source_text == "$this" {
240-
(TT_VARIABLE, TM_READONLY)
246+
(TT_VARIABLE, TM_READONLY | TM_DEFAULT_LIBRARY)
247+
} else if keyword == "parent" {
248+
// Resolve to the parent class kind when possible.
249+
let tt = self.resolve_self_static_parent_token_type(keyword, uri, ctx);
250+
(tt, TM_DEFAULT_LIBRARY)
241251
} else {
242-
// self, static, parent are type references.
252+
// self, static — resolve to enclosing class kind.
243253
let tt = self.resolve_self_static_parent_token_type(keyword, uri, ctx);
244-
(tt, 0)
254+
(tt, TM_DEFAULT_LIBRARY)
245255
}
246256
}
247257

@@ -481,9 +491,6 @@ impl Backend {
481491
_uri: &str,
482492
ctx: &crate::types::FileContext,
483493
) -> u32 {
484-
// These keywords refer to the enclosing class. We could resolve
485-
// the exact kind, but for simplicity just emit TYPE (since they
486-
// are type references in a class context).
487494
if keyword == "parent" {
488495
// Try to resolve the parent class kind.
489496
if let Some(class) = ctx.classes.first()
@@ -570,7 +577,7 @@ mod tests {
570577
// Ensure the legend has all the token types we reference.
571578
assert!(l.token_types.len() > TT_ENUM_MEMBER as usize);
572579
assert_eq!(l.token_types.len(), 13);
573-
assert_eq!(l.token_modifiers.len(), 6);
580+
assert_eq!(l.token_modifiers.len(), 7);
574581
}
575582

576583
#[test]

tests/semantic_tokens.rs

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ const TM_READONLY: u32 = 1 << 2;
4343
const TM_DEPRECATED: u32 = 1 << 3;
4444
const TM_ABSTRACT: u32 = 1 << 4;
4545
const TM_DEFINITION: u32 = 1 << 5;
46+
const TM_DEFAULT_LIBRARY: u32 = 1 << 6;
47+
48+
// ─── Helpers ────────────────────────────────────────────────────────────────
49+
50+
/// Assert that a decoded token has a specific modifier bit set.
51+
fn has_modifier(tok: &DecodedToken, modifier: u32) -> bool {
52+
tok.modifiers & modifier != 0
53+
}
4654

4755
/// Decode all tokens to absolute positions for easier assertion.
4856
#[derive(Debug)]
@@ -184,7 +192,7 @@ function make() {
184192
"#;
185193
let tokens = get_tokens(php);
186194
let decoded = decode_tokens(&tokens);
187-
// "Item" on line 3 (after `new `)
195+
// "Item" on line 3 (after `new `) should be a class reference.
188196
let item_refs: Vec<_> = decoded
189197
.iter()
190198
.filter(|t| t.token_type == TT_CLASS && t.length == 4 && t.line == 3)
@@ -396,7 +404,7 @@ function main() {
396404
}
397405

398406
#[test]
399-
fn this_is_variable_with_readonly() {
407+
fn this_is_variable_with_readonly_and_default_library() {
400408
let php = r#"<?php
401409
class Foo {
402410
public function bar(): void {
@@ -406,42 +414,76 @@ class Foo {
406414
"#;
407415
let tokens = get_tokens(php);
408416
let decoded = decode_tokens(&tokens);
409-
// "$this" on line 3 should be a variable with readonly modifier
417+
// "$this" on line 3 should be a variable with readonly + defaultLibrary
410418
let this_tokens: Vec<_> = decoded
411419
.iter()
412420
.filter(|t| t.token_type == TT_VARIABLE && t.length == 5 && t.line == 3)
413421
.collect();
414422
assert!(!this_tokens.is_empty(), "expected variable token for $this");
415423
assert!(
416-
this_tokens.iter().any(|t| t.modifiers & TM_READONLY != 0),
417-
"expected readonly modifier on $this"
424+
this_tokens
425+
.iter()
426+
.any(|t| has_modifier(t, TM_READONLY) && has_modifier(t, TM_DEFAULT_LIBRARY)),
427+
"expected readonly + defaultLibrary modifiers on $this"
418428
);
419429
}
420430

421431
#[test]
422-
fn self_static_parent_are_type_tokens() {
432+
fn self_static_are_type_with_default_library() {
423433
let php = r#"<?php
424-
class Base {}
425-
class Child extends Base {
434+
class Foo {
426435
public static function make(): static {
427436
return new static();
428437
}
429-
public function parent_ref(): void {
430-
parent::class;
438+
public function selfRef(): self {
439+
return $this;
431440
}
432441
}
433442
"#;
434443
let tokens = get_tokens(php);
435444
let decoded = decode_tokens(&tokens);
436-
// "self", "static", and "parent" are type references (TT_TYPE or class-kind).
437-
// "static" appears in type hint position on line 3
445+
// "static" in return type hint (line 2) → type + defaultLibrary
438446
let static_types: Vec<_> = decoded
439447
.iter()
440-
.filter(|t| t.token_type == TT_TYPE && t.length == 6)
448+
.filter(|t| t.token_type == TT_TYPE && t.length == 6 && has_modifier(t, TM_DEFAULT_LIBRARY))
441449
.collect();
442450
assert!(
443451
!static_types.is_empty(),
444-
"expected type token for static keyword"
452+
"expected type + defaultLibrary token for static keyword"
453+
);
454+
// "self" in return type hint (line 5) → type + defaultLibrary
455+
let self_types: Vec<_> = decoded
456+
.iter()
457+
.filter(|t| t.token_type == TT_TYPE && t.length == 4 && has_modifier(t, TM_DEFAULT_LIBRARY))
458+
.collect();
459+
assert!(
460+
!self_types.is_empty(),
461+
"expected type + defaultLibrary token for self keyword"
462+
);
463+
}
464+
465+
#[test]
466+
fn parent_has_default_library() {
467+
let php = r#"<?php
468+
class Base {}
469+
class Child extends Base {
470+
public function test(): void {
471+
parent::class;
472+
}
473+
}
474+
"#;
475+
let tokens = get_tokens(php);
476+
let decoded = decode_tokens(&tokens);
477+
// "parent" on line 4 should carry the defaultLibrary modifier.
478+
// The token type is resolved from the parent class kind (TT_CLASS
479+
// for Base) or falls back to TT_TYPE.
480+
let parent_tokens: Vec<_> = decoded
481+
.iter()
482+
.filter(|t| t.length == 6 && t.line == 4 && has_modifier(t, TM_DEFAULT_LIBRARY))
483+
.collect();
484+
assert!(
485+
!parent_tokens.is_empty(),
486+
"expected defaultLibrary modifier on parent keyword"
445487
);
446488
}
447489

0 commit comments

Comments
 (0)