summaryrefslogtreecommitdiff
path: root/support/texlab/src/workspace.rs
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2022-05-26 03:01:06 +0000
committerNorbert Preining <norbert@preining.info>2022-05-26 03:01:06 +0000
commit02d941fa9c9895bb08a84ac9afe3559abd1ba8ad (patch)
tree481d1d368c6d295a779b590d23bcd5397bcba628 /support/texlab/src/workspace.rs
parentf01a37f8311f33e32441d25bdadcda9dcdbd165d (diff)
CTAN sync 202205260301
Diffstat (limited to 'support/texlab/src/workspace.rs')
-rw-r--r--support/texlab/src/workspace.rs232
1 files changed, 208 insertions, 24 deletions
diff --git a/support/texlab/src/workspace.rs b/support/texlab/src/workspace.rs
index 12924e2cf4..8fda9c7c1b 100644
--- a/support/texlab/src/workspace.rs
+++ b/support/texlab/src/workspace.rs
@@ -1,32 +1,216 @@
-mod api;
-mod children_expand;
-mod document;
-mod parent_expand;
-mod storage;
-mod watch;
-
-use std::sync::Arc;
+use std::{fs, path::PathBuf, sync::Arc};
use anyhow::Result;
+use crossbeam_channel::Sender;
+use lsp_types::Url;
+use petgraph::{graphmap::UnGraphMap, visit::Dfs};
+use rustc_hash::{FxHashMap, FxHashSet};
-use crate::ServerContext;
+use crate::{component_db::COMPONENT_DATABASE, Document, DocumentLanguage, Environment};
-pub use self::{api::*, document::*};
-use self::{
- children_expand::ChildrenExpander, parent_expand::ParentExpander, storage::Storage,
- watch::DocumentWatcher,
-};
+#[derive(Debug, Clone)]
+pub enum WorkspaceEvent {
+ Changed(Workspace, Document),
+}
-pub fn create_workspace_fast(context: Arc<ServerContext>) -> Result<impl Workspace> {
- let workspace = Storage::new(context);
- let workspace = ChildrenExpander::new(Arc::new(workspace));
- Ok(workspace)
+#[derive(Debug, Clone, Default)]
+pub struct Workspace {
+ pub documents_by_uri: FxHashMap<Arc<Url>, Document>,
+ pub viewport: FxHashSet<Arc<Url>>,
+ pub listeners: Vec<Sender<WorkspaceEvent>>,
+ pub environment: Environment,
}
-pub fn create_workspace_full(context: Arc<ServerContext>) -> Result<impl Workspace> {
- let workspace = Storage::new(context);
- let workspace = DocumentWatcher::new(Arc::new(workspace))?;
- let workspace = ParentExpander::new(workspace);
- let workspace = ChildrenExpander::new(Arc::new(workspace));
- Ok(workspace)
+impl Workspace {
+ pub fn new(environment: Environment) -> Self {
+ Self {
+ environment,
+ ..Self::default()
+ }
+ }
+
+ pub fn open(
+ &mut self,
+ uri: Arc<Url>,
+ text: Arc<String>,
+ language: DocumentLanguage,
+ ) -> Result<Document> {
+ log::debug!("(Re)Loading document: {}", uri);
+ let document = Document::parse(&self.environment, Arc::clone(&uri), text, language);
+
+ self.documents_by_uri
+ .insert(Arc::clone(&uri), document.clone());
+
+ for listener in &self.listeners {
+ listener.send(WorkspaceEvent::Changed(self.clone(), document.clone()))?;
+ }
+
+ self.expand_parent(&document);
+ self.expand_children(&document);
+ Ok(document)
+ }
+
+ pub fn reload(&mut self, path: PathBuf) -> Result<Option<Document>> {
+ let uri = Arc::new(Url::from_file_path(path.clone()).unwrap());
+
+ if self.is_open(&uri) && !uri.as_str().ends_with(".log") {
+ return Ok(self.documents_by_uri.get(&uri).cloned());
+ }
+
+ if let Some(language) = DocumentLanguage::by_path(&path) {
+ let data = fs::read(&path)?;
+ let text = Arc::new(String::from_utf8_lossy(&data).into_owned());
+ Ok(Some(self.open(uri, text, language)?))
+ } else {
+ Ok(None)
+ }
+ }
+
+ pub fn load(&mut self, path: PathBuf) -> Result<Option<Document>> {
+ let uri = Arc::new(Url::from_file_path(path.clone()).unwrap());
+
+ if let Some(document) = self.documents_by_uri.get(&uri).cloned() {
+ return Ok(Some(document));
+ }
+
+ let data = fs::read(&path)?;
+ let text = Arc::new(String::from_utf8_lossy(&data).into_owned());
+ if let Some(language) = DocumentLanguage::by_path(&path) {
+ Ok(Some(self.open(uri, text, language)?))
+ } else {
+ Ok(None)
+ }
+ }
+
+ pub fn close(&mut self, uri: &Url) {
+ self.viewport.remove(uri);
+ }
+
+ pub fn is_open(&self, uri: &Url) -> bool {
+ self.viewport.contains(uri)
+ }
+
+ 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, ()));
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ let mut slice = self.clone();
+ slice.documents_by_uri = FxHashMap::default();
+ let graph = UnGraphMap::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
+ })
+ .unwrap_or_default()
+ }
+
+ fn find_parent(&self, uri: &Url) -> Option<Document> {
+ 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(|link| link.as_component_name())
+ .any(|name| name == "subfiles.cls")
+ })
+ })
+ .cloned()
+ }
+
+ fn expand_parent(&mut self, document: &Document) {
+ let all_current_paths = self
+ .documents_by_uri
+ .values()
+ .filter_map(|doc| doc.uri.to_file_path().ok())
+ .collect::<FxHashSet<_>>();
+
+ 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() {
+ std::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| {
+ let _ = self.load(path);
+ });
+ }
+ }
+ }
+ }
+
+ fn expand_children(&mut self, 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_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 self.load(path).is_ok() {
+ break;
+ }
+ }
+ });
+ }
+ }
}