From 6facbe3e2795e196932edba5ecae8c2295b69300 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20F=C3=B6rster?= Date: Thu, 16 Apr 2020 14:51:43 +0200 Subject: [PATCH] Add support for imports in workspace detection --- crates/texlab_feature/src/workspace.rs | 67 ++++++++++++++++++++++ crates/texlab_syntax/src/latex/analysis.rs | 67 ++++++++++++++++------ 2 files changed, 118 insertions(+), 16 deletions(-) diff --git a/crates/texlab_feature/src/workspace.rs b/crates/texlab_feature/src/workspace.rs index d42009bc7..2c7ce2706 100644 --- a/crates/texlab_feature/src/workspace.rs +++ b/crates/texlab_feature/src/workspace.rs @@ -154,6 +154,16 @@ impl Snapshot { graph.add_edge(indices_by_uri[&parent.uri], indices_by_uri[&child.uri], ()); }); + table + .imports + .iter() + .flat_map(|import| import.targets.iter()) + .find_map(|target| self.find(target)) + .into_iter() + .for_each(|child| { + graph.add_edge(indices_by_uri[&parent.uri], indices_by_uri[&child.uri], ()); + }); + self.resolve_aux_targets(&parent.uri, options, current_dir, "aux") .into_iter() .flatten() @@ -204,6 +214,18 @@ impl Snapshot { .flatten() .for_each(|target| unknown_targets.push(target.clone())); + table + .imports + .iter() + .filter(|import| { + import + .targets + .iter() + .all(|target| self.find(target).is_none()) + }) + .flat_map(|import| import.targets.iter()) + .for_each(|target| unknown_targets.push(target.clone())); + self.resolve_aux_targets(&parent.uri, options, current_dir, "aux") .into_iter() .filter(|targets| targets.iter().all(|target| self.find(target).is_none())) @@ -785,6 +807,27 @@ mod tests { assert_eq!(actual_uris, vec![uri1, uri2]); } + #[test] + fn relations_import() { + let uri1 = Uri::parse("http://www.example.com/foo.tex").unwrap(); + let uri2 = Uri::parse("http://www.example.com/bar/baz.tex").unwrap(); + let uri3 = Uri::parse("http://www.example.com/bar/qux/foo-bar.tex").unwrap(); + let mut snapshot = Snapshot::new(); + snapshot.0 = vec![ + create_simple_document(&uri1, Language::Latex, r#"\import{bar/}{baz.tex}"#), + create_simple_document(&uri2, Language::Latex, r#"\subimport{qux/}{foo-bar}"#), + create_simple_document(&uri3, Language::Latex, r#""#), + ]; + + let actual_uris: Vec<_> = snapshot + .relations(&uri1, &Options::default(), &env::current_dir().unwrap()) + .into_iter() + .map(|doc| doc.uri.clone()) + .collect(); + + assert_eq!(actual_uris, vec![uri1, uri2, uri3]); + } + #[test] fn parent() { let uri1 = Uri::parse("http://www.example.com/foo.tex").unwrap(); @@ -894,4 +937,28 @@ mod tests { vec!["http://www.example.com/bar/baz.tex"] ); } + + #[test] + fn expand_import() { + let uri1 = Uri::parse("http://www.example.com/qux/foo.tex").unwrap(); + let uri2 = Uri::parse("http://www.example.com/qux/baz/bar.tex").unwrap(); + let mut snapshot = Snapshot::new(); + snapshot.0 = vec![ + create_simple_document( + &uri1, + Language::Latex, + r#"\import{.}{foo}\import{baz/}{bar}\import{baz/foo-bar/}{qux}"#, + ), + create_simple_document(&uri2, Language::Latex, r#""#), + ]; + let expansion = snapshot.expand(&Options::default(), &env::current_dir().unwrap()); + assert_eq!( + expansion + .iter() + .map(|uri| uri.as_str()) + .filter(|uri| uri.ends_with(".tex")) + .collect_vec(), + vec!["http://www.example.com/qux/baz/foo-bar/qux.tex"] + ) + } } diff --git a/crates/texlab_syntax/src/latex/analysis.rs b/crates/texlab_syntax/src/latex/analysis.rs index f00daf3eb..3a8f95b03 100644 --- a/crates/texlab_syntax/src/latex/analysis.rs +++ b/crates/texlab_syntax/src/latex/analysis.rs @@ -21,6 +21,7 @@ pub struct SymbolTable { pub environments: Vec, pub is_standalone: bool, pub includes: Vec, + pub imports: Vec, pub components: Vec, pub citations: Vec, pub command_definitions: Vec, @@ -58,6 +59,7 @@ impl SymbolTable { let mut environments = None; let mut includes = None; + let mut imports = None; let mut citations = None; let mut command_definitions = None; let mut glossary_entries = None; @@ -74,6 +76,7 @@ impl SymbolTable { rayon::scope(|s| { s.spawn(|_| environments = Some(Environment::parse(ctx))); s.spawn(|_| includes = Some(Include::parse(ctx))); + s.spawn(|_| imports = Some(Import::parse(ctx))); s.spawn(|_| citations = Some(Citation::parse(ctx))); s.spawn(|_| command_definitions = Some(CommandDefinition::parse(ctx))); s.spawn(|_| glossary_entries = Some(GlossaryEntry::parse(ctx))); @@ -107,6 +110,7 @@ impl SymbolTable { environments: environments.unwrap(), is_standalone, includes: includes.unwrap(), + imports: imports.unwrap(), components, citations: citations.unwrap(), command_definitions: command_definitions.unwrap(), @@ -320,7 +324,7 @@ impl Include { .extract_comma_separated_words(parent, GroupKind::Group, desc.index)?; for path in paths { let mut targets = Vec::new(); - let base_url = Self::base_url(ctx)?; + let base_url = base_url(ctx)?; targets.push(base_url.join(path.text()).ok()?.into()); if let Some(extensions) = desc.kind.extensions() { @@ -346,21 +350,6 @@ impl Include { Some(include) } - fn base_url(ctx: SymbolContext) -> Option { - if let Some(root_directory) = ctx - .options - .latex - .as_ref() - .and_then(|opts| opts.root_directory.as_ref()) - { - let file_name = ctx.uri.path_segments()?.last()?; - let path = ctx.current_dir.join(root_directory).join(file_name); - Uri::from_file_path(path).ok() - } else { - Some(ctx.uri.clone()) - } - } - fn resolve_distro_file( ctx: SymbolContext, desc: &LatexIncludeCommand, @@ -379,6 +368,52 @@ impl Include { } } +fn base_url(ctx: SymbolContext) -> Option { + if let Some(root_directory) = ctx + .options + .latex + .as_ref() + .and_then(|opts| opts.root_directory.as_ref()) + { + let file_name = ctx.uri.path_segments()?.last()?; + let path = ctx.current_dir.join(root_directory).join(file_name); + Uri::from_file_path(path).ok() + } else { + Some(ctx.uri.clone()) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Import { + pub parent: AstNodeIndex, + pub targets: Vec, +} + +impl Import { + fn parse(ctx: SymbolContext) -> Vec { + ctx.commands + .iter() + .filter_map(|parent| Self::parse_single(ctx, *parent)) + .collect() + } + + fn parse_single(ctx: SymbolContext, parent: AstNodeIndex) -> Option { + let cmd = ctx.tree.as_command(parent)?; + if cmd.name.text() != "\\import" && cmd.name.text() != "\\subimport" { + return None; + } + + let dir = ctx.tree.extract_word(parent, GroupKind::Group, 0)?; + let file = ctx.tree.extract_word(parent, GroupKind::Group, 1)?; + + let mut targets = Vec::new(); + let base_url = base_url(ctx)?.join(dir.text()).ok()?; + targets.push(base_url.join(file.text()).ok()?.into()); + targets.push(base_url.join(&format!("{}.tex", file.text())).ok()?.into()); + Some(Self { parent, targets }) + } +} + #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct Citation { parent: AstNodeIndex,