diff options
Diffstat (limited to 'support/texlab/src/definition')
-rw-r--r-- | support/texlab/src/definition/bibtex_string.rs | 132 | ||||
-rw-r--r-- | support/texlab/src/definition/latex_citation.rs | 119 | ||||
-rw-r--r-- | support/texlab/src/definition/latex_command.rs | 106 | ||||
-rw-r--r-- | support/texlab/src/definition/latex_label.rs | 141 | ||||
-rw-r--r-- | support/texlab/src/definition/mod.rs | 53 |
5 files changed, 551 insertions, 0 deletions
diff --git a/support/texlab/src/definition/bibtex_string.rs b/support/texlab/src/definition/bibtex_string.rs new file mode 100644 index 0000000000..0e4f643153 --- /dev/null +++ b/support/texlab/src/definition/bibtex_string.rs @@ -0,0 +1,132 @@ +use crate::syntax::*; +use crate::workspace::*; +use futures_boxed::boxed; +use lsp_types::{LocationLink, Position, TextDocumentPositionParams}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct BibtexStringDefinitionProvider; + +impl FeatureProvider for BibtexStringDefinitionProvider { + type Params = TextDocumentPositionParams; + type Output = Vec<LocationLink>; + + #[boxed] + async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output { + if let SyntaxTree::Bibtex(tree) = &request.document().tree { + if let Some(reference) = Self::find_reference(tree, request.params.position) { + return Self::find_definitions(&request.view.document.uri, tree, reference); + } + } + Vec::new() + } +} + +impl BibtexStringDefinitionProvider { + fn find_reference(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), + _ => None, + } + } + + fn find_definitions( + uri: &Uri, + tree: &BibtexSyntaxTree, + reference: &BibtexToken, + ) -> Vec<LocationLink> { + let mut links = Vec::new(); + for string in tree.strings() { + if let Some(name) = &string.name { + if name.text() == reference.text() { + links.push(LocationLink { + origin_selection_range: Some(reference.range()), + target_uri: uri.clone().into(), + target_range: string.range(), + target_selection_range: name.range(), + }); + } + } + } + links + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::range::RangeExt; + use lsp_types::{Position, Range}; + + #[test] + fn test_simple() { + let links = test_feature( + BibtexStringDefinitionProvider, + FeatureSpec { + files: vec![FeatureSpec::file( + "foo.bib", + "@string{foo = {bar}}\n@article{bar, author = foo}", + )], + main_file: "foo.bib", + position: Position::new(1, 24), + ..FeatureSpec::default() + }, + ); + + assert_eq!( + links, + vec![LocationLink { + origin_selection_range: Some(Range::new_simple(1, 23, 1, 26)), + target_uri: FeatureSpec::uri("foo.bib"), + target_range: Range::new_simple(0, 0, 0, 20), + target_selection_range: Range::new_simple(0, 8, 0, 11) + }] + ); + } + + #[test] + fn test_concat() { + let links = test_feature( + BibtexStringDefinitionProvider, + FeatureSpec { + files: vec![FeatureSpec::file( + "foo.bib", + "@string{foo = {bar}}\n@article{bar, author = foo # \"bar\"}", + )], + main_file: "foo.bib", + position: Position::new(1, 24), + ..FeatureSpec::default() + }, + ); + + assert_eq!( + links, + vec![LocationLink { + origin_selection_range: Some(Range::new_simple(1, 23, 1, 26)), + target_uri: FeatureSpec::uri("foo.bib"), + target_range: Range::new_simple(0, 0, 0, 20), + target_selection_range: Range::new_simple(0, 8, 0, 11) + }] + ); + } + + #[test] + fn test_field() { + let links = test_feature( + BibtexStringDefinitionProvider, + FeatureSpec { + files: vec![FeatureSpec::file( + "foo.bib", + "@string{foo = {bar}}\n@article{bar, author = foo}", + )], + main_file: "foo.bib", + position: Position::new(1, 18), + ..FeatureSpec::default() + }, + ); + + assert!(links.is_empty()); + } +} diff --git a/support/texlab/src/definition/latex_citation.rs b/support/texlab/src/definition/latex_citation.rs new file mode 100644 index 0000000000..daefa8b2ef --- /dev/null +++ b/support/texlab/src/definition/latex_citation.rs @@ -0,0 +1,119 @@ +use crate::range::RangeExt; +use crate::syntax::*; +use crate::workspace::*; +use futures_boxed::boxed; +use lsp_types::{LocationLink, TextDocumentPositionParams}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexCitationDefinitionProvider; + +impl FeatureProvider for LatexCitationDefinitionProvider { + type Params = TextDocumentPositionParams; + type Output = Vec<LocationLink>; + + #[boxed] + async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output { + let mut links = Vec::new(); + if let Some(reference) = Self::find_reference(&request) { + for document in request.related_documents() { + Self::find_definitions(&document, &reference, &mut links); + } + } + links + } +} + +impl LatexCitationDefinitionProvider { + fn find_definitions( + document: &Document, + reference: &LatexToken, + links: &mut Vec<LocationLink>, + ) { + if let SyntaxTree::Bibtex(tree) = &document.tree { + for entry in tree.entries() { + if let Some(key) = &entry.key { + if key.text() == reference.text() { + let link = LocationLink { + origin_selection_range: Some(reference.range()), + target_uri: document.uri.clone().into(), + target_range: entry.range(), + target_selection_range: key.range(), + }; + links.push(link); + } + } + } + } + } + + fn find_reference(request: &FeatureRequest<TextDocumentPositionParams>) -> Option<&LatexToken> { + if let SyntaxTree::Latex(tree) = &request.document().tree { + tree.citations + .iter() + .flat_map(LatexCitation::keys) + .find(|key| key.range().contains(request.params.position)) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use lsp_types::{Position, Range}; + + #[test] + fn test_has_definition() { + let links = test_feature( + LatexCitationDefinitionProvider, + FeatureSpec { + files: vec![ + FeatureSpec::file("foo.tex", "\\addbibresource{baz.bib}\n\\cite{foo}"), + FeatureSpec::file("bar.bib", "@article{foo, bar = {baz}}"), + FeatureSpec::file("baz.bib", "@article{foo, bar = {baz}}"), + ], + main_file: "foo.tex", + position: Position::new(1, 6), + ..FeatureSpec::default() + }, + ); + assert_eq!( + links, + vec![LocationLink { + origin_selection_range: Some(Range::new_simple(1, 6, 1, 9)), + target_uri: FeatureSpec::uri("baz.bib"), + target_range: Range::new_simple(0, 0, 0, 26), + target_selection_range: Range::new_simple(0, 9, 0, 12) + }] + ); + } + + #[test] + fn test_no_definition_latex() { + let links = test_feature( + LatexCitationDefinitionProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.tex", "")], + main_file: "foo.tex", + position: Position::new(0, 0), + ..FeatureSpec::default() + }, + ); + assert!(links.is_empty()); + } + + #[test] + fn test_no_definition_bibtex() { + let links = test_feature( + LatexCitationDefinitionProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.bib", "")], + main_file: "foo.bib", + position: Position::new(0, 0), + ..FeatureSpec::default() + }, + ); + assert!(links.is_empty()); + } +} diff --git a/support/texlab/src/definition/latex_command.rs b/support/texlab/src/definition/latex_command.rs new file mode 100644 index 0000000000..7f91412ef7 --- /dev/null +++ b/support/texlab/src/definition/latex_command.rs @@ -0,0 +1,106 @@ +use crate::syntax::*; +use crate::workspace::*; +use futures_boxed::boxed; +use lsp_types::{LocationLink, TextDocumentPositionParams}; + +pub struct LatexCommandDefinitionProvider; + +impl FeatureProvider for LatexCommandDefinitionProvider { + type Params = TextDocumentPositionParams; + type Output = Vec<LocationLink>; + + #[boxed] + async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output { + let mut definitions = Vec::new(); + if let SyntaxTree::Latex(tree) = &request.document().tree { + if let Some(LatexNode::Command(command)) = tree.find(request.params.position).last() { + for document in request.related_documents() { + if let SyntaxTree::Latex(tree) = &document.tree { + tree.command_definitions + .iter() + .filter(|def| def.definition.name.text() == command.name.text()) + .map(|def| LocationLink { + origin_selection_range: Some(command.range()), + target_uri: document.uri.clone().into(), + target_range: def.range(), + target_selection_range: def.range(), + }) + .for_each(|def| definitions.push(def)); + + tree.math + .operators + .iter() + .filter(|op| op.definition.name.text() == command.name.text()) + .map(|op| LocationLink { + origin_selection_range: Some(command.range()), + target_uri: document.uri.clone().into(), + target_range: op.range(), + target_selection_range: op.range(), + }) + .for_each(|def| definitions.push(def)); + } + } + } + } + definitions + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::range::RangeExt; + use lsp_types::{Position, Range}; + + #[test] + fn test_command_definition() { + let links = test_feature( + LatexCommandDefinitionProvider, + FeatureSpec { + files: vec![ + FeatureSpec::file("foo.tex", "\\include{bar}\n\\foo"), + FeatureSpec::file("bar.tex", "\\newcommand{\\foo}{bar}"), + FeatureSpec::file("baz.tex", "\\newcommand{\\foo}{baz}"), + ], + main_file: "foo.tex", + position: Position::new(1, 3), + ..FeatureSpec::default() + }, + ); + assert_eq!( + links, + vec![LocationLink { + origin_selection_range: Some(Range::new_simple(1, 0, 1, 4)), + target_uri: FeatureSpec::uri("bar.tex"), + target_range: Range::new_simple(0, 0, 0, 22), + target_selection_range: Range::new_simple(0, 0, 0, 22), + }] + ); + } + + #[test] + fn test_math_operator() { + let links = test_feature( + LatexCommandDefinitionProvider, + FeatureSpec { + files: vec![ + FeatureSpec::file("foo.tex", "\\include{bar}\n\\foo"), + FeatureSpec::file("bar.tex", "\\DeclareMathOperator{\\foo}{bar}"), + FeatureSpec::file("baz.tex", "\\DeclareMathOperator{\\foo}{baz}"), + ], + main_file: "foo.tex", + position: Position::new(1, 3), + ..FeatureSpec::default() + }, + ); + assert_eq!( + links, + vec![LocationLink { + origin_selection_range: Some(Range::new_simple(1, 0, 1, 4)), + target_uri: FeatureSpec::uri("bar.tex"), + target_range: Range::new_simple(0, 0, 0, 31), + target_selection_range: Range::new_simple(0, 0, 0, 31) + }] + ); + } +} diff --git a/support/texlab/src/definition/latex_label.rs b/support/texlab/src/definition/latex_label.rs new file mode 100644 index 0000000000..8d1a415749 --- /dev/null +++ b/support/texlab/src/definition/latex_label.rs @@ -0,0 +1,141 @@ +use crate::range::RangeExt; +use crate::symbol::build_section_tree; +use crate::syntax::*; +use crate::workspace::*; +use futures_boxed::boxed; +use lsp_types::{LocationLink, TextDocumentPositionParams}; +use std::sync::Arc; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct LatexLabelDefinitionProvider; + +impl FeatureProvider for LatexLabelDefinitionProvider { + type Params = TextDocumentPositionParams; + type Output = Vec<LocationLink>; + + #[boxed] + async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output { + let mut links = Vec::new(); + if let Some(reference) = Self::find_reference(&request) { + for document in request.related_documents() { + let workspace = Arc::clone(&request.view.workspace); + let view = DocumentView::new(workspace, Arc::clone(&document)); + Self::find_definitions(&view, &reference, &mut links); + } + } + links + } +} + +impl LatexLabelDefinitionProvider { + fn find_definitions( + view: &DocumentView, + reference: &LatexToken, + links: &mut Vec<LocationLink>, + ) { + if let SyntaxTree::Latex(tree) = &view.document.tree { + let outline = Outline::from(view); + let section_tree = build_section_tree(view, tree); + for label in &tree.structure.labels { + if label.kind == LatexLabelKind::Definition { + let context = OutlineContext::parse(view, label, &outline); + for name in label.names() { + if name.text() == reference.text() { + let target_range = if let Some(OutlineContextItem::Section { .. }) = + context.as_ref().map(|ctx| &ctx.item) + { + section_tree + .find(reference.text()) + .map(|sec| sec.full_range) + } else { + context.as_ref().map(|ctx| ctx.range) + }; + + links.push(LocationLink { + origin_selection_range: Some(reference.range()), + target_uri: view.document.uri.clone().into(), + target_range: target_range.unwrap_or_else(|| label.range()), + target_selection_range: label.range(), + }); + } + } + } + } + } + } + + fn find_reference(request: &FeatureRequest<TextDocumentPositionParams>) -> Option<&LatexToken> { + if let SyntaxTree::Latex(tree) = &request.document().tree { + tree.structure + .labels + .iter() + .flat_map(LatexLabel::names) + .find(|label| label.range().contains(request.params.position)) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use lsp_types::{Position, Range}; + + #[test] + fn test_has_definition() { + let links = test_feature( + LatexLabelDefinitionProvider, + FeatureSpec { + files: vec![ + FeatureSpec::file("foo.tex", "\\label{foo}"), + FeatureSpec::file( + "bar.tex", + "\\begin{a}\\begin{b}\\label{foo}\\end{b}\\end{a}\n\\input{baz.tex}", + ), + FeatureSpec::file("baz.tex", "\\ref{foo}"), + ], + main_file: "baz.tex", + position: Position::new(0, 5), + ..FeatureSpec::default() + }, + ); + assert_eq!( + links, + vec![LocationLink { + origin_selection_range: Some(Range::new_simple(0, 5, 0, 8)), + target_uri: FeatureSpec::uri("bar.tex"), + target_range: Range::new_simple(0, 18, 0, 29), + target_selection_range: Range::new_simple(0, 18, 0, 29) + }] + ); + } + + #[test] + fn test_no_definition_latex() { + let links = test_feature( + LatexLabelDefinitionProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.tex", "")], + main_file: "foo.tex", + position: Position::new(0, 0), + ..FeatureSpec::default() + }, + ); + assert!(links.is_empty()); + } + + #[test] + fn test_no_definition_bibtex() { + let links = test_feature( + LatexLabelDefinitionProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.bib", "")], + main_file: "foo.bib", + position: Position::new(0, 0), + ..FeatureSpec::default() + }, + ); + assert!(links.is_empty()); + } +} diff --git a/support/texlab/src/definition/mod.rs b/support/texlab/src/definition/mod.rs new file mode 100644 index 0000000000..9c7ac0145a --- /dev/null +++ b/support/texlab/src/definition/mod.rs @@ -0,0 +1,53 @@ +mod bibtex_string; +mod latex_citation; +mod latex_command; +mod latex_label; + +use self::bibtex_string::BibtexStringDefinitionProvider; +use self::latex_citation::LatexCitationDefinitionProvider; +use self::latex_command::LatexCommandDefinitionProvider; +use self::latex_label::LatexLabelDefinitionProvider; +use crate::workspace::*; +use futures_boxed::boxed; +use lsp_types::{Location, LocationLink, TextDocumentPositionParams}; +use serde::{Deserialize, Serialize}; + +pub struct DefinitionProvider { + provider: ConcatProvider<TextDocumentPositionParams, LocationLink>, +} + +impl DefinitionProvider { + pub fn new() -> Self { + Self { + provider: ConcatProvider::new(vec![ + Box::new(BibtexStringDefinitionProvider), + Box::new(LatexCitationDefinitionProvider), + Box::new(LatexCommandDefinitionProvider), + Box::new(LatexLabelDefinitionProvider), + ]), + } + } +} + +impl Default for DefinitionProvider { + fn default() -> Self { + Self::new() + } +} + +impl FeatureProvider for DefinitionProvider { + type Params = TextDocumentPositionParams; + type Output = Vec<LocationLink>; + + #[boxed] + async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output { + self.provider.execute(request).await + } +} + +#[serde(untagged)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub enum DefinitionResponse { + Locations(Vec<Location>), + LocationLinks(Vec<LocationLink>), +} |