tinymist_query/
references.rs1use std::sync::OnceLock;
2
3use tinymist_analysis::adt::interner::Interned;
4use typst::syntax::Span;
5
6use crate::{
7 StrRef,
8 analysis::{Definition, SearchCtx},
9 prelude::*,
10 syntax::{RefExpr, SyntaxClass, get_index_info},
11};
12
13#[derive(Debug, Clone)]
19pub struct ReferencesRequest {
20 pub path: PathBuf,
22 pub position: LspPosition,
24}
25
26impl SemanticRequest for ReferencesRequest {
27 type Response = Vec<LspLocation>;
28
29 fn request(self, ctx: &mut LocalContext) -> Option<Self::Response> {
30 let source = ctx.source_by_path(&self.path).ok()?;
31 let syntax = ctx.classify_for_decl(&source, self.position)?;
32
33 let locations = find_references(ctx, &source, syntax)?;
34
35 crate::log_debug_ct!("references: {locations:?}");
36 Some(locations)
37 }
38}
39
40pub(crate) fn find_references(
41 ctx: &mut LocalContext,
42 source: &Source,
43 syntax: SyntaxClass<'_>,
44) -> Option<Vec<LspLocation>> {
45 let finding_label = match syntax {
46 SyntaxClass::VarAccess(..) | SyntaxClass::Callee(..) => false,
47 SyntaxClass::Label { .. }
48 | SyntaxClass::Ref {
49 suffix_colon: false,
50 ..
51 } => true,
52 SyntaxClass::ImportPath(..)
53 | SyntaxClass::IncludePath(..)
54 | SyntaxClass::Ref {
55 suffix_colon: true, ..
56 }
57 | SyntaxClass::Normal(..) => {
58 return None;
59 }
60 };
61
62 let def = ctx.def_of_syntax(source, syntax)?;
63
64 let worker = ReferencesWorker {
65 ctx: ctx.fork_for_search(),
66 references: vec![],
67 def,
68 module_path: OnceLock::new(),
69 };
70
71 if finding_label {
72 worker.label_root()
73 } else {
74 worker.ident_root()
76 }
77}
78
79struct ReferencesWorker<'a> {
80 ctx: SearchCtx<'a>,
81 references: Vec<LspLocation>,
82 def: Definition,
83 module_path: OnceLock<StrRef>,
84}
85
86impl ReferencesWorker<'_> {
87 fn label_root(mut self) -> Option<Vec<LspLocation>> {
88 for ref_fid in self.ctx.ctx.depended_files() {
89 self.file(ref_fid)?;
90 }
91
92 Some(self.references)
93 }
94
95 fn ident_root(mut self) -> Option<Vec<LspLocation>> {
96 self.file(self.def.decl.file_id()?);
97 while let Some(ref_fid) = self.ctx.worklist.pop() {
98 self.file(ref_fid);
99 }
100
101 Some(self.references)
102 }
103
104 fn file(&mut self, ref_fid: TypstFileId) -> Option<()> {
105 log::debug!("references: file: {ref_fid:?}");
106 let src = self.ctx.ctx.source_by_id(ref_fid).ok()?;
107 let index = get_index_info(&src);
108 match self.def.decl.kind() {
109 DefKind::Constant | DefKind::Function | DefKind::Struct | DefKind::Variable => {
110 if !index.identifiers.contains(self.def.decl.name()) {
111 return Some(());
112 }
113 }
114 DefKind::Module => {
115 let ref_by_ident = index.identifiers.contains(self.def.decl.name());
116 let ref_by_path = index.paths.contains(self.module_path());
117 if !(ref_by_ident || ref_by_path) {
118 return Some(());
119 }
120 }
121 DefKind::Reference => {}
122 }
123
124 let ei = self.ctx.ctx.expr_stage(&src);
125 let uri = self.ctx.ctx.uri_for_id(ref_fid).ok()?;
126
127 let t = ei.get_refs(self.def.decl.clone());
128 self.push_idents(&ei.source, &uri, t);
129
130 if ei.is_exported(&self.def.decl) {
131 self.ctx.push_dependents(ref_fid);
132 }
133
134 Some(())
135 }
136
137 fn push_idents<'b>(
138 &mut self,
139 src: &Source,
140 url: &Url,
141 idents: impl Iterator<Item = (&'b Span, &'b Interned<RefExpr>)>,
142 ) {
143 self.push_ranges(src, url, idents.map(|(span, _)| span));
144 }
145
146 fn push_ranges<'b>(&mut self, src: &Source, url: &Url, spans: impl Iterator<Item = &'b Span>) {
147 self.references.extend(spans.filter_map(|span| {
148 let range = self.ctx.ctx.to_lsp_range(src.range(*span)?, src);
150 Some(LspLocation {
151 uri: url.clone(),
152 range,
153 })
154 }));
155 }
156
157 fn module_path(&self) -> &StrRef {
159 self.module_path.get_or_init(|| {
160 self.def
161 .decl
162 .file_id()
163 .and_then(|fid| {
164 fid.vpath()
165 .as_rooted_path()
166 .file_name()?
167 .to_str()
168 .map(From::from)
169 })
170 .unwrap_or_default()
171 })
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178 use crate::tests::*;
179
180 #[test]
181 fn test() {
182 snapshot_testing("references", &|ctx, path| {
183 let source = ctx.source_by_path(&path).unwrap();
184
185 let request = ReferencesRequest {
186 path: path.clone(),
187 position: find_test_position(&source),
188 };
189
190 let result = request.request(ctx);
191 let mut result = result.map(|v| {
192 v.into_iter()
193 .map(|loc| {
194 let fp = file_uri(loc.uri.as_str());
195 format!(
196 "{fp}@{}:{}:{}:{}",
197 loc.range.start.line,
198 loc.range.start.character,
199 loc.range.end.line,
200 loc.range.end.character
201 )
202 })
203 .collect::<Vec<_>>()
204 });
205 if let Some(result) = result.as_mut() {
207 result.sort();
208 }
209
210 assert_snapshot!(JsonRepr::new_pure(result));
211 });
212 }
213}