diff --git a/src/diagnostics/chktex.rs b/src/diagnostics/chktex.rs index 919f1279..59299674 100644 --- a/src/diagnostics/chktex.rs +++ b/src/diagnostics/chktex.rs @@ -39,7 +39,7 @@ pub fn collect_chktex_diagnostics( }) .or_else(|| { workspace - .find_parent(uri) + .parent(&document) .or(Some(document.clone())) .filter(|doc| doc.uri().scheme() == "file") .and_then(|doc| doc.uri().to_file_path().ok()) diff --git a/src/features/execute_command.rs b/src/features/execute_command.rs index ffbe155a..f22d2cbd 100644 --- a/src/features/execute_command.rs +++ b/src/features/execute_command.rs @@ -48,7 +48,8 @@ fn clean_output_files( normalize_uri(&mut params.uri); let uri = workspace - .find_parent(¶ms.uri) + .get(¶ms.uri) + .and_then(|child| workspace.parent(&child)) .map(|document| Arc::clone(document.uri())) .unwrap_or_else(|| Arc::new(params.uri)); diff --git a/src/syntax/latex/analysis.rs b/src/syntax/latex/analysis.rs index 02d9b315..e8f2de95 100644 --- a/src/syntax/latex/analysis.rs +++ b/src/syntax/latex/analysis.rs @@ -36,5 +36,12 @@ pub fn analyze(context: &mut LatexAnalyzerContext, root: &latex::SyntaxNode) { .or_else(|| analyze_theorem_definition(context, node.clone())) .or_else(|| analyze_graphics_path(context, node)); } + context.extras.has_document_environment = context.extras.environment_names.contains("document"); + context.extras.has_subfiles_package = context + .extras + .explicit_links + .iter() + .filter_map(ExplicitLink::as_component_name) + .any(|name| name == "subfiles.cls"); } diff --git a/src/syntax/latex/analysis/explicit_link.rs b/src/syntax/latex/analysis/explicit_link.rs index 77e2cfb6..df6c5316 100644 --- a/src/syntax/latex/analysis/explicit_link.rs +++ b/src/syntax/latex/analysis/explicit_link.rs @@ -41,6 +41,7 @@ pub fn analyze_include(context: &mut LatexAnalyzerContext, node: latex::SyntaxNo kind, stem: stem.into(), stem_range: latex::small_range(&path), + working_dir: None, targets, }); } @@ -65,6 +66,7 @@ pub fn analyze_import(context: &mut LatexAnalyzerContext, node: latex::SyntaxNod context.extras.explicit_links.push(ExplicitLink { stem: stem.into(), stem_range: latex::small_range(&file), + working_dir: Some(import.directory()?.key()?.to_string()), targets, kind: ExplicitLinkKind::Latex, }); diff --git a/src/syntax/latex/analysis/types.rs b/src/syntax/latex/analysis/types.rs index 7fb938e3..70a53ebc 100644 --- a/src/syntax/latex/analysis/types.rs +++ b/src/syntax/latex/analysis/types.rs @@ -19,13 +19,14 @@ pub struct LatexAnalyzerContext<'a> { pub struct Extras { pub implicit_links: ImplicitLinks, pub explicit_links: Vec, - pub has_document_environment: bool, pub command_names: FxHashSet, pub environment_names: FxHashSet, pub label_names: Vec, pub label_numbers_by_name: FxHashMap, pub theorem_environments: Vec, pub graphics_paths: FxHashSet, + pub has_document_environment: bool, + pub has_subfiles_package: bool, } #[derive(Debug, PartialEq, Eq, Clone, Default, Hash)] @@ -47,6 +48,7 @@ pub enum ExplicitLinkKind { pub struct ExplicitLink { pub stem: SmolStr, pub stem_range: TextRange, + pub working_dir: Option, pub targets: Vec>, pub kind: ExplicitLinkKind, } diff --git a/src/workspace.rs b/src/workspace.rs index 28a0747c..aaead9d1 100644 --- a/src/workspace.rs +++ b/src/workspace.rs @@ -1,4 +1,5 @@ use std::{ + borrow::Cow, fs::{self, FileType}, path::{Path, PathBuf}, sync::{Arc, Mutex}, @@ -6,13 +7,13 @@ use std::{ use anyhow::Result; use crossbeam_channel::Sender; +use itertools::Itertools; use lsp_types::Url; use notify::Watcher; -use petgraph::{graphmap::DiGraphMap, visit::Dfs}; use rustc_hash::{FxHashMap, FxHashSet}; use crate::{ - component_db::COMPONENT_DATABASE, syntax::latex::ExplicitLink, Document, DocumentLanguage, + component_db::COMPONENT_DATABASE, syntax::latex::ExplicitLinkKind, Document, DocumentLanguage, Environment, }; @@ -135,76 +136,17 @@ impl Workspace { } pub fn slice(&self, uri: &Url) -> Self { - let all_uris: Vec<_> = self.documents_by_uri.keys().cloned().collect(); - - all_uris - .iter() - .position(|u| u.as_ref() == uri) - .map(|start| { - let mut edges = Vec::new(); - for (i, uri) in all_uris.iter().enumerate() { - let document = self.documents_by_uri.get(uri); - if let Some(data) = document - .as_ref() - .and_then(|document| document.data().as_latex()) - { - let extras = &data.extras; - let mut all_targets = - vec![&extras.implicit_links.aux, &extras.implicit_links.log]; - for link in &extras.explicit_links { - all_targets.push(&link.targets); - } - - for targets in all_targets { - for target in targets { - if let Some(j) = all_uris.iter().position(|uri| uri == target) { - edges.push((i, j, ())); - - if target.as_str().ends_with(".tex") - || target.as_str().ends_with(".bib") - || target.as_str().ends_with(".rnw") - { - edges.push((j, i, ())); - } - - break; - } - } - } - } - } - - let mut slice = self.clone(); - slice.documents_by_uri = FxHashMap::default(); - let graph = DiGraphMap::from_edges(edges); - let mut dfs = Dfs::new(&graph, start); - while let Some(i) = dfs.next(&graph) { - let uri = &all_uris[i]; - let doc = self.documents_by_uri[uri].clone(); - slice.documents_by_uri.insert(Arc::clone(uri), doc); - } - - slice + let mut slice = self.clone(); + slice.documents_by_uri = self + .get(uri) + .map(|document| { + self.siblings(&document) + .into_iter() + .map(|document| (Arc::clone(document.uri()), document)) + .collect() }) - .unwrap_or_default() - } - - pub fn find_parent(&self, uri: &Url) -> Option { - self.slice(uri) - .documents_by_uri - .values() - .find(|document| { - document.data().as_latex().map_or(false, |data| { - data.extras.has_document_environment - && !data - .extras - .explicit_links - .iter() - .filter_map(ExplicitLink::as_component_name) - .any(|name| name == "subfiles.cls") - }) - }) - .cloned() + .unwrap_or_default(); + slice } fn expand_parent(&mut self, document: &Document) { @@ -216,7 +158,7 @@ impl Workspace { if document.uri().scheme() == "file" { if let Ok(mut path) = document.uri().to_file_path() { - while path.pop() && self.find_parent(document.uri()).is_none() { + while path.pop() && self.parent(&document).is_none() { std::fs::read_dir(&path) .into_iter() .flatten() @@ -265,4 +207,115 @@ impl Workspace { } } } + + pub fn project_roots<'a>(&'a self) -> impl Iterator + 'a { + self.iter().filter(|root| { + root.data().as_latex().map_or(false, |data| { + data.extras.has_document_environment && !data.extras.has_subfiles_package + }) + }) + } + + pub fn parent(&self, child: &Document) -> Option { + self.project_roots().find(|root| { + self.dependencies(root) + .into_iter() + .any(|doc| doc.uri() == child.uri()) + }) + } + + pub fn dependencies(&self, root: &Document) -> Vec { + fn go<'a>( + workspace: &Workspace, + root: &Document, + working_dir: &Url, + visited: &mut FxHashSet>, + results: &mut Vec, + ) { + if !visited.insert(Arc::clone(root.uri())) { + return; + } + + results.push(root.clone()); + if let Some(data) = root.data().as_latex() { + for link in &data.extras.explicit_links { + if link + .as_component_name() + .and_then(|name| COMPONENT_DATABASE.find(&name)) + .is_some() + { + continue; + } + + let suffixes: &[&str] = match link.kind { + ExplicitLinkKind::Package => &[".sty"], + ExplicitLinkKind::Class => &[".cls"], + ExplicitLinkKind::Latex => &["", ".tex"], + ExplicitLinkKind::Bibtex => &["", ".bib"], + }; + + let working_dir = link + .working_dir + .as_ref() + .and_then(|path| working_dir.join(path).ok()) + .map_or(Cow::Borrowed(working_dir), Cow::Owned); + + for suffix in suffixes { + let file_name = format!("{}{}", link.stem, suffix); + + if let Some(child) = working_dir + .join(&file_name) + .ok() + .and_then(|uri| workspace.get(&uri)) + { + go(workspace, &child, &working_dir, visited, results); + break; + } + } + } + } + + for extension in &["aux", "log"] { + if let Some(child) = change_extension(root.uri(), extension) + .and_then(|file_name| working_dir.join(&file_name).ok()) + .and_then(|uri| workspace.get(&uri)) + { + go(workspace, &child, &working_dir, visited, results); + } + } + } + + let mut results = Vec::new(); + let working_dir = self + .environment + .options + .root_directory + .as_deref() + .and_then(|path| path.to_str()) + .and_then(|path| root.uri().join(path).map(Arc::new).ok()) + .unwrap_or_else(|| Arc::clone(root.uri())); + + let mut visited = FxHashSet::default(); + go(self, root, &working_dir, &mut visited, &mut results); + results + } + + pub fn siblings(&self, child: &Document) -> Vec { + self.iter() + .map(|root| self.dependencies(&root)) + .filter(|project| project.iter().any(|doc| doc.uri() == child.uri())) + .flatten() + .unique_by(|doc| Arc::clone(doc.uri())) + .collect() + } +} + +fn change_extension(uri: &Url, extension: &str) -> Option { + let file_name = uri.path_segments()?.last()?; + let file_stem = file_name + .rfind('.') + .map(|i| &file_name[..i]) + .unwrap_or(file_name); + + Some(format!("{}.{}", file_stem, extension)) }