Skip to main content

logicaffeine_lsp/
document_highlights.rs

1//! Document highlights: every occurrence of the symbol under the cursor,
2//! with WRITE kind on binding and mutation sites and READ everywhere else —
3//! the cursor-hold highlight editors render natively.
4
5use tower_lsp::lsp_types::{DocumentHighlight, DocumentHighlightKind, Position, Range};
6
7use crate::document::DocumentState;
8use crate::semantic_tokens::find_mutation_targets;
9
10pub fn document_highlights(
11    doc: &DocumentState,
12    position: Position,
13) -> Option<Vec<DocumentHighlight>> {
14    let offset = doc.line_index.offset(position);
15
16    // Resolve the cursor to a definition: through the reference at the
17    // cursor, or directly when the cursor sits on the definition itself.
18    let def_idx = doc
19        .symbol_index
20        .references
21        .iter()
22        .find(|r| r.span.start <= offset && offset < r.span.end)
23        .and_then(|r| r.definition_idx)
24        .or_else(|| {
25            doc.symbol_index
26                .definitions
27                .iter()
28                .position(|d| d.span.start <= offset && offset < d.span.end)
29        })?;
30    let def = &doc.symbol_index.definitions[def_idx];
31
32    let writes = find_mutation_targets(&doc.tokens);
33    let to_range = |span: logicaffeine_language::token::Span| Range {
34        start: doc.line_index.position(span.start),
35        end: doc.line_index.position(span.end),
36    };
37
38    let mut highlights = Vec::new();
39    if def.span != logicaffeine_language::token::Span::default() {
40        // The binding itself writes the name into existence.
41        highlights.push(DocumentHighlight {
42            range: to_range(def.span),
43            kind: Some(DocumentHighlightKind::WRITE),
44        });
45    }
46    for reference in &doc.symbol_index.references {
47        if reference.definition_idx != Some(def_idx) || reference.span.start == def.span.start {
48            continue;
49        }
50        let kind = if writes.contains(&reference.span.start) {
51            DocumentHighlightKind::WRITE
52        } else {
53            DocumentHighlightKind::READ
54        };
55        highlights.push(DocumentHighlight {
56            range: to_range(reference.span),
57            kind: Some(kind),
58        });
59    }
60
61    if highlights.is_empty() {
62        None
63    } else {
64        Some(highlights)
65    }
66}