1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
use tower_lsp::lsp_types::{DocumentHighlight, DocumentHighlightKind, Position, Range};
use crate::ast::ParsedDoc;
use crate::util::word_at_position;
use crate::walk::{collect_var_refs_in_scope, refs_in_stmts};
/// Return all ranges in the document where the word at `position` appears.
/// For `$variables` the search is scope-aware: only occurrences within the
/// same function/method scope are returned, preventing unrelated variables
/// with the same name in other scopes from being highlighted.
pub fn document_highlights(
source: &str,
doc: &ParsedDoc,
position: Position,
) -> Vec<DocumentHighlight> {
let word = match word_at_position(source, position) {
Some(w) => w,
None => return vec![],
};
let word_utf16_len: u32 = word.chars().map(|c| c.len_utf16() as u32).sum();
let sv = doc.view();
if word.starts_with('$') {
let bare = word.trim_start_matches('$');
let byte_off = sv.byte_of_position(position) as usize;
let mut var_spans = Vec::new();
collect_var_refs_in_scope(&doc.program().stmts, bare, byte_off, &mut var_spans);
var_spans
.into_iter()
.map(|(span, kind)| {
let start = sv.position_of(span.start);
let end = Position {
line: start.line,
character: start.character + word_utf16_len,
};
DocumentHighlight {
range: Range { start, end },
kind: Some(kind),
}
})
.collect()
} else {
// Use `doc.source()` (the string the AST was parsed from), not the
// caller's `source`. `refs_in_stmts` resolves AST name slices via
// `str_offset` pointer arithmetic; if the parameter `source` is a
// separate allocation, the arithmetic falls back to a content
// search that returns the *first* textual occurrence — including
// hits inside comments and string literals — instead of the actual
// AST node location.
let mut spans = Vec::new();
refs_in_stmts(doc.source(), &doc.program().stmts, &word, &mut spans);
spans
.into_iter()
.map(|span| {
let start = sv.position_of(span.start);
let end = Position {
line: start.line,
character: start.character + word_utf16_len,
};
DocumentHighlight {
range: Range { start, end },
kind: Some(DocumentHighlightKind::TEXT),
}
})
.collect()
}
}