use std::collections::HashMap;
use tower_lsp::lsp_types::*;
use crate::Backend;
use crate::symbol_map::{SelfStaticParentKind, SymbolKind, SymbolMap, VarDefKind};
use crate::util::{build_fqn, byte_range_to_lsp_range};
impl Backend {
pub fn handle_document_highlight(
&self,
uri: &str,
content: &str,
position: Position,
) -> Option<Vec<DocumentHighlight>> {
let span = self.lookup_symbol_at_position(uri, content, position)?;
let maps = self.symbol_maps.read();
let symbol_map = maps.get(uri)?;
let highlights = match &span.kind {
SymbolKind::Variable { name } => {
if let Some(VarDefKind::Property) = symbol_map.var_def_kind_at(name, span.start) {
self.highlight_member_name(symbol_map, content, name)
} else {
self.highlight_variable(symbol_map, content, name, span.start)
}
}
SymbolKind::ClassReference { name, is_fqn } => {
let ctx = self.file_context(uri);
let fqn = if *is_fqn {
name.clone()
} else {
ctx.resolve_name_at(name, span.start)
};
self.highlight_class(symbol_map, content, &fqn, &ctx.use_map, &ctx.namespace)
}
SymbolKind::ClassDeclaration { name } => {
let ctx = self.file_context(uri);
let fqn = build_fqn(name, &ctx.namespace);
self.highlight_class(symbol_map, content, &fqn, &ctx.use_map, &ctx.namespace)
}
SymbolKind::MemberAccess { member_name, .. } => {
self.highlight_member_name(symbol_map, content, member_name)
}
SymbolKind::MemberDeclaration { name, .. } => {
self.highlight_member_name(symbol_map, content, name)
}
SymbolKind::FunctionCall { name, .. } => {
self.highlight_function(symbol_map, content, name)
}
SymbolKind::ConstantReference { name } => {
self.highlight_constant(symbol_map, content, name)
}
SymbolKind::SelfStaticParent(ssp_kind) => {
if *ssp_kind == SelfStaticParentKind::This {
self.highlight_this(symbol_map, content, span.start, uri)
} else {
self.highlight_keyword(symbol_map, content, *ssp_kind, span.start, uri)
}
}
};
if highlights.is_empty() {
None
} else {
Some(highlights)
}
}
fn highlight_variable(
&self,
symbol_map: &SymbolMap,
content: &str,
var_name: &str,
cursor_offset: u32,
) -> Vec<DocumentHighlight> {
let scope_start = symbol_map.find_variable_scope(var_name, cursor_offset);
let mut highlights = Vec::new();
let mut seen_offsets = std::collections::HashSet::new();
for span in &symbol_map.spans {
if let SymbolKind::Variable { name } = &span.kind {
if name != var_name {
continue;
}
let span_scope = symbol_map.find_variable_scope(name, span.start);
if span_scope != scope_start {
continue;
}
seen_offsets.insert(span.start);
let kind = if symbol_map.var_def_kind_at(name, span.start).is_some() {
DocumentHighlightKind::WRITE
} else {
DocumentHighlightKind::READ
};
highlights.push(DocumentHighlight {
range: byte_range_to_lsp_range(content, span.start as usize, span.end as usize),
kind: Some(kind),
});
}
}
for def in &symbol_map.var_defs {
if def.name == var_name
&& def.scope_start == scope_start
&& seen_offsets.insert(def.offset)
{
let end_offset = def.offset + 1 + def.name.len() as u32;
highlights.push(DocumentHighlight {
range: byte_range_to_lsp_range(
content,
def.offset as usize,
end_offset as usize,
),
kind: Some(DocumentHighlightKind::WRITE),
});
}
}
highlights.sort_by(cmp_highlight_range);
highlights
}
fn highlight_this(
&self,
symbol_map: &SymbolMap,
content: &str,
cursor_offset: u32,
uri: &str,
) -> Vec<DocumentHighlight> {
let ctx_classes: Vec<std::sync::Arc<crate::types::ClassInfo>> =
self.ast_map.read().get(uri).cloned().unwrap_or_default();
let current_class = crate::util::find_class_at_offset(&ctx_classes, cursor_offset);
let (class_start, class_end) = match current_class {
Some(cc) => (cc.start_offset, cc.end_offset),
None => (0, u32::MAX),
};
let mut highlights = Vec::new();
for span in &symbol_map.spans {
if span.start < class_start || span.start > class_end {
continue;
}
if let SymbolKind::SelfStaticParent(SelfStaticParentKind::This) = &span.kind {
highlights.push(DocumentHighlight {
range: byte_range_to_lsp_range(content, span.start as usize, span.end as usize),
kind: Some(DocumentHighlightKind::READ),
});
}
}
highlights.sort_by(cmp_highlight_range);
highlights
}
fn highlight_class(
&self,
symbol_map: &SymbolMap,
content: &str,
target_fqn: &str,
use_map: &HashMap<String, String>,
namespace: &Option<String>,
) -> Vec<DocumentHighlight> {
let mut highlights = Vec::new();
for span in &symbol_map.spans {
let fqn = match &span.kind {
SymbolKind::ClassReference { name, is_fqn } => {
if *is_fqn {
name.clone()
} else {
Self::resolve_to_fqn(name, use_map, namespace)
}
}
SymbolKind::ClassDeclaration { name } => build_fqn(name, namespace),
_ => continue,
};
if fqn == target_fqn {
highlights.push(DocumentHighlight {
range: byte_range_to_lsp_range(content, span.start as usize, span.end as usize),
kind: Some(DocumentHighlightKind::READ),
});
}
}
highlights.sort_by(cmp_highlight_range);
highlights
}
fn highlight_member_name(
&self,
symbol_map: &SymbolMap,
content: &str,
target_name: &str,
) -> Vec<DocumentHighlight> {
let mut highlights = Vec::new();
for span in &symbol_map.spans {
match &span.kind {
SymbolKind::MemberAccess { member_name, .. } if member_name == target_name => {
highlights.push(DocumentHighlight {
range: byte_range_to_lsp_range(
content,
span.start as usize,
span.end as usize,
),
kind: Some(DocumentHighlightKind::READ),
});
}
SymbolKind::MemberDeclaration { name, .. } if name == target_name => {
highlights.push(DocumentHighlight {
range: byte_range_to_lsp_range(
content,
span.start as usize,
span.end as usize,
),
kind: Some(DocumentHighlightKind::WRITE),
});
}
SymbolKind::Variable { name } if name == target_name => {
if symbol_map
.var_def_kind_at(name, span.start)
.is_some_and(|k| *k == VarDefKind::Property)
{
highlights.push(DocumentHighlight {
range: byte_range_to_lsp_range(
content,
span.start as usize,
span.end as usize,
),
kind: Some(DocumentHighlightKind::WRITE),
});
}
}
_ => {}
}
}
highlights.sort_by(cmp_highlight_range);
highlights
}
fn highlight_function(
&self,
symbol_map: &SymbolMap,
content: &str,
target_name: &str,
) -> Vec<DocumentHighlight> {
let mut highlights = Vec::new();
for span in &symbol_map.spans {
if let SymbolKind::FunctionCall { name, .. } = &span.kind
&& name == target_name
{
highlights.push(DocumentHighlight {
range: byte_range_to_lsp_range(content, span.start as usize, span.end as usize),
kind: Some(DocumentHighlightKind::READ),
});
}
}
highlights.sort_by(cmp_highlight_range);
highlights
}
fn highlight_constant(
&self,
symbol_map: &SymbolMap,
content: &str,
target_name: &str,
) -> Vec<DocumentHighlight> {
let mut highlights = Vec::new();
for span in &symbol_map.spans {
if let SymbolKind::ConstantReference { name } = &span.kind
&& name == target_name
{
highlights.push(DocumentHighlight {
range: byte_range_to_lsp_range(content, span.start as usize, span.end as usize),
kind: Some(DocumentHighlightKind::READ),
});
}
}
highlights.sort_by(cmp_highlight_range);
highlights
}
fn highlight_keyword(
&self,
symbol_map: &SymbolMap,
content: &str,
target_kind: SelfStaticParentKind,
cursor_offset: u32,
uri: &str,
) -> Vec<DocumentHighlight> {
let ctx_classes: Vec<std::sync::Arc<crate::types::ClassInfo>> =
self.ast_map.read().get(uri).cloned().unwrap_or_default();
let current_class = crate::util::find_class_at_offset(&ctx_classes, cursor_offset);
let (class_start, class_end) = match current_class {
Some(cc) => (cc.start_offset, cc.end_offset),
None => (0, u32::MAX),
};
let mut highlights = Vec::new();
for span in &symbol_map.spans {
if span.start < class_start || span.start > class_end {
continue;
}
if let SymbolKind::SelfStaticParent(ssp_kind) = &span.kind
&& *ssp_kind == target_kind
{
highlights.push(DocumentHighlight {
range: byte_range_to_lsp_range(content, span.start as usize, span.end as usize),
kind: Some(DocumentHighlightKind::READ),
});
}
}
highlights.sort_by(cmp_highlight_range);
highlights
}
}
fn cmp_highlight_range(a: &DocumentHighlight, b: &DocumentHighlight) -> std::cmp::Ordering {
a.range
.start
.line
.cmp(&b.range.start.line)
.then(a.range.start.character.cmp(&b.range.start.character))
}