diff options
Diffstat (limited to 'support/texlab/src/definition')
-rw-r--r-- | support/texlab/src/definition/bibtex_string.rs | 225 | ||||
-rw-r--r-- | support/texlab/src/definition/latex_citation.rs | 161 | ||||
-rw-r--r-- | support/texlab/src/definition/latex_cmd.rs | 154 | ||||
-rw-r--r-- | support/texlab/src/definition/latex_command.rs | 106 | ||||
-rw-r--r-- | support/texlab/src/definition/latex_label.rs | 192 | ||||
-rw-r--r-- | support/texlab/src/definition/mod.rs | 32 |
6 files changed, 482 insertions, 388 deletions
diff --git a/support/texlab/src/definition/bibtex_string.rs b/support/texlab/src/definition/bibtex_string.rs index 0e4f643153..a1fe5132c0 100644 --- a/support/texlab/src/definition/bibtex_string.rs +++ b/support/texlab/src/definition/bibtex_string.rs @@ -1,20 +1,23 @@ -use crate::syntax::*; -use crate::workspace::*; -use futures_boxed::boxed; -use lsp_types::{LocationLink, Position, TextDocumentPositionParams}; +use crate::{ + feature::{FeatureProvider, FeatureRequest}, + protocol::{LocationLink, Position, TextDocumentPositionParams, Uri}, + syntax::{bibtex, SyntaxNode}, + workspace::DocumentContent, +}; +use async_trait::async_trait; -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] pub struct BibtexStringDefinitionProvider; +#[async_trait] 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); + async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output { + if let DocumentContent::Bibtex(tree) = &req.current().content { + if let Some(reference) = Self::find_reference(tree, req.params.position) { + return Self::find_definitions(&req.current().uri, tree, reference); } } Vec::new() @@ -22,31 +25,36 @@ impl FeatureProvider for BibtexStringDefinitionProvider { } impl BibtexStringDefinitionProvider { - fn find_reference(tree: &BibtexSyntaxTree, position: Position) -> Option<&BibtexToken> { - let mut nodes = tree.find(position); + fn find_reference(tree: &bibtex::Tree, pos: Position) -> Option<&bibtex::Token> { + let mut nodes = tree.find(pos); nodes.reverse(); - match (&nodes[0], &nodes.get(1)) { - (BibtexNode::Word(word), Some(BibtexNode::Field(_))) - | (BibtexNode::Word(word), Some(BibtexNode::Concat(_))) => Some(&word.token), + match ( + &tree.graph[nodes[0]], + nodes.get(1).map(|node| &tree.graph[*node]), + ) { + (bibtex::Node::Word(word), Some(bibtex::Node::Field(_))) + | (bibtex::Node::Word(word), Some(bibtex::Node::Concat(_))) => Some(&word.token), _ => None, } } fn find_definitions( uri: &Uri, - tree: &BibtexSyntaxTree, - reference: &BibtexToken, + tree: &bibtex::Tree, + reference: &bibtex::Token, ) -> 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(), - }); + for node in tree.children(tree.root) { + if let Some(string) = tree.as_string(node) { + 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(), + }); + } } } } @@ -57,76 +65,107 @@ impl BibtexStringDefinitionProvider { #[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) - }] - ); + use crate::{ + feature::FeatureTester, + protocol::{Range, RangeExt}, + }; + use indoc::indoc; + + #[tokio::test] + async fn empty_latex_document() { + let actual_links = FeatureTester::new() + .file("main.tex", "") + .main("main.tex") + .position(0, 0) + .test_position(BibtexStringDefinitionProvider) + .await; + + assert!(actual_links.is_empty()); + } + + #[tokio::test] + async fn empty_bibtex_document() { + let actual_links = FeatureTester::new() + .file("main.bib", "") + .main("main.bib") + .position(0, 0) + .test_position(BibtexStringDefinitionProvider) + .await; + + assert!(actual_links.is_empty()); + } + + #[tokio::test] + async fn simple() { + let actual_links = FeatureTester::new() + .file( + "main.bib", + indoc!( + r#" + @string{foo = {bar}} + @article{bar, author = foo} + "# + ), + ) + .main("main.bib") + .position(1, 24) + .test_position(BibtexStringDefinitionProvider) + .await; + + let expected_links = vec![LocationLink { + origin_selection_range: Some(Range::new_simple(1, 23, 1, 26)), + target_uri: FeatureTester::uri("main.bib").into(), + target_range: Range::new_simple(0, 0, 0, 20), + target_selection_range: Range::new_simple(0, 8, 0, 11), + }]; + + assert_eq!(actual_links, expected_links); } - #[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) - }] - ); + #[tokio::test] + async fn concat() { + let actual_links = FeatureTester::new() + .file( + "main.bib", + indoc!( + r#" + @string{foo = {bar}} + @article{bar, author = foo # "bar"} + "# + ), + ) + .main("main.bib") + .position(1, 24) + .test_position(BibtexStringDefinitionProvider) + .await; + + let expected_links = vec![LocationLink { + origin_selection_range: Some(Range::new_simple(1, 23, 1, 26)), + target_uri: FeatureTester::uri("main.bib").into(), + target_range: Range::new_simple(0, 0, 0, 20), + target_selection_range: Range::new_simple(0, 8, 0, 11), + }]; + + assert_eq!(actual_links, expected_links); } - #[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()); + #[tokio::test] + async fn field() { + let actual_links = FeatureTester::new() + .file( + "main.bib", + indoc!( + r#" + @string{foo = {bar}} + @article{bar, author = foo} + "# + ), + ) + .main("main.bib") + .position(1, 18) + .test_position(BibtexStringDefinitionProvider) + .await; + + assert!(actual_links.is_empty()); } } diff --git a/support/texlab/src/definition/latex_citation.rs b/support/texlab/src/definition/latex_citation.rs index daefa8b2ef..aa93e25fc0 100644 --- a/support/texlab/src/definition/latex_citation.rs +++ b/support/texlab/src/definition/latex_citation.rs @@ -1,22 +1,24 @@ -use crate::range::RangeExt; -use crate::syntax::*; -use crate::workspace::*; -use futures_boxed::boxed; -use lsp_types::{LocationLink, TextDocumentPositionParams}; +use crate::{ + feature::{FeatureProvider, FeatureRequest}, + protocol::{LocationLink, RangeExt, TextDocumentPositionParams}, + syntax::{latex, SyntaxNode}, + workspace::{Document, DocumentContent}, +}; +use async_trait::async_trait; #[derive(Debug, PartialEq, Eq, Clone)] pub struct LatexCitationDefinitionProvider; +#[async_trait] 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 { + async fn execute<'a>(&'a self, req: &'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); + if let Some(reference) = Self::find_reference(req) { + for doc in req.related() { + Self::find_definitions(&doc, &reference, &mut links); } } links @@ -24,96 +26,93 @@ impl FeatureProvider for LatexCitationDefinitionProvider { } 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() { + fn find_reference(req: &FeatureRequest<TextDocumentPositionParams>) -> Option<&latex::Token> { + req.current().content.as_latex().and_then(|table| { + table + .citations + .iter() + .flat_map(|citation| citation.keys(&table)) + .find(|key| key.range().contains(req.params.position)) + }) + } + + fn find_definitions(doc: &Document, reference: &latex::Token, links: &mut Vec<LocationLink>) { + if let DocumentContent::Bibtex(tree) = &doc.content { + for entry in tree + .children(tree.root) + .filter_map(|node| tree.as_entry(node)) + { if let Some(key) = &entry.key { if key.text() == reference.text() { - let link = LocationLink { + links.push(LocationLink { origin_selection_range: Some(reference.range()), - target_uri: document.uri.clone().into(), + target_uri: doc.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}; + use crate::{feature::FeatureTester, protocol::Range}; + use indoc::indoc; + + #[tokio::test] + async fn empty_latex_document() { + let actual_links = FeatureTester::new() + .file("main.tex", "") + .main("main.tex") + .position(0, 0) + .test_position(LatexCitationDefinitionProvider) + .await; - #[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) - }] - ); + assert!(actual_links.is_empty()); } - #[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()); + #[tokio::test] + async fn empty_bibtex_document() { + let actual_links = FeatureTester::new() + .file("main.bib", "") + .main("main.bib") + .position(0, 0) + .test_position(LatexCitationDefinitionProvider) + .await; + + assert!(actual_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()); + #[tokio::test] + async fn has_definition() { + let actual_links = FeatureTester::new() + .file( + "foo.tex", + indoc!( + r#" + \addbibresource{baz.bib} + \cite{foo} + "# + ), + ) + .file("bar.bib", r#"@article{foo, bar = {baz}}"#) + .file("baz.bib", r#"@article{foo, bar = {baz}}"#) + .main("foo.tex") + .position(1, 6) + .test_position(LatexCitationDefinitionProvider) + .await; + + let exepcted_links = vec![LocationLink { + origin_selection_range: Some(Range::new_simple(1, 6, 1, 9)), + target_uri: FeatureTester::uri("baz.bib").into(), + target_range: Range::new_simple(0, 0, 0, 26), + target_selection_range: Range::new_simple(0, 9, 0, 12), + }]; + + assert_eq!(actual_links, exepcted_links); } } diff --git a/support/texlab/src/definition/latex_cmd.rs b/support/texlab/src/definition/latex_cmd.rs new file mode 100644 index 0000000000..35fc42be4f --- /dev/null +++ b/support/texlab/src/definition/latex_cmd.rs @@ -0,0 +1,154 @@ +use crate::{ + feature::{FeatureProvider, FeatureRequest}, + protocol::{LocationLink, TextDocumentPositionParams}, + syntax::SyntaxNode, + workspace::DocumentContent, +}; +use async_trait::async_trait; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] +pub struct LatexCommandDefinitionProvider; + +#[async_trait] +impl FeatureProvider for LatexCommandDefinitionProvider { + type Params = TextDocumentPositionParams; + type Output = Vec<LocationLink>; + + async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output { + let mut links = Vec::new(); + if let DocumentContent::Latex(table) = &req.current().content { + if let Some(cmd) = table + .find(req.params.position) + .last() + .and_then(|node| table.as_command(*node)) + { + for doc in req.related() { + if let DocumentContent::Latex(table) = &doc.content { + table + .command_definitions + .iter() + .filter(|def| def.definition_name(&table) == cmd.name.text()) + .map(|def| { + let def_range = table[def.parent].range(); + LocationLink { + origin_selection_range: Some(cmd.range()), + target_uri: doc.uri.clone().into(), + target_range: def_range, + target_selection_range: def_range, + } + }) + .for_each(|link| links.push(link)); + + table + .math_operators + .iter() + .filter(|op| op.definition_name(&table) == cmd.name.text()) + .map(|op| { + let def_range = table[op.parent].range(); + LocationLink { + origin_selection_range: Some(cmd.range()), + target_uri: doc.uri.clone().into(), + target_range: def_range, + target_selection_range: def_range, + } + }) + .for_each(|link| links.push(link)); + } + } + } + } + links + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + feature::FeatureTester, + protocol::{Range, RangeExt}, + }; + use indoc::indoc; + + #[tokio::test] + async fn empty_latex_document() { + let actual_links = FeatureTester::new() + .file("main.tex", "") + .main("main.tex") + .position(0, 0) + .test_position(LatexCommandDefinitionProvider) + .await; + + assert!(actual_links.is_empty()); + } + + #[tokio::test] + async fn empty_bibtex_document() { + let actual_links = FeatureTester::new() + .file("main.bib", "") + .main("main.bib") + .position(0, 0) + .test_position(LatexCommandDefinitionProvider) + .await; + + assert!(actual_links.is_empty()); + } + + #[tokio::test] + async fn command_definition() { + let actual_links = FeatureTester::new() + .file( + "foo.tex", + indoc!( + r#" + \include{bar} + \foo + "# + ), + ) + .file("bar.tex", r#"\newcommand{\foo}{bar}"#) + .file("baz.tex", r#"\newcommand{\foo}{baz}"#) + .main("foo.tex") + .position(1, 3) + .test_position(LatexCommandDefinitionProvider) + .await; + + let expected_links = vec![LocationLink { + origin_selection_range: Some(Range::new_simple(1, 0, 1, 4)), + target_uri: FeatureTester::uri("bar.tex").into(), + target_range: Range::new_simple(0, 0, 0, 22), + target_selection_range: Range::new_simple(0, 0, 0, 22), + }]; + + assert_eq!(actual_links, expected_links); + } + + #[tokio::test] + async fn math_operator() { + let actual_links = FeatureTester::new() + .file( + "foo.tex", + indoc!( + r#" + \include{bar} + \foo + "# + ), + ) + .file("bar.tex", r#"\DeclareMathOperator{\foo}{bar}"#) + .file("baz.tex", r#"\DeclareMathOperator{\foo}{baz}"#) + .main("foo.tex") + .position(1, 3) + .test_position(LatexCommandDefinitionProvider) + .await; + + let expected_links = vec![LocationLink { + origin_selection_range: Some(Range::new_simple(1, 0, 1, 4)), + target_uri: FeatureTester::uri("bar.tex").into(), + target_range: Range::new_simple(0, 0, 0, 31), + target_selection_range: Range::new_simple(0, 0, 0, 31), + }]; + + assert_eq!(actual_links, expected_links); + } +} diff --git a/support/texlab/src/definition/latex_command.rs b/support/texlab/src/definition/latex_command.rs deleted file mode 100644 index 7f91412ef7..0000000000 --- a/support/texlab/src/definition/latex_command.rs +++ /dev/null @@ -1,106 +0,0 @@ -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 index 8d1a415749..5318982ccd 100644 --- a/support/texlab/src/definition/latex_label.rs +++ b/support/texlab/src/definition/latex_label.rs @@ -1,26 +1,40 @@ -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; +use crate::{ + feature::{DocumentView, FeatureProvider, FeatureRequest}, + outline::{Outline, OutlineContext, OutlineContextItem}, + protocol::{LocationLink, Options, RangeExt, TextDocumentPositionParams}, + symbol::build_section_tree, + syntax::{latex, LatexLabelKind, SyntaxNode}, + workspace::DocumentContent, +}; +use async_trait::async_trait; +use std::{path::Path, sync::Arc}; -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] pub struct LatexLabelDefinitionProvider; +#[async_trait] 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 { + async fn execute<'a>(&'a self, req: &'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); + if let Some(reference) = Self::find_reference(req) { + for doc in req.related() { + let snapshot = Arc::clone(&req.view.snapshot); + let view = DocumentView::analyze( + snapshot, + Arc::clone(&doc), + &req.options, + &req.current_dir, + ); + Self::find_definitions( + &view, + &req.options, + &req.current_dir, + &reference, + &mut links, + ); } } links @@ -28,18 +42,32 @@ impl FeatureProvider for LatexLabelDefinitionProvider { } impl LatexLabelDefinitionProvider { + fn find_reference(req: &FeatureRequest<TextDocumentPositionParams>) -> Option<&latex::Token> { + if let DocumentContent::Latex(table) = &req.current().content { + table + .labels + .iter() + .flat_map(|label| label.names(&table)) + .find(|label| label.range().contains(req.params.position)) + } else { + None + } + } + fn find_definitions( view: &DocumentView, - reference: &LatexToken, + options: &Options, + current_dir: &Path, + reference: &latex::Token, 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 let DocumentContent::Latex(table) = &view.current.content { + let outline = Outline::analyze(view, options, current_dir); + let section_tree = build_section_tree(view, table, options, current_dir); + for label in &table.labels { if label.kind == LatexLabelKind::Definition { - let context = OutlineContext::parse(view, label, &outline); - for name in label.names() { + let context = OutlineContext::parse(view, &outline, *label); + for name in label.names(&table) { if name.text() == reference.text() { let target_range = if let Some(OutlineContextItem::Section { .. }) = context.as_ref().map(|ctx| &ctx.item) @@ -53,9 +81,10 @@ impl LatexLabelDefinitionProvider { 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(), + target_uri: view.current.uri.clone().into(), + target_range: target_range + .unwrap_or_else(|| table[label.parent].range()), + target_selection_range: table[label.parent].range(), }); } } @@ -63,79 +92,64 @@ impl LatexLabelDefinitionProvider { } } } - - 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}; + use crate::{feature::FeatureTester, protocol::Range}; + use indoc::indoc; - #[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) - }] - ); + #[tokio::test] + async fn empty_latex_document() { + let actual_links = FeatureTester::new() + .file("main.tex", "") + .main("main.tex") + .position(0, 0) + .test_position(LatexLabelDefinitionProvider) + .await; + + assert!(actual_links.is_empty()); } - #[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()); + #[tokio::test] + async fn empty_bibtex_document() { + let actual_links = FeatureTester::new() + .file("main.bib", "") + .main("main.bib") + .position(0, 0) + .test_position(LatexLabelDefinitionProvider) + .await; + + assert!(actual_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()); + #[tokio::test] + async fn unknown_context() { + let actual_links = FeatureTester::new() + .file("foo.tex", r#"\label{foo}"#) + .file( + "bar.tex", + indoc!( + r#" + \begin{a}\begin{b}\label{foo}\end{b}\end{a} + \input{baz.tex} + "# + ), + ) + .file("baz.tex", r#"\ref{foo}"#) + .main("baz.tex") + .position(0, 5) + .test_position(LatexLabelDefinitionProvider) + .await; + + let expected_links = vec![LocationLink { + origin_selection_range: Some(Range::new_simple(0, 5, 0, 8)), + target_uri: FeatureTester::uri("bar.tex").into(), + target_range: Range::new_simple(0, 18, 0, 29), + target_selection_range: Range::new_simple(0, 18, 0, 29), + }]; + + assert_eq!(actual_links, expected_links); } } diff --git a/support/texlab/src/definition/mod.rs b/support/texlab/src/definition/mod.rs index 9c7ac0145a..08901454fe 100644 --- a/support/texlab/src/definition/mod.rs +++ b/support/texlab/src/definition/mod.rs @@ -1,16 +1,17 @@ mod bibtex_string; mod latex_citation; -mod latex_command; +mod latex_cmd; 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}; +use self::{ + bibtex_string::BibtexStringDefinitionProvider, latex_citation::LatexCitationDefinitionProvider, + latex_cmd::LatexCommandDefinitionProvider, latex_label::LatexLabelDefinitionProvider, +}; +use crate::{ + feature::{ConcatProvider, FeatureProvider, FeatureRequest}, + protocol::{LocationLink, TextDocumentPositionParams}, +}; +use async_trait::async_trait; pub struct DefinitionProvider { provider: ConcatProvider<TextDocumentPositionParams, LocationLink>, @@ -35,19 +36,12 @@ impl Default for DefinitionProvider { } } +#[async_trait] 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 + async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output { + self.provider.execute(req).await } } - -#[serde(untagged)] -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub enum DefinitionResponse { - Locations(Vec<Location>), - LocationLinks(Vec<LocationLink>), -} |