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