summaryrefslogtreecommitdiff
path: root/support/texlab/src/workspace
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/workspace')
-rw-r--r--support/texlab/src/workspace/api.rs86
-rw-r--r--support/texlab/src/workspace/children_expand.rs92
-rw-r--r--support/texlab/src/workspace/document.rs127
-rw-r--r--support/texlab/src/workspace/parent_expand.rs123
-rw-r--r--support/texlab/src/workspace/storage.rs136
-rw-r--r--support/texlab/src/workspace/watch.rs107
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)
+ }
+}