Skip to content

Commit

Permalink
Refactor workspace synchronization
Browse files Browse the repository at this point in the history
  • Loading branch information
pfoerster committed Mar 6, 2020
1 parent 36ccaf8 commit 13e3645
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 13 deletions.
86 changes: 80 additions & 6 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ use crate::{
config::ConfigManager,
jsonrpc::{server::Result, Middleware},
protocol::*,
tex::Distribution,
tex::{Distribution, DistributionKind, KpsewhichError},
workspace::Workspace,
};
use futures::lock::Mutex;
use futures_boxed::boxed;
use jsonrpc_derive::{jsonrpc_method, jsonrpc_server};
use log::{error, info};
use once_cell::sync::{Lazy, OnceCell};
use std::{mem, path::PathBuf, sync::Arc};

Expand Down Expand Up @@ -122,6 +123,7 @@ impl<C: LspClient + Send + Sync + 'static> LatexLspServer<C> {
pub async fn initialized(&self, _params: InitializedParams) {
self.action_manager.push(Action::PullConfiguration).await;
self.action_manager.push(Action::RegisterCapabilities).await;
self.action_manager.push(Action::LoadDistribution).await;
}

#[jsonrpc_method("shutdown", kind = "request")]
Expand All @@ -136,10 +138,25 @@ impl<C: LspClient + Send + Sync + 'static> LatexLspServer<C> {
pub async fn cancel_request(&self, _params: CancelParams) {}

#[jsonrpc_method("textDocument/didOpen", kind = "notification")]
pub async fn did_open(&self, params: DidOpenTextDocumentParams) {}
pub async fn did_open(&self, params: DidOpenTextDocumentParams) {
let uri = params.text_document.uri.clone();
let options = self.config_manager().get().await;
self.workspace.add(params.text_document, &options).await;
self.action_manager
.push(Action::DetectRoot(uri.clone().into()))
.await;
}

#[jsonrpc_method("textDocument/didChange", kind = "notification")]
pub async fn did_change(&self, params: DidChangeTextDocumentParams) {}
pub async fn did_change(&self, params: DidChangeTextDocumentParams) {
let options = self.config_manager().get().await;
for change in params.content_changes {
let uri = params.text_document.uri.clone();
self.workspace
.update(uri.into(), change.text, &options)
.await;
}
}

#[jsonrpc_method("textDocument/didSave", kind = "notification")]
pub async fn did_save(&self, params: DidSaveTextDocumentParams) {}
Expand Down Expand Up @@ -254,33 +271,90 @@ impl<C: LspClient + Send + Sync + 'static> LatexLspServer<C> {
status: ForwardSearchStatus::Failure,
})
}

async fn load_distribution(&self) {
info!("Detected TeX distribution: {:?}", self.distro.kind());
if self.distro.kind() == DistributionKind::Unknown {
let params = ShowMessageParams {
message: "Your TeX distribution could not be detected. \
Please make sure that your distribution is in your PATH."
.into(),
typ: MessageType::Error,
};
self.client.show_message(params).await;
}

if let Err(why) = self.distro.load().await {
let message = match why {
KpsewhichError::NotInstalled | KpsewhichError::InvalidOutput => {
"An error occurred while executing `kpsewhich`.\
Please make sure that your distribution is in your PATH \
environment variable and provides the `kpsewhich` tool."
}
KpsewhichError::CorruptDatabase | KpsewhichError::NoDatabase => {
"The file database of your TeX distribution seems \
to be corrupt. Please rebuild it and try again."
}
KpsewhichError::Decode(_) => {
"An error occurred while decoding the output of `kpsewhich`."
}
KpsewhichError::IO(why) => {
error!("An I/O error occurred while executing 'kpsewhich': {}", why);
"An I/O error occurred while executing 'kpsewhich'"
}
};
let params = ShowMessageParams {
message: message.into(),
typ: MessageType::Error,
};
self.client.show_message(params).await;
};
}
}

impl<C: LspClient + Send + Sync + 'static> Middleware for LatexLspServer<C> {
#[boxed]
async fn before_message(&self) {}
async fn before_message(&self) {
if let Some(config_manager) = self.config_manager.get() {
let options = config_manager.get().await;
self.workspace.detect_children(&options).await;
self.workspace.reparse_all_if_newer(&options).await;
}
}

#[boxed]
async fn after_message(&self) {
for action in self.action_manager.take().await {
match action {
Action::LoadDistribution => {
self.load_distribution().await;
}
Action::RegisterCapabilities => {
let config_manager = self.config_manager();
config_manager.register().await;
}
Action::PullConfiguration => {
let config_manager = self.config_manager();
if config_manager.pull().await {
let options = config_manager.get().await;
self.workspace.reparse(&options).await;
}
}
Action::RegisterCapabilities => self.config_manager().register().await,
Action::DetectRoot(uri) => {
let options = self.config_manager().get().await;
let _ = self.workspace.detect_root(&uri, &options).await;
}
};
}
}
}

