1use language::BufferSnapshot;
2use std::collections::HashMap;
3use std::ops::Range;
4
5use crate::{
6 excerpt::{EditPredictionExcerpt, EditPredictionExcerptText},
7 outline::Identifier,
8};
9
10#[derive(Debug)]
11pub struct Reference {
12 pub identifier: Identifier,
13 pub range: Range<usize>,
14 pub region: ReferenceRegion,
15}
16
17#[derive(Copy, Clone, Debug, Eq, PartialEq)]
18pub enum ReferenceRegion {
19 Breadcrumb,
20 Nearby,
21}
22
23pub fn references_in_excerpt(
24 excerpt: &EditPredictionExcerpt,
25 excerpt_text: &EditPredictionExcerptText,
26 snapshot: &BufferSnapshot,
27) -> HashMap<Identifier, Vec<Reference>> {
28 let mut references = identifiers_in_range(
29 excerpt.range.clone(),
30 excerpt_text.body.as_str(),
31 ReferenceRegion::Nearby,
32 snapshot,
33 );
34
35 for (range, text) in excerpt
36 .parent_signature_ranges
37 .iter()
38 .zip(excerpt_text.parent_signatures.iter())
39 {
40 references.extend(identifiers_in_range(
41 range.clone(),
42 text.as_str(),
43 ReferenceRegion::Breadcrumb,
44 snapshot,
45 ));
46 }
47
48 let mut identifier_to_references: HashMap<Identifier, Vec<Reference>> = HashMap::new();
49 for reference in references {
50 identifier_to_references
51 .entry(reference.identifier.clone())
52 .or_insert_with(Vec::new)
53 .push(reference);
54 }
55 identifier_to_references
56}
57
58/// Finds all nodes which have a "variable" match from the highlights query within the offset range.
59pub fn identifiers_in_range(
60 range: Range<usize>,
61 range_text: &str,
62 reference_region: ReferenceRegion,
63 buffer: &BufferSnapshot,
64) -> Vec<Reference> {
65 let mut matches = buffer
66 .syntax
67 .matches(range.clone(), &buffer.text, |grammar| {
68 grammar
69 .highlights_config
70 .as_ref()
71 .map(|config| &config.query)
72 });
73
74 let mut references = Vec::new();
75 let mut last_added_range = None;
76 while let Some(mat) = matches.peek() {
77 let config = matches.grammars()[mat.grammar_index]
78 .highlights_config
79 .as_ref();
80
81 for capture in mat.captures {
82 if let Some(config) = config {
83 if config.identifier_capture_indices.contains(&capture.index) {
84 let node_range = capture.node.byte_range();
85
86 // sometimes multiple highlight queries match - this deduplicates them
87 if Some(node_range.clone()) == last_added_range {
88 continue;
89 }
90
91 let identifier_text =
92 &range_text[node_range.start - range.start..node_range.end - range.start];
93 references.push(Reference {
94 identifier: Identifier {
95 name: identifier_text.into(),
96 language_id: mat.language.id(),
97 },
98 range: node_range.clone(),
99 region: reference_region,
100 });
101 last_added_range = Some(node_range);
102 }
103 }
104 }
105
106 matches.advance();
107 }
108 references
109}