summaryrefslogtreecommitdiff
path: root/support/texlab/src/features/reference/entry.rs
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/features/reference/entry.rs')
-rw-r--r--support/texlab/src/features/reference/entry.rs198
1 files changed, 198 insertions, 0 deletions
diff --git a/support/texlab/src/features/reference/entry.rs b/support/texlab/src/features/reference/entry.rs
new file mode 100644
index 0000000000..fbbcbdb4cc
--- /dev/null
+++ b/support/texlab/src/features/reference/entry.rs
@@ -0,0 +1,198 @@
+use cancellation::CancellationToken;
+use lsp_types::{Location, ReferenceParams};
+
+use crate::{
+ features::cursor::CursorContext,
+ syntax::{bibtex, latex, CstNode},
+ DocumentData, LineIndexExt,
+};
+
+pub fn find_entry_references(
+ context: &CursorContext<ReferenceParams>,
+ cancellation_token: &CancellationToken,
+ references: &mut Vec<Location>,
+) -> Option<()> {
+ let (key_text, _) = context
+ .find_citation_key_word()
+ .or_else(|| context.find_citation_key_command())
+ .or_else(|| context.find_entry_key())?;
+
+ for document in &context.request.subset.documents {
+ cancellation_token.result().ok()?;
+
+ match &document.data {
+ DocumentData::Latex(data) => {
+ data.root
+ .descendants()
+ .filter_map(latex::Citation::cast)
+ .filter_map(|citation| citation.key_list())
+ .flat_map(|keys| keys.words())
+ .filter(|key| key.text() == key_text)
+ .map(|key| document.line_index.line_col_lsp_range(key.text_range()))
+ .for_each(|range| {
+ references.push(Location::new(document.uri.as_ref().clone().into(), range));
+ });
+ }
+ DocumentData::Bibtex(data) if context.request.params.context.include_declaration => {
+ data.root
+ .children()
+ .filter_map(bibtex::Entry::cast)
+ .filter_map(|entry| entry.key())
+ .filter(|key| key.text() == key_text)
+ .map(|key| document.line_index.line_col_lsp_range(key.text_range()))
+ .for_each(|range| {
+ references.push(Location::new(document.uri.as_ref().clone().into(), range));
+ });
+ }
+ DocumentData::Bibtex(_) | DocumentData::BuildLog(_) => {}
+ }
+ }
+ Some(())
+}
+
+#[cfg(test)]
+mod tests {
+ use lsp_types::Range;
+
+ use crate::{features::testing::FeatureTester, RangeExt};
+
+ use super::*;
+
+ #[test]
+ fn test_empty_latex_document() {
+ let request = FeatureTester::builder()
+ .files(vec![("main.tex", "")])
+ .main("main.tex")
+ .line(0)
+ .character(0)
+ .build()
+ .reference();
+
+ let mut actual_references = Vec::new();
+ let context = CursorContext::new(request);
+ find_entry_references(&context, CancellationToken::none(), &mut actual_references);
+
+ assert!(actual_references.is_empty());
+ }
+
+ #[test]
+ fn test_empty_bibtex_document() {
+ let request = FeatureTester::builder()
+ .files(vec![("main.bib", "")])
+ .main("main.bib")
+ .line(0)
+ .character(0)
+ .build()
+ .reference();
+
+ let mut actual_references = Vec::new();
+ let context = CursorContext::new(request);
+ find_entry_references(&context, CancellationToken::none(), &mut actual_references);
+
+ assert!(actual_references.is_empty());
+ }
+
+ #[test]
+ fn test_definition() {
+ let tester = FeatureTester::builder()
+ .files(vec![
+ ("foo.bib", r#"@article{foo,}"#),
+ ("bar.tex", r#"\cite{foo}\addbibresource{foo.bib}"#),
+ ])
+ .main("foo.bib")
+ .line(0)
+ .character(11)
+ .build();
+ let uri = tester.uri("bar.tex");
+ let mut actual_references = Vec::new();
+
+ let request = tester.reference();
+ let context = CursorContext::new(request);
+ find_entry_references(&context, CancellationToken::none(), &mut actual_references);
+
+ let expected_references = vec![Location::new(
+ uri.as_ref().clone().into(),
+ Range::new_simple(0, 6, 0, 9),
+ )];
+ assert_eq!(actual_references, expected_references);
+ }
+
+ #[test]
+ fn test_definition_include_declaration() {
+ let tester = FeatureTester::builder()
+ .files(vec![
+ ("foo.bib", r#"@article{foo,}"#),
+ ("bar.tex", r#"\cite{foo}\addbibresource{foo.bib}"#),
+ ])
+ .main("foo.bib")
+ .line(0)
+ .character(11)
+ .include_declaration(true)
+ .build();
+ let uri1 = tester.uri("foo.bib");
+ let uri2 = tester.uri("bar.tex");
+ let mut actual_references = Vec::new();
+
+ let request = tester.reference();
+ let context = CursorContext::new(request);
+ find_entry_references(&context, CancellationToken::none(), &mut actual_references);
+
+ let expected_references = vec![
+ Location::new(uri1.as_ref().clone().into(), Range::new_simple(0, 9, 0, 12)),
+ Location::new(uri2.as_ref().clone().into(), Range::new_simple(0, 6, 0, 9)),
+ ];
+ assert_eq!(actual_references, expected_references);
+ }
+
+ #[test]
+ fn test_reference() {
+ let tester = FeatureTester::builder()
+ .files(vec![
+ ("foo.bib", r#"@article{foo,}"#),
+ ("bar.tex", r#"\cite{foo}\addbibresource{foo.bib}"#),
+ ])
+ .main("bar.tex")
+ .line(0)
+ .character(8)
+ .build();
+ let uri = tester.uri("bar.tex");
+ let mut actual_references = Vec::new();
+
+ let request = tester.reference();
+ let context = CursorContext::new(request);
+ find_entry_references(&context, CancellationToken::none(), &mut actual_references);
+
+ let expected_references = vec![Location::new(
+ uri.as_ref().clone().into(),
+ Range::new_simple(0, 6, 0, 9),
+ )];
+ assert_eq!(actual_references, expected_references);
+ }
+
+ #[test]
+ fn test_reference_include_declaration() {
+ let tester = FeatureTester::builder()
+ .files(vec![
+ ("foo.bib", r#"@article{foo,}"#),
+ ("bar.tex", r#"\cite{foo}\addbibresource{foo.bib}"#),
+ ])
+ .main("bar.tex")
+ .line(0)
+ .character(6)
+ .include_declaration(true)
+ .build();
+ let uri1 = tester.uri("foo.bib");
+ let uri2 = tester.uri("bar.tex");
+ let mut actual_references = Vec::new();
+
+ let request = tester.reference();
+ let context = CursorContext::new(request);
+ find_entry_references(&context, CancellationToken::none(), &mut actual_references);
+
+ let expected_references = vec![
+ Location::new(uri2.as_ref().clone().into(), Range::new_simple(0, 6, 0, 9)),
+ Location::new(uri1.as_ref().clone().into(), Range::new_simple(0, 9, 0, 12)),
+ ];
+ assert_eq!(actual_references, expected_references);
+ }
+}