#[derive(Debug, PartialEq, Clone)]
enum Action {
PullConfiguration,
LoadDistribution,
RegisterCapabilities,
PullConfiguration,
DetectRoot(Uri),
}

#[derive(Debug, Default)]
Expand Down
110 changes: 103 additions & 7 deletions src/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,12 @@ impl Snapshot {
self.0.iter().find(|doc| doc.uri == *uri).map(Arc::clone)
}

pub fn relations(&self, uri: &Uri, options: &Options, cwd: &Path) -> Vec<Arc<Document>> {
pub fn relations(
&self,
uri: &Uri,
options: &Options,
current_dir: &Path,
) -> Vec<Arc<Document>> {
let mut graph = Graph::new_undirected();
let mut indices_by_uri = HashMap::new();
for document in &self.0 {
Expand All @@ -130,7 +135,7 @@ impl Snapshot {
graph.add_edge(indices_by_uri[&parent.uri], indices_by_uri[&child.uri], ());
});

self.resolve_aux_targets(&parent.uri, options, cwd)
self.resolve_aux_targets(&parent.uri, options, current_dir)
.into_iter()
.flatten()
.find_map(|target| self.find(&target))
Expand All @@ -151,8 +156,13 @@ impl Snapshot {
documents
}

pub fn parent(&self, uri: &Uri, options: &Options, cwd: &Path) -> Option<Arc<Document>> {
for document in self.relations(uri, options, cwd) {
pub fn parent(
&self,
uri: &Uri,
options: &Options,
current_dir: &Path,
) -> Option<Arc<Document>> {
for document in self.relations(uri, options, current_dir) {
if let DocumentContent::Latex(table) = &document.content {
if table.is_standalone {
return Some(document);
Expand All @@ -162,7 +172,7 @@ impl Snapshot {
None
}

pub fn expand(&self, options: &Options, cwd: &Path) -> Vec<Uri> {
pub fn expand(&self, options: &Options, current_dir: &Path) -> Vec<Uri> {
let mut unknown_targets = Vec::new();
for parent in &self.0 {
if let DocumentContent::Latex(table) = &parent.content {
Expand All @@ -175,7 +185,7 @@ impl Snapshot {
.flatten()
.for_each(|target| unknown_targets.push(target.clone()));

self.resolve_aux_targets(&parent.uri, options, cwd)
self.resolve_aux_targets(&parent.uri, options, current_dir)
.into_iter()
.filter(|targets| targets.iter().all(|target| self.find(target).is_none()))
.flatten()
Expand Down Expand Up @@ -365,7 +375,6 @@ impl Workspace {
DocumentContent::Bibtex(_) => Language::Bibtex,
};

debug!("Updating document: {}", uri);
*snapshot = self
.add_or_update(&snapshot, uri, text, language, options)
.await;
Expand Down Expand Up @@ -393,6 +402,93 @@ impl Workspace {
}
}

pub async fn detect_root(&self, uri: &Uri, options: &Options) -> io::Result<()> {
if uri.scheme() != "file" {
return Ok(());
}

if let Ok(mut path) = uri.to_file_path() {
while path.pop() {
let snapshot = self.get().await;
if snapshot.parent(&uri, &options, &self.current_dir).is_some() {
break;
}

let mut entries = fs::read_dir(&path).await?;
while let Some(entry) = entries.next_entry().await? {
if entry.file_type().await?.is_file() {
let path = entry.path();
if path
.extension()
.and_then(OsStr::to_str)
.and_then(Language::by_extension)
.is_some()
{
if let Ok(parent_uri) = Uri::from_file_path(&path) {
if snapshot.find(&parent_uri).is_none() {
let _ = self.load(&path, options).await;
}
}
}
}
}
}
}
Ok(())
}

pub async fn detect_children(&self, options: &Options) {
loop {
let mut changed = false;

let snapshot = self.get().await;
for path in snapshot
.expand(&options, &self.current_dir)
.into_iter()
.filter(|uri| uri.scheme() == "file")
.filter_map(|uri| uri.to_file_path().ok())
{
if path.exists() {
changed |= self.load(&path, &options).await.is_ok();
}
}

if !changed {
break;
}
}
}

pub async fn reparse_all_if_newer(&self, options: &Options) {
let snapshot = self.get().await;
for doc in &snapshot.0 {
match self.reparse_if_newer(doc, options).await {
Err(WorkspaceLoadError::IO(why)) => {
warn!("Reparsing document {} failed: {}", doc.uri, why);
}
_ => (),
}
}
}

async fn reparse_if_newer(
&self,
doc: &Document,
options: &Options,
) -> Result<(), WorkspaceLoadError> {
if !doc.is_file() {
return Ok(());
}

if let Ok(path) = doc.uri.to_file_path() {
let data = fs::metadata(&path).await?;
if data.modified()? > doc.modified {
self.load(&path, options).await?;
}
}
Ok(())
}

async fn add_or_update(
&self,
snapshot: &Snapshot,
Expand Down

0 comments on commit 13e3645

Please sign in to comment.