diff --git a/crates/texlab_syntax/src/latex/analysis.rs b/crates/texlab_syntax/src/latex/analysis.rs index 3a8f95b03..900a8be53 100644 --- a/crates/texlab_syntax/src/latex/analysis.rs +++ b/crates/texlab_syntax/src/latex/analysis.rs @@ -390,6 +390,14 @@ pub struct Import { } impl Import { + pub fn dir<'a>(&self, tree: &'a Tree) -> &'a Token { + tree.extract_word(self.parent, GroupKind::Group, 0).unwrap() + } + + pub fn file<'a>(&self, tree: &'a Tree) -> &'a Token { + tree.extract_word(self.parent, GroupKind::Group, 1).unwrap() + } + fn parse(ctx: SymbolContext) -> Vec { ctx.commands .iter() diff --git a/src/link/latex_import.rs b/src/link/latex_import.rs new file mode 100644 index 000000000..51bf88337 --- /dev/null +++ b/src/link/latex_import.rs @@ -0,0 +1,94 @@ +use futures_boxed::boxed; +use texlab_feature::{DocumentContent, FeatureProvider, FeatureRequest}; +use texlab_protocol::{DocumentLink, DocumentLinkParams}; +use texlab_syntax::{latex, SyntaxNode}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] +pub struct LatexImportLinkProvider; + +impl FeatureProvider for LatexImportLinkProvider { + type Params = DocumentLinkParams; + type Output = Vec; + + #[boxed] + async fn execute<'a>(&'a self, req: &'a FeatureRequest) -> Self::Output { + if let DocumentContent::Latex(table) = &req.current().content { + table + .imports + .iter() + .flat_map(|import| Self::resolve(req, table, import)) + .collect() + } else { + Vec::new() + } + } +} + +impl LatexImportLinkProvider { + fn resolve( + req: &FeatureRequest, + table: &latex::SymbolTable, + import: &latex::Import, + ) -> Vec { + let mut links = Vec::new(); + let file = import.file(&table); + for target in &import.targets { + if let Some(link) = req.snapshot().find(target).map(|doc| DocumentLink { + range: file.range(), + target: doc.uri.clone().into(), + tooltip: None, + }) { + links.push(link); + break; + } + } + links + } +} + +#[cfg(test)] +mod tests { + use super::*; + use texlab_feature::FeatureTester; + use texlab_protocol::{Range, RangeExt}; + + #[tokio::test] + async fn empty_latex_document_command() { + let actual_links = FeatureTester::new() + .file("main.tex", "") + .main("main.tex") + .test_link(LatexImportLinkProvider) + .await; + + assert!(actual_links.is_empty()); + } + + #[tokio::test] + async fn empty_bibtex_document_command() { + let actual_links = FeatureTester::new() + .file("main.bib", "") + .main("main.bib") + .test_link(LatexImportLinkProvider) + .await; + + assert!(actual_links.is_empty()); + } + + #[tokio::test] + async fn has_links() { + let actual_links = FeatureTester::new() + .file("foo.tex", r#"\import{bar/}{baz}"#) + .file("bar/baz.tex", r#""#) + .main("foo.tex") + .test_link(LatexImportLinkProvider) + .await; + + let expected_links = vec![DocumentLink { + range: Range::new_simple(0, 14, 0, 17), + target: FeatureTester::uri("bar/baz.tex").into(), + tooltip: None, + }]; + + assert_eq!(actual_links, expected_links); + } +} diff --git a/src/link/latex_include.rs b/src/link/latex_include.rs index f36c4613f..e49d15e27 100644 --- a/src/link/latex_include.rs +++ b/src/link/latex_include.rs @@ -13,11 +13,11 @@ impl FeatureProvider for LatexIncludeLinkProvider { #[boxed] async fn execute<'a>(&'a self, req: &'a FeatureRequest) -> Self::Output { if let DocumentContent::Latex(table) = &req.current().content { - return table + table .includes .iter() .flat_map(|include| Self::resolve(req, table, include)) - .collect(); + .collect() } else { Vec::new() } @@ -55,28 +55,21 @@ mod tests { use texlab_protocol::{Range, RangeExt}; #[tokio::test] - async fn has_links() { + async fn empty_latex_document_command() { let actual_links = FeatureTester::new() - .file("foo.tex", r#"\input{bar.tex}"#) - .file("bar.tex", r#""#) - .main("foo.tex") + .file("main.tex", "") + .main("main.tex") .test_link(LatexIncludeLinkProvider) .await; - let expected_links = vec![DocumentLink { - range: Range::new_simple(0, 7, 0, 14), - target: FeatureTester::uri("bar.tex").into(), - tooltip: None, - }]; - - assert_eq!(actual_links, expected_links); + assert!(actual_links.is_empty()); } #[tokio::test] - async fn no_links_latex() { + async fn empty_bibtex_document_command() { let actual_links = FeatureTester::new() - .file("foo.tex", r#""#) - .main("foo.tex") + .file("main.bib", "") + .main("main.bib") .test_link(LatexIncludeLinkProvider) .await; @@ -84,13 +77,20 @@ mod tests { } #[tokio::test] - async fn no_links_bibtex() { + async fn has_links() { let actual_links = FeatureTester::new() - .file("foo.bib", r#""#) - .main("foo.bib") + .file("foo.tex", r#"\input{bar.tex}"#) + .file("bar.tex", r#""#) + .main("foo.tex") .test_link(LatexIncludeLinkProvider) .await; - assert!(actual_links.is_empty()); + let expected_links = vec![DocumentLink { + range: Range::new_simple(0, 7, 0, 14), + target: FeatureTester::uri("bar.tex").into(), + tooltip: None, + }]; + + assert_eq!(actual_links, expected_links); } } diff --git a/src/link/mod.rs b/src/link/mod.rs index 40554837b..afd897259 100644 --- a/src/link/mod.rs +++ b/src/link/mod.rs @@ -1,6 +1,7 @@ +mod latex_import; mod latex_include; -use self::latex_include::LatexIncludeLinkProvider; +use self::{latex_import::LatexImportLinkProvider, latex_include::LatexIncludeLinkProvider}; use futures_boxed::boxed; use texlab_feature::{ConcatProvider, FeatureProvider, FeatureRequest}; use texlab_protocol::{DocumentLink, DocumentLinkParams}; @@ -12,7 +13,10 @@ pub struct LinkProvider { impl LinkProvider { pub fn new() -> Self { Self { - provider: ConcatProvider::new(vec![Box::new(LatexIncludeLinkProvider)]), + provider: ConcatProvider::new(vec![ + Box::new(LatexImportLinkProvider), + Box::new(LatexIncludeLinkProvider), + ]), } } }