diff options
Diffstat (limited to 'support/texlab/src/reference')
-rw-r--r-- | support/texlab/src/reference/bibtex_entry.rs | 197 | ||||
-rw-r--r-- | support/texlab/src/reference/bibtex_string.rs | 235 | ||||
-rw-r--r-- | support/texlab/src/reference/latex_label.rs | 180 | ||||
-rw-r--r-- | support/texlab/src/reference/mod.rs | 42 |
4 files changed, 654 insertions, 0 deletions
diff --git a/support/texlab/src/reference/bibtex_entry.rs b/support/texlab/src/reference/bibtex_entry.rs new file mode 100644 index 0000000000..23e09bdbb6 --- /dev/null +++ b/support/texlab/src/reference/bibtex_entry.rs @@ -0,0 +1,197 @@ +use crate::range::RangeExt; +use crate::syntax::*; +use crate::workspace::*; +use futures_boxed::boxed; +use lsp_types::{Location, ReferenceParams}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct BibtexEntryReferenceProvider; + +impl FeatureProvider for BibtexEntryReferenceProvider { + type Params = ReferenceParams; + type Output = Vec<Location>; + + #[boxed] + async fn execute<'a>(&'a self, request: &'a FeatureRequest<ReferenceParams>) -> Vec<Location> { + let mut references = Vec::new(); + if let Some(key) = Self::find_key(request) { + for document in request.related_documents() { + match &document.tree { + SyntaxTree::Latex(tree) => tree + .citations + .iter() + .flat_map(LatexCitation::keys) + .filter(|citation| citation.text() == key) + .map(|citation| { + Location::new(document.uri.clone().into(), citation.range()) + }) + .for_each(|location| references.push(location)), + SyntaxTree::Bibtex(tree) => { + if request.params.context.include_declaration { + for entry in tree.entries() { + if let Some(key_token) = &entry.key { + if key_token.text() == key { + let uri = document.uri.clone(); + let location = Location::new(uri.into(), key_token.range()); + references.push(location); + } + } + } + } + } + } + } + } + references + } +} + +impl BibtexEntryReferenceProvider { + fn find_key(request: &FeatureRequest<ReferenceParams>) -> Option<&str> { + match &request.document().tree { + SyntaxTree::Latex(tree) => tree + .citations + .iter() + .flat_map(LatexCitation::keys) + .find(|key| { + key.range() + .contains(request.params.text_document_position.position) + }) + .map(LatexToken::text), + SyntaxTree::Bibtex(tree) => { + for entry in tree.entries() { + if let Some(key) = &entry.key { + if key + .range() + .contains(request.params.text_document_position.position) + { + return Some(key.text()); + } + } + } + None + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::range::RangeExt; + use lsp_types::{Position, Range}; + + #[test] + fn test_entry() { + let references = test_feature( + BibtexEntryReferenceProvider, + FeatureSpec { + files: vec![ + FeatureSpec::file("foo.bib", "@article{foo, bar = {baz}}"), + FeatureSpec::file("bar.tex", "\\addbibresource{foo.bib}\n\\cite{foo}"), + FeatureSpec::file("baz.tex", "\\cite{foo}"), + ], + main_file: "foo.bib", + position: Position::new(0, 9), + include_declaration: false, + ..FeatureSpec::default() + }, + ); + assert_eq!( + references, + vec![Location::new( + FeatureSpec::uri("bar.tex"), + Range::new_simple(1, 6, 1, 9) + )] + ); + } + + #[test] + fn test_entry_include_declaration() { + let references = test_feature( + BibtexEntryReferenceProvider, + FeatureSpec { + files: vec![ + FeatureSpec::file("foo.bib", "@article{foo, bar = {baz}}"), + FeatureSpec::file("bar.tex", "\\addbibresource{foo.bib}\n\\cite{foo}"), + FeatureSpec::file("baz.tex", "\\cite{foo}"), + ], + main_file: "foo.bib", + position: Position::new(0, 9), + include_declaration: true, + ..FeatureSpec::default() + }, + ); + assert_eq!( + references, + vec![ + Location::new(FeatureSpec::uri("foo.bib"), Range::new_simple(0, 9, 0, 12)), + Location::new(FeatureSpec::uri("bar.tex"), Range::new_simple(1, 6, 1, 9)), + ] + ); + } + + #[test] + fn test_citation() { + let references = test_feature( + BibtexEntryReferenceProvider, + FeatureSpec { + files: vec![ + FeatureSpec::file("foo.bib", "@article{foo, bar = {baz}}"), + FeatureSpec::file("bar.tex", "\\addbibresource{foo.bib}\n\\cite{foo}"), + FeatureSpec::file("baz.tex", "\\cite{foo}"), + ], + main_file: "bar.tex", + position: Position::new(1, 8), + include_declaration: false, + ..FeatureSpec::default() + }, + ); + assert_eq!( + references, + vec![Location::new( + FeatureSpec::uri("bar.tex"), + Range::new_simple(1, 6, 1, 9) + )] + ); + } + + #[test] + fn test_citation_include_declaration() { + let references = test_feature( + BibtexEntryReferenceProvider, + FeatureSpec { + files: vec![ + FeatureSpec::file("foo.bib", "@article{foo, bar = {baz}}"), + FeatureSpec::file("bar.tex", "\\addbibresource{foo.bib}\n\\cite{foo}"), + FeatureSpec::file("baz.tex", "\\cite{foo}"), + ], + main_file: "bar.tex", + position: Position::new(1, 9), + include_declaration: true, + ..FeatureSpec::default() + }, + ); + assert_eq!( + references, + vec![ + Location::new(FeatureSpec::uri("bar.tex"), Range::new_simple(1, 6, 1, 9)), + Location::new(FeatureSpec::uri("foo.bib"), Range::new_simple(0, 9, 0, 12)), + ] + ); + } + + #[test] + fn test_empty() { + let references = test_feature( + BibtexEntryReferenceProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.tex", "")], + main_file: "foo.tex", + position: Position::new(0, 0), + ..FeatureSpec::default() + }, + ); + assert!(references.is_empty()); + } +} diff --git a/support/texlab/src/reference/bibtex_string.rs b/support/texlab/src/reference/bibtex_string.rs new file mode 100644 index 0000000000..58888dbf11 --- /dev/null +++ b/support/texlab/src/reference/bibtex_string.rs @@ -0,0 +1,235 @@ +use crate::range::RangeExt; +use crate::syntax::*; +use crate::workspace::*; +use futures_boxed::boxed; +use lsp_types::{Location, Position, ReferenceParams, Url}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct BibtexStringReferenceProvider; + +impl FeatureProvider for BibtexStringReferenceProvider { + type Params = ReferenceParams; + type Output = Vec<Location>; + + #[boxed] + async fn execute<'a>(&'a self, request: &'a FeatureRequest<ReferenceParams>) -> Vec<Location> { + let mut references = Vec::new(); + if let SyntaxTree::Bibtex(tree) = &request.document().tree { + if let Some(name) = + Self::find_name(tree, request.params.text_document_position.position) + { + let uri: Url = request.document().uri.clone().into(); + if request.params.context.include_declaration { + for string in tree.strings() { + if let Some(string_name) = &string.name { + if string_name.text() == name.text() { + references.push(Location::new(uri.clone(), string_name.range())); + } + } + } + } + + let mut visitor = BibtexStringReferenceVisitor::default(); + visitor.visit_root(&tree.root); + visitor + .references + .into_iter() + .filter(|reference| reference.text() == name.text()) + .map(|reference| Location::new(uri.clone(), reference.range())) + .for_each(|reference| references.push(reference)); + } + } + references + } +} + +impl BibtexStringReferenceProvider { + fn find_name(tree: &BibtexSyntaxTree, position: Position) -> Option<&BibtexToken> { + let mut nodes = tree.find(position); + nodes.reverse(); + match (&nodes[0], nodes.get(1)) { + (BibtexNode::Word(word), Some(BibtexNode::Field(_))) + | (BibtexNode::Word(word), Some(BibtexNode::Concat(_))) => Some(&word.token), + (BibtexNode::String(string), _) => string + .name + .as_ref() + .filter(|name| name.range().contains(position)), + _ => None, + } + } +} + +#[derive(Debug, Default)] +struct BibtexStringReferenceVisitor<'a> { + references: Vec<&'a BibtexToken>, +} + +impl<'a> BibtexVisitor<'a> for BibtexStringReferenceVisitor<'a> { + fn visit_root(&mut self, root: &'a BibtexRoot) { + BibtexWalker::walk_root(self, root); + } + + fn visit_comment(&mut self, _comment: &'a BibtexComment) {} + + fn visit_preamble(&mut self, preamble: &'a BibtexPreamble) { + BibtexWalker::walk_preamble(self, preamble); + } + + fn visit_string(&mut self, string: &'a BibtexString) { + BibtexWalker::walk_string(self, string); + } + + fn visit_entry(&mut self, entry: &'a BibtexEntry) { + BibtexWalker::walk_entry(self, entry); + } + + fn visit_field(&mut self, field: &'a BibtexField) { + if let Some(BibtexContent::Word(word)) = &field.content { + self.references.push(&word.token); + } + + BibtexWalker::walk_field(self, field); + } + + fn visit_word(&mut self, _word: &'a BibtexWord) {} + + fn visit_command(&mut self, _command: &'a BibtexCommand) {} + + fn visit_quoted_content(&mut self, content: &'a BibtexQuotedContent) { + BibtexWalker::walk_quoted_content(self, content); + } + + fn visit_braced_content(&mut self, content: &'a BibtexBracedContent) { + BibtexWalker::walk_braced_content(self, content); + } + + fn visit_concat(&mut self, concat: &'a BibtexConcat) { + if let BibtexContent::Word(word) = &concat.left { + self.references.push(&word.token); + } + + if let Some(BibtexContent::Word(word)) = &concat.right { + self.references.push(&word.token); + } + + BibtexWalker::walk_concat(self, concat); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::range::RangeExt; + use lsp_types::{Position, Range}; + + #[test] + fn test_definition() { + let references = test_feature( + BibtexStringReferenceProvider, + FeatureSpec { + files: vec![FeatureSpec::file( + "foo.bib", + "@string{foo = {Foo}}\n@string{bar = {Bar}}\n@article{baz, author = foo}", + )], + main_file: "foo.bib", + position: Position::new(2, 24), + include_declaration: false, + ..FeatureSpec::default() + }, + ); + assert_eq!( + references, + vec![Location::new( + FeatureSpec::uri("foo.bib"), + Range::new_simple(2, 23, 2, 26) + )] + ); + } + + #[test] + fn test_definition_include_declaration() { + let references = test_feature( + BibtexStringReferenceProvider, + FeatureSpec { + files: vec![FeatureSpec::file( + "foo.bib", + "@string{foo = {Foo}}\n@string{bar = {Bar}}\n@article{baz, author = foo}", + )], + main_file: "foo.bib", + position: Position::new(2, 24), + include_declaration: true, + ..FeatureSpec::default() + }, + ); + assert_eq!( + references, + vec![ + Location::new(FeatureSpec::uri("foo.bib"), Range::new_simple(0, 8, 0, 11)), + Location::new(FeatureSpec::uri("foo.bib"), Range::new_simple(2, 23, 2, 26)) + ] + ); + } + + #[test] + fn test_reference() { + let references = test_feature( + BibtexStringReferenceProvider, + FeatureSpec { + files: vec![FeatureSpec::file( + "foo.bib", + "@string{foo = {Foo}}\n@string{bar = {Bar}}\n@article{baz, author = foo}", + )], + main_file: "foo.bib", + position: Position::new(0, 10), + include_declaration: false, + ..FeatureSpec::default() + }, + ); + assert_eq!( + references, + vec![Location::new( + FeatureSpec::uri("foo.bib"), + Range::new_simple(2, 23, 2, 26) + )] + ); + } + + #[test] + fn test_reference_include_declaration() { + let references = test_feature( + BibtexStringReferenceProvider, + FeatureSpec { + files: vec![FeatureSpec::file( + "foo.bib", + "@string{foo = {Foo}}\n@string{bar = {Bar}}\n@article{baz, author = foo}", + )], + main_file: "foo.bib", + position: Position::new(0, 10), + include_declaration: true, + ..FeatureSpec::default() + }, + ); + assert_eq!( + references, + vec![ + Location::new(FeatureSpec::uri("foo.bib"), Range::new_simple(0, 8, 0, 11)), + Location::new(FeatureSpec::uri("foo.bib"), Range::new_simple(2, 23, 2, 26)) + ] + ); + } + + #[test] + fn test_empty() { + let references = test_feature( + BibtexStringReferenceProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.bib", "")], + main_file: "foo.bib", + position: Position::new(0, 0), + include_declaration: false, + ..FeatureSpec::default() + }, + ); + assert!(references.is_empty()); + } +} diff --git a/support/texlab/src/reference/latex_label.rs b/support/texlab/src/reference/latex_label.rs new file mode 100644 index 0000000000..424429b60e --- /dev/null +++ b/support/texlab/src/reference/latex_label.rs @@ -0,0 +1,180 @@ +use crate::range::RangeExt; +use crate::syntax::*; +use crate::workspace::*; +use futures_boxed::boxed; +use lsp_types::{Location, ReferenceParams}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexLabelReferenceProvider; + +impl FeatureProvider for LatexLabelReferenceProvider { + type Params = ReferenceParams; + type Output = Vec<Location>; + + #[boxed] + async fn execute<'a>(&'a self, request: &'a FeatureRequest<ReferenceParams>) -> Vec<Location> { + let mut references = Vec::new(); + if let Some(definition) = Self::find_name(request) { + for document in request.related_documents() { + if let SyntaxTree::Latex(tree) = &document.tree { + tree.structure + .labels + .iter() + .filter(|label| Self::is_included(request, label)) + .flat_map(LatexLabel::names) + .filter(|label| label.text() == definition) + .map(|label| Location::new(document.uri.clone().into(), label.range())) + .for_each(|location| references.push(location)) + } + } + } + references + } +} + +impl LatexLabelReferenceProvider { + fn find_name(request: &FeatureRequest<ReferenceParams>) -> Option<&str> { + if let SyntaxTree::Latex(tree) = &request.document().tree { + tree.structure + .labels + .iter() + .flat_map(LatexLabel::names) + .find(|label| { + label + .range() + .contains(request.params.text_document_position.position) + }) + .map(LatexToken::text) + } else { + None + } + } + + fn is_included(request: &FeatureRequest<ReferenceParams>, label: &LatexLabel) -> bool { + match label.kind { + LatexLabelKind::Reference(_) => true, + LatexLabelKind::Definition => request.params.context.include_declaration, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::range::RangeExt; + use lsp_types::{Position, Range}; + + #[test] + fn test_definition() { + let references = test_feature( + LatexLabelReferenceProvider, + FeatureSpec { + files: vec![ + FeatureSpec::file("foo.tex", "\\label{foo}"), + FeatureSpec::file("bar.tex", "\\input{foo.tex}\n\\ref{foo}"), + FeatureSpec::file("baz.tex", "\\ref{foo}"), + ], + main_file: "foo.tex", + include_declaration: false, + position: Position::new(0, 8), + ..FeatureSpec::default() + }, + ); + assert_eq!( + references, + vec![Location::new( + FeatureSpec::uri("bar.tex"), + Range::new_simple(1, 5, 1, 8) + )] + ); + } + + #[test] + fn test_definition_include_declaration() { + let references = test_feature( + LatexLabelReferenceProvider, + FeatureSpec { + files: vec![ + FeatureSpec::file("foo.tex", "\\label{foo}"), + FeatureSpec::file("bar.tex", "\\input{foo.tex}\n\\ref{foo}"), + FeatureSpec::file("baz.tex", "\\ref{foo}"), + ], + main_file: "foo.tex", + include_declaration: true, + position: Position::new(0, 8), + ..FeatureSpec::default() + }, + ); + assert_eq!( + references, + vec![ + Location::new(FeatureSpec::uri("foo.tex"), Range::new_simple(0, 7, 0, 10)), + Location::new(FeatureSpec::uri("bar.tex"), Range::new_simple(1, 5, 1, 8)), + ] + ); + } + + #[test] + fn test_reference() { + let references = test_feature( + LatexLabelReferenceProvider, + FeatureSpec { + files: vec![ + FeatureSpec::file("foo.tex", "\\label{foo}"), + FeatureSpec::file("bar.tex", "\\input{foo.tex}\n\\ref{foo}"), + FeatureSpec::file("baz.tex", "\\ref{foo}"), + ], + main_file: "bar.tex", + position: Position::new(1, 7), + include_declaration: false, + ..FeatureSpec::default() + }, + ); + assert_eq!( + references, + vec![Location::new( + FeatureSpec::uri("bar.tex"), + Range::new_simple(1, 5, 1, 8) + ),] + ); + } + + #[test] + fn test_reference_include_declaration() { + let references = test_feature( + LatexLabelReferenceProvider, + FeatureSpec { + files: vec![ + FeatureSpec::file("foo.tex", "\\label{foo}"), + FeatureSpec::file("bar.tex", "\\input{foo.tex}\n\\ref{foo}"), + FeatureSpec::file("baz.tex", "\\ref{foo}"), + ], + main_file: "bar.tex", + position: Position::new(1, 7), + include_declaration: true, + ..FeatureSpec::default() + }, + ); + assert_eq!( + references, + vec![ + Location::new(FeatureSpec::uri("bar.tex"), Range::new_simple(1, 5, 1, 8)), + Location::new(FeatureSpec::uri("foo.tex"), Range::new_simple(0, 7, 0, 10)), + ] + ); + } + + #[test] + fn test_bibtex() { + let references = test_feature( + LatexLabelReferenceProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.bib", "")], + main_file: "foo.bib", + position: Position::new(0, 0), + ..FeatureSpec::default() + }, + ); + assert!(references.is_empty()); + } +} diff --git a/support/texlab/src/reference/mod.rs b/support/texlab/src/reference/mod.rs new file mode 100644 index 0000000000..120807a9f1 --- /dev/null +++ b/support/texlab/src/reference/mod.rs @@ -0,0 +1,42 @@ +mod bibtex_entry; +mod bibtex_string; +mod latex_label; + +use self::bibtex_entry::BibtexEntryReferenceProvider; +use self::bibtex_string::BibtexStringReferenceProvider; +use self::latex_label::LatexLabelReferenceProvider; +use crate::workspace::*; +use futures_boxed::boxed; +use lsp_types::{Location, ReferenceParams}; + +pub struct ReferenceProvider { + provider: ConcatProvider<ReferenceParams, Location>, +} + +impl ReferenceProvider { + pub fn new() -> Self { + Self { + provider: ConcatProvider::new(vec![ + Box::new(BibtexEntryReferenceProvider), + Box::new(BibtexStringReferenceProvider), + Box::new(LatexLabelReferenceProvider), + ]), + } + } +} + +impl Default for ReferenceProvider { + fn default() -> Self { + Self::new() + } +} + +impl FeatureProvider for ReferenceProvider { + type Params = ReferenceParams; + type Output = Vec<Location>; + + #[boxed] + async fn execute<'a>(&'a self, request: &'a FeatureRequest<ReferenceParams>) -> Vec<Location> { + self.provider.execute(request).await + } +} |