use tower_lsp::lsp_types::*;
use crate::Backend;
use crate::symbol_map::{SelfStaticParentKind, SymbolKind, SymbolMap, VarDefKind};
use crate::types::ClassLikeKind;
const TT_NAMESPACE: u32 = 0;
const TT_CLASS: u32 = 1;
const TT_INTERFACE: u32 = 2;
const TT_ENUM: u32 = 3;
const TT_TYPE: u32 = 4;
const TT_TYPE_PARAMETER: u32 = 5;
const TT_PARAMETER: u32 = 6;
const TT_VARIABLE: u32 = 7;
const TT_PROPERTY: u32 = 8;
const TT_FUNCTION: u32 = 9;
const TT_METHOD: u32 = 10;
const TT_DECORATOR: u32 = 11;
const TT_ENUM_MEMBER: u32 = 12;
const TM_DECLARATION: u32 = 1 << 0;
const TM_STATIC: u32 = 1 << 1;
const TM_READONLY: u32 = 1 << 2;
const TM_DEPRECATED: u32 = 1 << 3;
const TM_ABSTRACT: u32 = 1 << 4;
const TM_DEFINITION: u32 = 1 << 5;
const TM_DEFAULT_LIBRARY: u32 = 1 << 6;
pub fn legend() -> SemanticTokensLegend {
const _: () = {
assert!(TT_NAMESPACE == 0);
assert!(TT_CLASS == 1);
assert!(TT_INTERFACE == 2);
assert!(TT_ENUM == 3);
assert!(TT_TYPE == 4);
assert!(TT_TYPE_PARAMETER == 5);
assert!(TT_PARAMETER == 6);
assert!(TT_VARIABLE == 7);
assert!(TT_PROPERTY == 8);
assert!(TT_FUNCTION == 9);
assert!(TT_METHOD == 10);
assert!(TT_DECORATOR == 11);
assert!(TT_ENUM_MEMBER == 12);
};
SemanticTokensLegend {
token_types: vec![
SemanticTokenType::NAMESPACE, SemanticTokenType::CLASS, SemanticTokenType::INTERFACE, SemanticTokenType::ENUM, SemanticTokenType::TYPE, SemanticTokenType::TYPE_PARAMETER, SemanticTokenType::PARAMETER, SemanticTokenType::VARIABLE, SemanticTokenType::PROPERTY, SemanticTokenType::FUNCTION, SemanticTokenType::METHOD, SemanticTokenType::DECORATOR, SemanticTokenType::ENUM_MEMBER, ],
token_modifiers: vec![
SemanticTokenModifier::DECLARATION, SemanticTokenModifier::STATIC, SemanticTokenModifier::READONLY, SemanticTokenModifier::DEPRECATED, SemanticTokenModifier::ABSTRACT, SemanticTokenModifier::DEFINITION, SemanticTokenModifier::DEFAULT_LIBRARY, ],
}
}
struct AbsoluteToken {
line: u32,
start_char: u32,
length: u32,
token_type: u32,
modifiers: u32,
}
impl Backend {
pub fn handle_semantic_tokens_full(
&self,
uri: &str,
content: &str,
) -> Option<SemanticTokensResult> {
let symbol_map = self.symbol_maps.read().get(uri)?.clone();
let ctx = self.file_context(uri);
let mut tokens = self.collect_tokens(&symbol_map, content, uri, &ctx);
tokens.sort_by(|a, b| a.line.cmp(&b.line).then(a.start_char.cmp(&b.start_char)));
tokens.dedup_by(|b, a| a.line == b.line && a.start_char == b.start_char);
let delta_tokens = encode_deltas(&tokens);
Some(SemanticTokensResult::Tokens(SemanticTokens {
result_id: None,
data: delta_tokens,
}))
}
fn collect_tokens(
&self,
symbol_map: &SymbolMap,
content: &str,
uri: &str,
ctx: &crate::types::FileContext,
) -> Vec<AbsoluteToken> {
let mut tokens = Vec::with_capacity(symbol_map.spans.len());
for span in &symbol_map.spans {
let length = span.end.saturating_sub(span.start);
if length == 0 {
continue;
}
let (token_type, modifiers) = match &span.kind {
SymbolKind::ClassReference { name, is_fqn } => {
if self.is_template_param(name, span.start, symbol_map) {
(TT_TYPE_PARAMETER, 0)
} else {
let tt = self.resolve_class_token_type(name, *is_fqn, ctx, span.start);
let mods = self.resolve_class_modifiers(name, *is_fqn, ctx, span.start);
(tt, mods)
}
}
SymbolKind::ClassDeclaration { name } => {
let tt = self.resolve_declaration_token_type(name, uri, ctx);
let mut mods = TM_DECLARATION;
mods |= self.resolve_class_declaration_modifiers(name, uri, ctx);
(tt, mods)
}
SymbolKind::MemberAccess {
member_name,
is_static,
is_method_call,
subject_text,
..
} => {
let tt = if *is_method_call {
TT_METHOD
} else {
TT_PROPERTY
};
let mut mods = if *is_static { TM_STATIC } else { 0 };
mods |= self.resolve_member_modifiers(
subject_text,
member_name,
*is_method_call,
uri,
ctx,
);
(tt, mods)
}
SymbolKind::MemberDeclaration { name, is_static } => {
let tt = self.classify_member_declaration(name, span.start, uri, ctx);
let mut mods = TM_DECLARATION;
if *is_static {
mods |= TM_STATIC;
}
(tt, mods)
}
SymbolKind::Variable { name } => {
let (tt, mut mods) =
self.classify_variable(name, span.start, symbol_map, uri, ctx);
if symbol_map.is_at_var_definition(name, span.start) {
mods |= TM_DEFINITION;
}
(tt, mods)
}
SymbolKind::FunctionCall {
name: _,
is_definition,
} => {
let mods = if *is_definition { TM_DECLARATION } else { 0 };
(TT_FUNCTION, mods)
}
SymbolKind::SelfStaticParent(ssp_kind) => match ssp_kind {
SelfStaticParentKind::This => (TT_VARIABLE, TM_READONLY | TM_DEFAULT_LIBRARY),
SelfStaticParentKind::Parent => {
let tt = self
.resolve_self_static_parent_token_type(ssp_kind, uri, ctx, span.start);
(tt, TM_DEFAULT_LIBRARY)
}
SelfStaticParentKind::Self_ | SelfStaticParentKind::Static => {
let tt = self
.resolve_self_static_parent_token_type(ssp_kind, uri, ctx, span.start);
(tt, TM_DEFAULT_LIBRARY)
}
},
SymbolKind::ConstantReference { name: _ } => {
let is_attr = span.start >= 2
&& content
.get((span.start as usize).saturating_sub(2)..span.start as usize)
.is_some_and(|s| s.ends_with('#') || s.ends_with("["));
if is_attr {
(TT_DECORATOR, 0)
} else {
(TT_ENUM_MEMBER, TM_READONLY)
}
}
};
if let Some(abs) =
offset_to_absolute(content, span.start, length, token_type, modifiers)
{
tokens.push(abs);
}
}
tokens
}
fn resolve_class_token_type(
&self,
name: &str,
is_fqn: bool,
ctx: &crate::types::FileContext,
offset: u32,
) -> u32 {
let fqn = if is_fqn {
name.to_string()
} else {
ctx.resolve_name_at(name, offset)
};
for class in &ctx.classes {
let class_fqn = match &class.file_namespace {
Some(ns) => format!("{}\\{}", ns, class.name),
None => class.name.clone(),
};
if class_fqn == fqn || class.name == fqn {
return kind_to_token_type(class.kind);
}
}
if let Some(class_info) = self.find_or_load_class(&fqn) {
return kind_to_token_type(class_info.kind);
}
TT_CLASS
}
fn resolve_class_modifiers(
&self,
name: &str,
is_fqn: bool,
ctx: &crate::types::FileContext,
offset: u32,
) -> u32 {
let fqn = if is_fqn {
name.to_string()
} else {
ctx.resolve_name_at(name, offset)
};
for class in &ctx.classes {
let class_fqn = match &class.file_namespace {
Some(ns) => format!("{}\\{}", ns, class.name),
None => class.name.clone(),
};
if class_fqn == fqn || class.name == fqn {
if class.deprecation_message.is_some() {
return TM_DEPRECATED;
}
return 0;
}
}
if let Some(class_info) = self.find_or_load_class(&fqn)
&& class_info.deprecation_message.is_some()
{
return TM_DEPRECATED;
}
0
}
fn resolve_declaration_token_type(
&self,
name: &str,
_uri: &str,
ctx: &crate::types::FileContext,
) -> u32 {
for class in &ctx.classes {
if class.name == name {
return kind_to_token_type(class.kind);
}
}
TT_CLASS
}
fn resolve_class_declaration_modifiers(
&self,
name: &str,
_uri: &str,
ctx: &crate::types::FileContext,
) -> u32 {
let mut mods = 0u32;
for class in &ctx.classes {
if class.name == name {
if class.deprecation_message.is_some() {
mods |= TM_DEPRECATED;
}
if class.is_abstract {
mods |= TM_ABSTRACT;
}
break;
}
}
mods
}
fn resolve_member_modifiers(
&self,
_subject_text: &str,
_member_name: &str,
_is_method_call: bool,
_uri: &str,
_ctx: &crate::types::FileContext,
) -> u32 {
0
}
fn classify_member_declaration(
&self,
name: &str,
offset: u32,
_uri: &str,
ctx: &crate::types::FileContext,
) -> u32 {
for class in &ctx.classes {
if offset < class.start_offset || offset > class.end_offset {
continue;
}
for method in &class.methods {
if method.name == name {
return TT_METHOD;
}
}
for prop in &class.properties {
if prop.name == name {
return TT_PROPERTY;
}
}
for constant in &class.constants {
if constant.name == name {
return TT_ENUM_MEMBER;
}
}
}
TT_METHOD
}
fn classify_variable(
&self,
name: &str,
offset: u32,
symbol_map: &SymbolMap,
_uri: &str,
_ctx: &crate::types::FileContext,
) -> (u32, u32) {
if let Some(kind) = symbol_map.var_def_kind_at(name, offset) {
match kind {
VarDefKind::Property => return (TT_PROPERTY, TM_DECLARATION),
VarDefKind::Parameter => return (TT_PARAMETER, 0),
_ => {}
}
}
let scope = symbol_map.find_enclosing_scope(offset);
for def in &symbol_map.var_defs {
if def.name == name && def.scope_start == scope {
match def.kind {
VarDefKind::Parameter => return (TT_PARAMETER, 0),
VarDefKind::Property => return (TT_PROPERTY, 0),
_ => {}
}
}
}
(TT_VARIABLE, 0)
}
fn is_template_param(&self, name: &str, offset: u32, symbol_map: &SymbolMap) -> bool {
symbol_map.find_template_def(name, offset).is_some()
}
fn resolve_self_static_parent_token_type(
&self,
ssp_kind: &crate::symbol_map::SelfStaticParentKind,
_uri: &str,
ctx: &crate::types::FileContext,
offset: u32,
) -> u32 {
if *ssp_kind == crate::symbol_map::SelfStaticParentKind::Parent {
if let Some(class) = ctx.classes.first()
&& let Some(ref parent_name) = class.parent_class
{
let fqn = ctx.resolve_name_at(parent_name, offset);
if let Some(parent_info) = self.find_or_load_class(&fqn) {
return kind_to_token_type(parent_info.kind);
}
}
}
TT_TYPE
}
}
fn kind_to_token_type(kind: ClassLikeKind) -> u32 {
match kind {
ClassLikeKind::Class => TT_CLASS,
ClassLikeKind::Interface => TT_INTERFACE,
ClassLikeKind::Trait => TT_TYPE,
ClassLikeKind::Enum => TT_ENUM,
}
}
fn offset_to_absolute(
content: &str,
start_offset: u32,
length: u32,
token_type: u32,
modifiers: u32,
) -> Option<AbsoluteToken> {
let pos = crate::util::offset_to_position(content, start_offset as usize);
Some(AbsoluteToken {
line: pos.line,
start_char: pos.character,
length,
token_type,
modifiers,
})
}
fn encode_deltas(tokens: &[AbsoluteToken]) -> Vec<SemanticToken> {
let mut result = Vec::with_capacity(tokens.len());
let mut prev_line = 0u32;
let mut prev_start = 0u32;
for tok in tokens {
let delta_line = tok.line - prev_line;
let delta_start = if delta_line == 0 {
tok.start_char - prev_start
} else {
tok.start_char
};
result.push(SemanticToken {
delta_line,
delta_start,
length: tok.length,
token_type: tok.token_type,
token_modifiers_bitset: tok.modifiers,
});
prev_line = tok.line;
prev_start = tok.start_char;
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn legend_has_correct_type_count() {
let l = legend();
assert!(l.token_types.len() > TT_ENUM_MEMBER as usize);
assert_eq!(l.token_types.len(), 13);
assert_eq!(l.token_modifiers.len(), 7);
}
#[test]
fn delta_encoding_single_token() {
let tokens = vec![AbsoluteToken {
line: 3,
start_char: 5,
length: 10,
token_type: TT_CLASS,
modifiers: 0,
}];
let deltas = encode_deltas(&tokens);
assert_eq!(deltas.len(), 1);
assert_eq!(deltas[0].delta_line, 3);
assert_eq!(deltas[0].delta_start, 5);
assert_eq!(deltas[0].length, 10);
assert_eq!(deltas[0].token_type, TT_CLASS);
}
#[test]
fn delta_encoding_same_line() {
let tokens = vec![
AbsoluteToken {
line: 1,
start_char: 2,
length: 3,
token_type: TT_VARIABLE,
modifiers: 0,
},
AbsoluteToken {
line: 1,
start_char: 10,
length: 4,
token_type: TT_METHOD,
modifiers: 0,
},
];
let deltas = encode_deltas(&tokens);
assert_eq!(deltas.len(), 2);
assert_eq!(deltas[0].delta_line, 1);
assert_eq!(deltas[0].delta_start, 2);
assert_eq!(deltas[1].delta_line, 0);
assert_eq!(deltas[1].delta_start, 8); }
#[test]
fn delta_encoding_new_line() {
let tokens = vec![
AbsoluteToken {
line: 1,
start_char: 5,
length: 3,
token_type: TT_FUNCTION,
modifiers: 0,
},
AbsoluteToken {
line: 3,
start_char: 2,
length: 6,
token_type: TT_CLASS,
modifiers: TM_DECLARATION,
},
];
let deltas = encode_deltas(&tokens);
assert_eq!(deltas.len(), 2);
assert_eq!(deltas[1].delta_line, 2); assert_eq!(deltas[1].delta_start, 2); assert_eq!(deltas[1].token_modifiers_bitset, TM_DECLARATION);
}
#[test]
fn kind_to_token_type_mapping() {
assert_eq!(kind_to_token_type(ClassLikeKind::Class), TT_CLASS);
assert_eq!(kind_to_token_type(ClassLikeKind::Interface), TT_INTERFACE);
assert_eq!(kind_to_token_type(ClassLikeKind::Enum), TT_ENUM);
assert_eq!(kind_to_token_type(ClassLikeKind::Trait), TT_TYPE);
}
}