1 use cancellation::CancellationToken;
2 use lsp_types::{DocumentHighlight, DocumentHighlightKind, DocumentHighlightParams};
3 
4 use crate::{
5     features::cursor::CursorContext,
6     syntax::{latex, CstNode},
7     LineIndexExt,
8 };
9 
find_label_highlights( context: &CursorContext<DocumentHighlightParams>, cancellation_token: &CancellationToken, ) -> Option<Vec<DocumentHighlight>>10 pub fn find_label_highlights(
11     context: &CursorContext<DocumentHighlightParams>,
12     cancellation_token: &CancellationToken,
13 ) -> Option<Vec<DocumentHighlight>> {
14     let (name_text, _) = context.find_label_name_key()?;
15 
16     let main_document = context.request.main_document();
17     let data = main_document.data.as_latex()?;
18 
19     let mut highlights = Vec::new();
20     for node in data.root.descendants() {
21         cancellation_token.result().ok()?;
22 
23         if let Some(label_name) = latex::LabelDefinition::cast(node)
24             .and_then(|label| label.name())
25             .and_then(|label_name| label_name.key())
26             .filter(|label_name| label_name.to_string() == name_text)
27         {
28             let range = main_document
29                 .line_index
30                 .line_col_lsp_range(label_name.small_range());
31 
32             highlights.push(DocumentHighlight {
33                 range,
34                 kind: Some(DocumentHighlightKind::Write),
35             });
36         } else if let Some(label) = latex::LabelReference::cast(node) {
37             for label_name in label
38                 .name_list()
39                 .into_iter()
40                 .flat_map(|name| name.keys())
41                 .filter(|label_name| label_name.to_string() == name_text)
42             {
43                 let range = main_document
44                     .line_index
45                     .line_col_lsp_range(label_name.small_range());
46 
47                 highlights.push(DocumentHighlight {
48                     range,
49                     kind: Some(DocumentHighlightKind::Read),
50                 });
51             }
52         } else if let Some(label) = latex::LabelReferenceRange::cast(node) {
53             if let Some(label_name) = label
54                 .from()
55                 .and_then(|label_name| label_name.key())
56                 .filter(|label_name| label_name.to_string() == name_text)
57             {
58                 let range = main_document
59                     .line_index
60                     .line_col_lsp_range(label_name.small_range());
61 
62                 highlights.push(DocumentHighlight {
63                     range,
64                     kind: Some(DocumentHighlightKind::Read),
65                 });
66             }
67 
68             if let Some(label_name) = label
69                 .to()
70                 .and_then(|label_name| label_name.key())
71                 .filter(|label_name| label_name.to_string() == name_text)
72             {
73                 let range = main_document
74                     .line_index
75                     .line_col_lsp_range(label_name.small_range());
76 
77                 highlights.push(DocumentHighlight {
78                     range,
79                     kind: Some(DocumentHighlightKind::Read),
80                 });
81             }
82         }
83     }
84 
85     Some(highlights)
86 }
87 
88 #[cfg(test)]
89 mod tests {
90     use lsp_types::Range;
91 
92     use crate::{features::testing::FeatureTester, RangeExt};
93 
94     use super::*;
95 
96     #[test]
test_empty_latex_document()97     fn test_empty_latex_document() {
98         let request = FeatureTester::builder()
99             .files(vec![("main.tex", "")])
100             .main("main.tex")
101             .line(0)
102             .character(0)
103             .build()
104             .highlight();
105         let context = CursorContext::new(request);
106 
107         let actual_links = find_label_highlights(&context, CancellationToken::none());
108 
109         assert!(actual_links.is_none());
110     }
111 
112     #[test]
test_empty_bibtex_document()113     fn test_empty_bibtex_document() {
114         let request = FeatureTester::builder()
115             .files(vec![("main.bib", "")])
116             .main("main.bib")
117             .line(0)
118             .character(0)
119             .build()
120             .highlight();
121         let context = CursorContext::new(request);
122 
123         let actual_links = find_label_highlights(&context, CancellationToken::none());
124 
125         assert!(actual_links.is_none());
126     }
127 
128     #[test]
test_label()129     fn test_label() {
130         let tester = FeatureTester::builder()
131             .files(vec![("main.tex", "\\label{foo}\n\\ref{foo}\\label{bar}")])
132             .main("main.tex")
133             .line(0)
134             .character(7)
135             .build();
136         let request = tester.highlight();
137         let context = CursorContext::new(request);
138 
139         let actual_highlights = find_label_highlights(&context, CancellationToken::none()).unwrap();
140 
141         let expected_highlights = vec![
142             DocumentHighlight {
143                 range: Range::new_simple(0, 7, 0, 10),
144                 kind: Some(DocumentHighlightKind::Write),
145             },
146             DocumentHighlight {
147                 range: Range::new_simple(1, 5, 1, 8),
148                 kind: Some(DocumentHighlightKind::Read),
149             },
150         ];
151 
152         assert_eq!(actual_highlights, expected_highlights);
153     }
154 }
155