1 use cancellation::CancellationToken;
2 use lsp_types::{Location, ReferenceParams};
3
4 use crate::{
5 features::cursor::CursorContext,
6 syntax::{bibtex, latex, CstNode},
7 DocumentData, LineIndexExt,
8 };
9
find_entry_references( context: &CursorContext<ReferenceParams>, cancellation_token: &CancellationToken, references: &mut Vec<Location>, ) -> Option<()>10 pub fn find_entry_references(
11 context: &CursorContext<ReferenceParams>,
12 cancellation_token: &CancellationToken,
13 references: &mut Vec<Location>,
14 ) -> Option<()> {
15 let (key_text, _) = context
16 .find_citation_key_word()
17 .or_else(|| context.find_citation_key_command())
18 .or_else(|| context.find_entry_key())?;
19
20 for document in &context.request.subset.documents {
21 cancellation_token.result().ok()?;
22
23 match &document.data {
24 DocumentData::Latex(data) => {
25 data.root
26 .descendants()
27 .filter_map(latex::Citation::cast)
28 .filter_map(|citation| citation.key_list())
29 .flat_map(|keys| keys.keys())
30 .filter(|key| key.to_string() == key_text)
31 .map(|key| document.line_index.line_col_lsp_range(key.small_range()))
32 .for_each(|range| {
33 references.push(Location::new(document.uri.as_ref().clone().into(), range));
34 });
35 }
36 DocumentData::Bibtex(data) if context.request.params.context.include_declaration => {
37 data.root
38 .children()
39 .filter_map(bibtex::Entry::cast)
40 .filter_map(|entry| entry.key())
41 .filter(|key| key.to_string() == key_text)
42 .map(|key| document.line_index.line_col_lsp_range(key.small_range()))
43 .for_each(|range| {
44 references.push(Location::new(document.uri.as_ref().clone().into(), range));
45 });
46 }
47 DocumentData::Bibtex(_) | DocumentData::BuildLog(_) => {}
48 }
49 }
50 Some(())
51 }
52
53 #[cfg(test)]
54 mod tests {
55 use lsp_types::Range;
56
57 use crate::{features::testing::FeatureTester, RangeExt};
58
59 use super::*;
60
61 #[test]
test_empty_latex_document()62 fn test_empty_latex_document() {
63 let request = FeatureTester::builder()
64 .files(vec![("main.tex", "")])
65 .main("main.tex")
66 .line(0)
67 .character(0)
68 .build()
69 .reference();
70
71 let mut actual_references = Vec::new();
72 let context = CursorContext::new(request);
73 find_entry_references(&context, CancellationToken::none(), &mut actual_references);
74
75 assert!(actual_references.is_empty());
76 }
77
78 #[test]
test_empty_bibtex_document()79 fn test_empty_bibtex_document() {
80 let request = FeatureTester::builder()
81 .files(vec![("main.bib", "")])
82 .main("main.bib")
83 .line(0)
84 .character(0)
85 .build()
86 .reference();
87
88 let mut actual_references = Vec::new();
89 let context = CursorContext::new(request);
90 find_entry_references(&context, CancellationToken::none(), &mut actual_references);
91
92 assert!(actual_references.is_empty());
93 }
94
95 #[test]
test_definition()96 fn test_definition() {
97 let tester = FeatureTester::builder()
98 .files(vec![
99 ("foo.bib", r#"@article{foo,}"#),
100 ("bar.tex", r#"\cite{foo}\addbibresource{foo.bib}"#),
101 ])
102 .main("foo.bib")
103 .line(0)
104 .character(11)
105 .build();
106 let uri = tester.uri("bar.tex");
107 let mut actual_references = Vec::new();
108
109 let request = tester.reference();
110 let context = CursorContext::new(request);
111 find_entry_references(&context, CancellationToken::none(), &mut actual_references);
112
113 let expected_references = vec![Location::new(
114 uri.as_ref().clone().into(),
115 Range::new_simple(0, 6, 0, 9),
116 )];
117 assert_eq!(actual_references, expected_references);
118 }
119
120 #[test]
test_definition_include_declaration()121 fn test_definition_include_declaration() {
122 let tester = FeatureTester::builder()
123 .files(vec![
124 ("foo.bib", r#"@article{foo,}"#),
125 ("bar.tex", r#"\cite{foo}\addbibresource{foo.bib}"#),
126 ])
127 .main("foo.bib")
128 .line(0)
129 .character(11)
130 .include_declaration(true)
131 .build();
132 let uri1 = tester.uri("foo.bib");
133 let uri2 = tester.uri("bar.tex");
134 let mut actual_references = Vec::new();
135
136 let request = tester.reference();
137 let context = CursorContext::new(request);
138 find_entry_references(&context, CancellationToken::none(), &mut actual_references);
139
140 let expected_references = vec![
141 Location::new(uri1.as_ref().clone().into(), Range::new_simple(0, 9, 0, 12)),
142 Location::new(uri2.as_ref().clone().into(), Range::new_simple(0, 6, 0, 9)),
143 ];
144 assert_eq!(actual_references, expected_references);
145 }
146
147 #[test]
test_reference()148 fn test_reference() {
149 let tester = FeatureTester::builder()
150 .files(vec![
151 ("foo.bib", r#"@article{foo,}"#),
152 ("bar.tex", r#"\cite{foo}\addbibresource{foo.bib}"#),
153 ])
154 .main("bar.tex")
155 .line(0)
156 .character(8)
157 .build();
158 let uri = tester.uri("bar.tex");
159 let mut actual_references = Vec::new();
160
161 let request = tester.reference();
162 let context = CursorContext::new(request);
163 find_entry_references(&context, CancellationToken::none(), &mut actual_references);
164
165 let expected_references = vec![Location::new(
166 uri.as_ref().clone().into(),
167 Range::new_simple(0, 6, 0, 9),
168 )];
169 assert_eq!(actual_references, expected_references);
170 }
171
172 #[test]
test_reference_include_declaration()173 fn test_reference_include_declaration() {
174 let tester = FeatureTester::builder()
175 .files(vec![
176 ("foo.bib", r#"@article{foo,}"#),
177 ("bar.tex", r#"\cite{foo}\addbibresource{foo.bib}"#),
178 ])
179 .main("bar.tex")
180 .line(0)
181 .character(6)
182 .include_declaration(true)
183 .build();
184 let uri1 = tester.uri("foo.bib");
185 let uri2 = tester.uri("bar.tex");
186 let mut actual_references = Vec::new();
187
188 let request = tester.reference();
189 let context = CursorContext::new(request);
190 find_entry_references(&context, CancellationToken::none(), &mut actual_references);
191
192 let expected_references = vec![
193 Location::new(uri2.as_ref().clone().into(), Range::new_simple(0, 6, 0, 9)),
194 Location::new(uri1.as_ref().clone().into(), Range::new_simple(0, 9, 0, 12)),
195 ];
196 assert_eq!(actual_references, expected_references);
197 }
198 }
199