diff options
Diffstat (limited to 'support/texlab/src/workspace')
-rw-r--r-- | support/texlab/src/workspace/api.rs | 86 | ||||
-rw-r--r-- | support/texlab/src/workspace/children_expand.rs | 92 | ||||
-rw-r--r-- | support/texlab/src/workspace/document.rs | 127 | ||||
-rw-r--r-- | support/texlab/src/workspace/parent_expand.rs | 123 | ||||
-rw-r--r-- | support/texlab/src/workspace/storage.rs | 136 | ||||
-rw-r--r-- | support/texlab/src/workspace/watch.rs | 107 |
6 files changed, 671 insertions, 0 deletions
diff --git a/support/texlab/src/workspace/api.rs b/support/texlab/src/workspace/api.rs new file mode 100644 index 0000000000..38fa0ce530 --- /dev/null +++ b/support/texlab/src/workspace/api.rs @@ -0,0 +1,86 @@ +use std::{fs, path::PathBuf, sync::Arc}; + +use anyhow::Result; + +use crate::{DocumentLanguage, Uri}; + +use super::Document; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)] +pub enum WorkspaceSource { + Client, + Server, +} + +#[derive(Debug, Clone)] +pub struct WorkspaceSubset { + pub documents: Vec<Arc<Document>>, +} + +pub type OpenHandler = Arc<dyn Fn(Arc<dyn Workspace>, Arc<Document>) + Send + Sync + 'static>; + +pub trait Workspace: Send + Sync { + fn open( + &self, + uri: Arc<Uri>, + text: String, + language: DocumentLanguage, + source: WorkspaceSource, + ) -> Arc<Document>; + + fn register_open_handler(&self, handler: OpenHandler); + + fn reload(&self, path: PathBuf) -> Result<Option<Arc<Document>>> { + let uri = Arc::new(Uri::from_file_path(path.clone()).unwrap()); + + if self.is_open(&uri) { + return Ok(self.get(&uri)); + } + + let data = fs::read(&path)?; + let text = String::from_utf8_lossy(&data).into_owned(); + if let Some(language) = DocumentLanguage::by_path(&path) { + Ok(Some(self.open( + uri, + text, + language, + WorkspaceSource::Server, + ))) + } else { + Ok(None) + } + } + + fn load(&self, path: PathBuf) -> Result<Option<Arc<Document>>> { + let uri = Arc::new(Uri::from_file_path(path.clone()).unwrap()); + + if let Some(document) = self.get(&uri) { + return Ok(Some(document)); + } + + let data = fs::read(&path)?; + let text = String::from_utf8_lossy(&data).into_owned(); + if let Some(language) = DocumentLanguage::by_path(&path) { + Ok(Some(self.open( + uri, + text, + language, + WorkspaceSource::Server, + ))) + } else { + Ok(None) + } + } + + fn documents(&self) -> Vec<Arc<Document>>; + + fn has(&self, uri: &Uri) -> bool; + + fn get(&self, uri: &Uri) -> Option<Arc<Document>>; + + fn close(&self, uri: &Uri); + + fn is_open(&self, uri: &Uri) -> bool; + + fn subset(&self, uri: Arc<Uri>) -> Option<WorkspaceSubset>; +} diff --git a/support/texlab/src/workspace/children_expand.rs b/support/texlab/src/workspace/children_expand.rs new file mode 100644 index 0000000000..006e7b17e2 --- /dev/null +++ b/support/texlab/src/workspace/children_expand.rs @@ -0,0 +1,92 @@ +use std::sync::Arc; + +use rayon::iter::{IntoParallelIterator, ParallelIterator}; + +use crate::{ + component_db::COMPONENT_DATABASE, Document, DocumentLanguage, OpenHandler, Uri, Workspace, + WorkspaceSource, WorkspaceSubset, +}; + +pub struct ChildrenExpander<W> { + workspace: Arc<W>, +} + +impl<W> ChildrenExpander<W> +where + W: Workspace + Send + Sync + 'static, +{ + pub fn new(workspace: Arc<W>) -> Self { + workspace.register_open_handler(Arc::new(move |workspace, document| { + Self::expand(workspace.as_ref(), document.as_ref()); + })); + Self { workspace } + } + + fn expand(workspace: &dyn Workspace, document: &Document) { + if let Some(data) = 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 { + if link + .as_component_name() + .and_then(|name| COMPONENT_DATABASE.find(&name)) + .is_none() + { + all_targets.push(&link.targets); + } + } + + all_targets.into_par_iter().for_each(|targets| { + for path in targets + .iter() + .filter(|uri| uri.scheme() == "file" && uri.fragment().is_none()) + .filter_map(|uri| uri.to_file_path().ok()) + { + if workspace.load(path).is_ok() { + break; + } + } + }); + } + } +} + +impl<W: Workspace> Workspace for ChildrenExpander<W> { + fn open( + &self, + uri: Arc<Uri>, + text: String, + language: DocumentLanguage, + source: WorkspaceSource, + ) -> Arc<Document> { + self.workspace.open(uri, text, language, source) + } + + fn register_open_handler(&self, handler: OpenHandler) { + self.workspace.register_open_handler(handler) + } + + fn documents(&self) -> Vec<Arc<Document>> { + self.workspace.documents() + } + + fn has(&self, uri: &Uri) -> bool { + self.workspace.has(uri) + } + + fn get(&self, uri: &Uri) -> Option<Arc<Document>> { + self.workspace.get(uri) + } + + fn close(&self, uri: &Uri) { + self.workspace.close(uri) + } + + fn is_open(&self, uri: &Uri) -> bool { + self.workspace.is_open(uri) + } + + fn subset(&self, uri: Arc<Uri>) -> Option<WorkspaceSubset> { + self.workspace.subset(uri) + } +} diff --git a/support/texlab/src/workspace/document.rs b/support/texlab/src/workspace/document.rs new file mode 100644 index 0000000000..a98be226c1 --- /dev/null +++ b/support/texlab/src/workspace/document.rs @@ -0,0 +1,127 @@ +use std::{fmt, sync::Arc}; + +use derive_more::From; + +use crate::{ + line_index::LineIndex, + syntax::{ + bibtex, build_log, + latex::{self, LatexAnalyzerContext}, + }, + DocumentLanguage, ServerContext, Uri, +}; + +#[derive(Debug, Clone)] +pub struct LatexDocumentData { + pub root: latex::SyntaxNode, + pub extras: latex::Extras, +} + +#[derive(Debug, Clone)] +pub struct BibtexDocumentData { + pub root: bibtex::SyntaxNode, +} + +#[derive(Debug, Clone, From)] +pub enum DocumentData { + Latex(LatexDocumentData), + Bibtex(BibtexDocumentData), + BuildLog(build_log::Parse), +} + +impl DocumentData { + pub fn language(&self) -> DocumentLanguage { + match self { + Self::Latex(_) => DocumentLanguage::Latex, + Self::Bibtex(_) => DocumentLanguage::Bibtex, + Self::BuildLog(_) => DocumentLanguage::BuildLog, + } + } + + pub fn as_latex(&self) -> Option<&LatexDocumentData> { + if let Self::Latex(data) = self { + Some(data) + } else { + None + } + } + + pub fn as_bibtex(&self) -> Option<&BibtexDocumentData> { + if let Self::Bibtex(data) = self { + Some(data) + } else { + None + } + } + + pub fn as_build_log(&self) -> Option<&build_log::Parse> { + if let Self::BuildLog(v) = self { + Some(v) + } else { + None + } + } +} + +#[derive(Clone)] +pub struct Document { + pub uri: Arc<Uri>, + pub text: String, + pub line_index: LineIndex, + pub data: DocumentData, +} + +impl fmt::Debug for Document { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.uri) + } +} + +impl Document { + pub fn parse( + context: Arc<ServerContext>, + uri: Arc<Uri>, + text: String, + language: DocumentLanguage, + ) -> Self { + let line_index = LineIndex::new(&text); + let data = match language { + DocumentLanguage::Latex => { + let root = latex::parse(&text).root; + + let base_uri = match &context.options.read().unwrap().root_directory { + Some(root_dir) => Uri::from_directory_path(root_dir) + .map(Arc::new) + .unwrap_or_else(|()| Arc::clone(&uri)), + None => Arc::clone(&uri), + }; + let mut context = LatexAnalyzerContext { + inner: context, + extras: latex::Extras::default(), + document_uri: Arc::clone(&uri), + base_uri, + }; + latex::analyze(&mut context, &root); + let extras = context.extras; + + LatexDocumentData { root, extras }.into() + } + DocumentLanguage::Bibtex => { + let root = bibtex::parse(&text).root; + BibtexDocumentData { root }.into() + } + DocumentLanguage::BuildLog => DocumentData::BuildLog(build_log::parse(&text)), + }; + + Self { + uri, + text, + line_index, + data, + } + } + + pub fn language(&self) -> DocumentLanguage { + self.data.language() + } +} diff --git a/support/texlab/src/workspace/parent_expand.rs b/support/texlab/src/workspace/parent_expand.rs new file mode 100644 index 0000000000..33b0ca3c5e --- /dev/null +++ b/support/texlab/src/workspace/parent_expand.rs @@ -0,0 +1,123 @@ +use std::{fs, sync::Arc}; + +use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use rustc_hash::FxHashSet; + +use crate::{ + Document, DocumentLanguage, OpenHandler, Uri, Workspace, WorkspaceSource, WorkspaceSubset, +}; + +pub struct ParentExpander<W> { + workspace: W, +} + +impl<W: Workspace> ParentExpander<W> { + pub fn new(workspace: W) -> Self { + Self { workspace } + } +} + +impl<W> Workspace for ParentExpander<W> +where + W: Workspace + Send + Sync + 'static, +{ + fn open( + &self, + uri: Arc<Uri>, + text: String, + language: DocumentLanguage, + source: WorkspaceSource, + ) -> Arc<Document> { + let document = self + .workspace + .open(Arc::clone(&uri), text, language, source); + + let all_current_paths = self + .workspace + .documents() + .into_iter() + .filter_map(|doc| doc.uri.to_file_path().ok()) + .collect::<FxHashSet<_>>(); + + if uri.scheme() == "file" { + if let Ok(mut path) = uri.to_file_path() { + while path.pop() && !self.has_parent(Arc::clone(&uri)).unwrap_or(false) { + let mut files = Vec::new(); + fs::read_dir(&path) + .into_iter() + .flatten() + .filter_map(|entry| entry.ok()) + .filter(|entry| entry.file_type().ok().filter(|ty| ty.is_file()).is_some()) + .map(|entry| entry.path()) + .filter(|path| { + matches!( + DocumentLanguage::by_path(&path), + Some(DocumentLanguage::Latex) + ) + }) + .filter(|path| !all_current_paths.contains(path)) + .for_each(|path| { + files.push(path); + }); + files.into_par_iter().for_each(|path| { + let _ = self.workspace.load(path); + }); + } + } + } + + document + } + + fn register_open_handler(&self, handler: OpenHandler) { + self.workspace.register_open_handler(handler) + } + + fn documents(&self) -> Vec<Arc<Document>> { + self.workspace.documents() + } + + fn has(&self, uri: &Uri) -> bool { + self.workspace.has(uri) + } + + fn get(&self, uri: &Uri) -> Option<Arc<Document>> { + self.workspace.get(uri) + } + + fn close(&self, uri: &Uri) { + self.workspace.close(uri) + } + + fn is_open(&self, uri: &Uri) -> bool { + self.workspace.is_open(uri) + } + + fn subset(&self, uri: Arc<Uri>) -> Option<WorkspaceSubset> { + self.workspace.subset(uri) + } +} + +impl<W> ParentExpander<W> +where + W: Workspace + Send + Sync + 'static, +{ + fn has_parent(&self, uri: Arc<Uri>) -> Option<bool> { + let subset = self.subset(Arc::clone(&uri))?; + Some(subset.documents.iter().any(|document| { + document + .data + .as_latex() + .map(|data| { + data.extras.has_document_environment + && !data + .extras + .explicit_links + .iter() + .filter_map(|link| link.as_component_name()) + .any(|name| name == "subfiles.cls") + }) + .unwrap_or(false) + })) + } +} diff --git a/support/texlab/src/workspace/storage.rs b/support/texlab/src/workspace/storage.rs new file mode 100644 index 0000000000..964535a718 --- /dev/null +++ b/support/texlab/src/workspace/storage.rs @@ -0,0 +1,136 @@ +use std::sync::{Arc, Mutex}; + +use petgraph::{graphmap::UnGraphMap, visit::Dfs}; +use rustc_hash::{FxHashMap, FxHashSet}; + +use crate::{ + Document, DocumentLanguage, OpenHandler, ServerContext, Uri, Workspace, WorkspaceSource, + WorkspaceSubset, +}; + +#[derive(Clone)] +pub struct Storage { + context: Arc<ServerContext>, + documents_by_uri: Arc<Mutex<FxHashMap<Arc<Uri>, Arc<Document>>>>, + opened_documents: Arc<Mutex<FxHashSet<Arc<Uri>>>>, + open_handlers: Arc<Mutex<Vec<OpenHandler>>>, +} + +impl Storage { + pub fn new(context: Arc<ServerContext>) -> Self { + Self { + context, + documents_by_uri: Arc::default(), + opened_documents: Arc::default(), + open_handlers: Arc::default(), + } + } +} + +impl Workspace for Storage { + fn open( + &self, + uri: Arc<Uri>, + text: String, + language: DocumentLanguage, + source: WorkspaceSource, + ) -> Arc<Document> { + log::debug!("(Re)Loading document: {}", uri); + let document = Arc::new(Document::parse( + Arc::clone(&self.context), + Arc::clone(&uri), + text, + language, + )); + { + self.documents_by_uri + .lock() + .unwrap() + .insert(Arc::clone(&uri), Arc::clone(&document)); + } + + if source == WorkspaceSource::Client { + self.opened_documents.lock().unwrap().insert(uri); + } + + let handlers = { self.open_handlers.lock().unwrap().clone() }; + for handler in handlers { + handler(Arc::new(self.clone()), Arc::clone(&document)); + } + + document + } + + fn register_open_handler(&self, handler: OpenHandler) { + self.open_handlers.lock().unwrap().push(handler); + } + + fn documents(&self) -> Vec<Arc<Document>> { + self.documents_by_uri + .lock() + .unwrap() + .values() + .cloned() + .collect() + } + + fn has(&self, uri: &Uri) -> bool { + self.documents_by_uri.lock().unwrap().contains_key(uri) + } + + fn get(&self, uri: &Uri) -> Option<Arc<Document>> { + self.documents_by_uri.lock().unwrap().get(uri).cloned() + } + + fn close(&self, uri: &Uri) { + self.opened_documents.lock().unwrap().remove(uri); + } + + fn is_open(&self, uri: &Uri) -> bool { + self.opened_documents.lock().unwrap().contains(uri) + } + + fn subset(&self, uri: Arc<Uri>) -> Option<WorkspaceSubset> { + let all_current_uris: Vec<Arc<Uri>> = self + .documents_by_uri + .lock() + .unwrap() + .keys() + .cloned() + .collect(); + + let mut edges = Vec::new(); + for (i, uri) in all_current_uris.iter().enumerate() { + let document = self.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_current_uris.iter().position(|uri| uri == target) { + edges.push((i, j, ())); + break; + } + } + } + } + } + + let graph = UnGraphMap::from_edges(edges); + let start = all_current_uris.iter().position(|u| *u == uri)?; + let mut dfs = Dfs::new(&graph, start); + let mut documents = Vec::new(); + while let Some(i) = dfs.next(&graph) { + documents.push(self.get(&all_current_uris[i])?); + } + + Some(WorkspaceSubset { documents }) + } +} diff --git a/support/texlab/src/workspace/watch.rs b/support/texlab/src/workspace/watch.rs new file mode 100644 index 0000000000..655872f603 --- /dev/null +++ b/support/texlab/src/workspace/watch.rs @@ -0,0 +1,107 @@ +use std::{ + path::PathBuf, + sync::{Arc, Mutex}, +}; + +use anyhow::Result; +use log::warn; +use notify::{RecommendedWatcher, RecursiveMode, Watcher}; +use rustc_hash::FxHashSet; + +use crate::{ + Document, DocumentLanguage, OpenHandler, Uri, Workspace, WorkspaceSource, WorkspaceSubset, +}; + +pub struct DocumentWatcher<W> { + workspace: Arc<W>, + watcher: Mutex<RecommendedWatcher>, + watched_paths: Mutex<FxHashSet<PathBuf>>, +} + +impl<W> DocumentWatcher<W> +where + W: Workspace + Send + Sync + 'static, +{ + pub fn new(workspace: Arc<W>) -> Result<Self> { + let watcher = Self::create_watcher(Arc::clone(&workspace))?; + Ok(Self { + workspace, + watcher: Mutex::new(watcher), + watched_paths: Mutex::default(), + }) + } + + fn create_watcher(workspace: Arc<W>) -> Result<RecommendedWatcher> { + let watcher = Watcher::new_immediate(move |event: notify::Result<notify::Event>| { + if let Ok(event) = event { + if event.kind.is_modify() { + for path in event.paths { + let _ = workspace.reload(path); + } + } + } + })?; + Ok(watcher) + } +} + +impl<W: Workspace> Workspace for DocumentWatcher<W> { + fn open( + &self, + uri: Arc<Uri>, + text: String, + language: DocumentLanguage, + source: WorkspaceSource, + ) -> Arc<Document> { + let document = self.workspace.open(uri, text, language, source); + if document.uri.scheme() == "file" { + if let Ok(mut path) = document.uri.to_file_path() { + path.pop(); + let mut watched_paths = self.watched_paths.lock().unwrap(); + if !watched_paths.contains(&path) { + if let Err(why) = self + .watcher + .lock() + .unwrap() + .watch(&path, RecursiveMode::NonRecursive) + { + warn!( + "Failed to watch folder of document \"{}\": {}", + document.uri, why + ); + } + watched_paths.insert(path); + } + } + } + document + } + + fn register_open_handler(&self, handler: OpenHandler) { + self.workspace.register_open_handler(handler); + } + + fn documents(&self) -> Vec<Arc<Document>> { + self.workspace.documents() + } + + fn has(&self, uri: &Uri) -> bool { + self.workspace.has(uri) + } + + fn get(&self, uri: &Uri) -> Option<Arc<Document>> { + self.workspace.get(uri) + } + + fn close(&self, uri: &Uri) { + self.workspace.close(uri) + } + + fn is_open(&self, uri: &Uri) -> bool { + self.workspace.is_open(uri) + } + + fn subset(&self, uri: Arc<Uri>) -> Option<WorkspaceSubset> { + self.workspace.subset(uri) + } +} |