From 745892fbddea56040139108277e728b53fd8fc11 Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Thu, 28 May 2020 03:03:21 +0000 Subject: CTAN sync 202005280303 --- support/texlab/src/server.rs | 835 ++++++++++++++++++++++++------------------- 1 file changed, 474 insertions(+), 361 deletions(-) (limited to 'support/texlab/src/server.rs') diff --git a/support/texlab/src/server.rs b/support/texlab/src/server.rs index 23bf973a3a..e22fe4f5c9 100644 --- a/support/texlab/src/server.rs +++ b/support/texlab/src/server.rs @@ -1,108 +1,112 @@ -use crate::action::{Action, ActionManager, LintReason}; -use crate::build::*; -use crate::capabilities::ClientCapabilitiesExt; +#[cfg(feature = "citation")] use crate::citeproc::render_citation; -use crate::client::LspClient; -use crate::completion::{CompletionItemData, CompletionProvider, DATABASE}; -use crate::definition::{DefinitionProvider, DefinitionResponse}; -use crate::diagnostics::{DiagnosticsManager, LatexLintOptions}; -use crate::folding::FoldingProvider; -use crate::formatting::bibtex::{self, BibtexFormattingOptions, BibtexFormattingParams}; -use crate::forward_search::{self, ForwardSearchOptions, ForwardSearchResult}; -use crate::highlight::HighlightProvider; -use crate::hover::HoverProvider; -use crate::link::LinkProvider; -use crate::reference::ReferenceProvider; -use crate::rename::{PrepareRenameProvider, RenameProvider}; -use crate::symbol::{self, SymbolProvider, SymbolResponse}; -use crate::syntax::*; -use crate::workspace::*; + +use crate::{ + build::BuildProvider, + completion::{CompletionItemData, CompletionProvider}, + components::COMPONENT_DATABASE, + config::ConfigManager, + definition::DefinitionProvider, + diagnostics::DiagnosticsManager, + feature::{DocumentView, FeatureProvider, FeatureRequest}, + folding::FoldingProvider, + forward_search, + highlight::HighlightProvider, + hover::HoverProvider, + link::LinkProvider, + protocol::*, + reference::ReferenceProvider, + rename::{PrepareRenameProvider, RenameProvider}, + symbol::{document_symbols, workspace_symbols, SymbolProvider}, + syntax::{bibtex, latexindent, CharStream, SyntaxNode}, + tex::{Distribution, DistributionKind, KpsewhichError}, + workspace::{DocumentContent, Workspace}, +}; +use async_trait::async_trait; +use chashmap::CHashMap; use futures::lock::Mutex; -use futures_boxed::boxed; -use jsonrpc::server::Middleware; -use jsonrpc::server::Result; +use jsonrpc::{server::Result, Middleware}; use jsonrpc_derive::{jsonrpc_method, jsonrpc_server}; -use log::*; -use lsp_types::*; +use log::{debug, error, info, warn}; use once_cell::sync::{Lazy, OnceCell}; -use serde::de::DeserializeOwned; -use std::ffi::OsStr; -use std::fs; -use std::future::Future; -use std::sync::Arc; -use tex::Language; -use walkdir::WalkDir; +use std::{mem, path::PathBuf, sync::Arc}; pub struct LatexLspServer { - distribution: Arc>, - build_manager: BuildManager, + distro: Arc, client: Arc, client_capabilities: OnceCell>, - workspace_manager: WorkspaceManager, + current_dir: Arc, + config_manager: OnceCell>, action_manager: ActionManager, - diagnostics_manager: Mutex, + workspace: Workspace, + build_provider: BuildProvider, completion_provider: CompletionProvider, definition_provider: DefinitionProvider, folding_provider: FoldingProvider, highlight_provider: HighlightProvider, - symbol_provider: SymbolProvider, - hover_provider: HoverProvider, link_provider: LinkProvider, reference_provider: ReferenceProvider, prepare_rename_provider: PrepareRenameProvider, rename_provider: RenameProvider, + symbol_provider: SymbolProvider, + hover_provider: HoverProvider, + diagnostics_manager: DiagnosticsManager, + last_position_by_uri: CHashMap, } #[jsonrpc_server] impl LatexLspServer { - pub fn new(distribution: Arc>, client: Arc) -> Self { - LatexLspServer { - distribution, - build_manager: BuildManager::new(Arc::clone(&client)), - client, + pub fn new(distro: Arc, client: Arc, current_dir: Arc) -> Self { + let workspace = Workspace::new(distro.clone(), Arc::clone(¤t_dir)); + Self { + distro, + client: Arc::clone(&client), client_capabilities: OnceCell::new(), - workspace_manager: WorkspaceManager::default(), + current_dir, + config_manager: OnceCell::new(), action_manager: ActionManager::default(), - diagnostics_manager: Mutex::new(DiagnosticsManager::default()), + workspace, + build_provider: BuildProvider::new(client), completion_provider: CompletionProvider::new(), definition_provider: DefinitionProvider::new(), folding_provider: FoldingProvider::new(), highlight_provider: HighlightProvider::new(), - symbol_provider: SymbolProvider::new(), - hover_provider: HoverProvider::new(), link_provider: LinkProvider::new(), reference_provider: ReferenceProvider::new(), prepare_rename_provider: PrepareRenameProvider::new(), rename_provider: RenameProvider::new(), + symbol_provider: SymbolProvider::new(), + hover_provider: HoverProvider::new(), + diagnostics_manager: DiagnosticsManager::default(), + last_position_by_uri: CHashMap::new(), } } - pub async fn execute<'a, T, A>(&'a self, action: A) -> T - where - A: FnOnce(&'a Self) -> T, - { - self.before_message().await; - let result = action(&self); - self.after_message().await; - result + fn client_capabilities(&self) -> Arc { + Arc::clone( + self.client_capabilities + .get() + .expect("initialize has not been called"), + ) } - pub async fn execute_async<'a, T, F, A>(&'a self, action: A) -> T - where - F: Future, - A: FnOnce(&'a Self) -> F, - { - self.before_message().await; - let result = action(&self).await; - self.after_message().await; - result + fn config_manager(&self) -> &ConfigManager { + self.config_manager + .get() + .expect("initialize has not been called") } #[jsonrpc_method("initialize", kind = "request")] pub async fn initialize(&self, params: InitializeParams) -> Result { self.client_capabilities .set(Arc::new(params.capabilities)) - .unwrap(); + .expect("initialize was called two times"); + + let _ = self.config_manager.set(ConfigManager::new( + Arc::clone(&self.client), + self.client_capabilities(), + )); + let capabilities = ServerCapabilities { text_document_sync: Some(TextDocumentSyncCapability::Options( TextDocumentSyncOptions { @@ -119,48 +123,49 @@ impl LatexLspServer { completion_provider: Some(CompletionOptions { resolve_provider: Some(true), trigger_characters: Some(vec![ - "\\".to_owned(), - "{".to_owned(), - "}".to_owned(), - "@".to_owned(), - "/".to_owned(), - " ".to_owned(), + "\\".into(), + "{".into(), + "}".into(), + "@".into(), + "/".into(), + " ".into(), ]), + ..CompletionOptions::default() }), - signature_help_provider: None, definition_provider: Some(true), - type_definition_provider: None, - implementation_provider: None, references_provider: Some(true), document_highlight_provider: Some(true), document_symbol_provider: Some(true), workspace_symbol_provider: Some(true), - code_action_provider: None, - code_lens_provider: None, document_formatting_provider: Some(true), - document_range_formatting_provider: None, - document_on_type_formatting_provider: None, rename_provider: Some(RenameProviderCapability::Options(RenameOptions { prepare_provider: Some(true), + work_done_progress_options: WorkDoneProgressOptions::default(), })), document_link_provider: Some(DocumentLinkOptions { resolve_provider: Some(false), + work_done_progress_options: WorkDoneProgressOptions::default(), }), - color_provider: None, folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)), - execute_command_provider: None, - workspace: None, - selection_range_provider: None, + ..ServerCapabilities::default() }; - Lazy::force(&DATABASE); - Ok(InitializeResult { capabilities }) + Lazy::force(&COMPONENT_DATABASE); + Ok(InitializeResult { + capabilities, + server_info: Some(ServerInfo { + name: "TexLab".to_owned(), + version: Some(env!("CARGO_PKG_VERSION").to_owned()), + }), + }) } #[jsonrpc_method("initialized", kind = "notification")] - pub fn initialized(&self, _params: InitializedParams) { - self.action_manager.push(Action::CheckInstalledDistribution); - self.action_manager.push(Action::PublishDiagnostics); + 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; + self.action_manager.push(Action::PublishDiagnostics).await; } #[jsonrpc_method("shutdown", kind = "request")] @@ -169,58 +174,88 @@ impl LatexLspServer { } #[jsonrpc_method("exit", kind = "notification")] - pub fn exit(&self, _params: ()) {} + pub async fn exit(&self, _params: ()) {} #[jsonrpc_method("$/cancelRequest", kind = "notification")] - pub fn cancel_request(&self, _params: CancelParams) {} + pub async fn cancel_request(&self, _params: CancelParams) {} #[jsonrpc_method("textDocument/didOpen", kind = "notification")] - pub fn did_open(&self, params: DidOpenTextDocumentParams) { + pub async fn did_open(&self, params: DidOpenTextDocumentParams) { let uri = params.text_document.uri.clone(); - self.workspace_manager.add(params.text_document); - self.action_manager.push(Action::DetectRoot(uri.into())); - self.action_manager.push(Action::PublishDiagnostics); + 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; + self.action_manager + .push(Action::RunLinter(uri.into(), LintReason::Save)) + .await; + self.action_manager.push(Action::PublishDiagnostics).await; } #[jsonrpc_method("textDocument/didChange", kind = "notification")] - pub 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_manager.update(uri.into(), change.text); + self.workspace + .update(uri.into(), change.text, &options) + .await; } - self.action_manager.push(Action::RunLinter( - params.text_document.uri.into(), - LintReason::Change, - )); - self.action_manager.push(Action::PublishDiagnostics); + self.action_manager + .push(Action::RunLinter( + params.text_document.uri.clone().into(), + LintReason::Change, + )) + .await; + self.action_manager.push(Action::PublishDiagnostics).await; } #[jsonrpc_method("textDocument/didSave", kind = "notification")] - pub fn did_save(&self, params: DidSaveTextDocumentParams) { - self.action_manager.push(Action::RunLinter( - params.text_document.uri.clone().into(), - LintReason::Save, - )); - self.action_manager.push(Action::PublishDiagnostics); + pub async fn did_save(&self, params: DidSaveTextDocumentParams) { + self.action_manager + .push(Action::Build(params.text_document.uri.clone().into())) + .await; + self.action_manager - .push(Action::Build(params.text_document.uri.into())); + .push(Action::RunLinter( + params.text_document.uri.into(), + LintReason::Save, + )) + .await; + self.action_manager.push(Action::PublishDiagnostics).await; } #[jsonrpc_method("textDocument/didClose", kind = "notification")] - pub fn did_close(&self, _params: DidCloseTextDocumentParams) {} + pub async fn did_close(&self, _params: DidCloseTextDocumentParams) {} + + #[jsonrpc_method("workspace/didChangeConfiguration", kind = "notification")] + pub async fn did_change_configuration(&self, params: DidChangeConfigurationParams) { + let config_manager = self.config_manager(); + config_manager.push(params.settings).await; + let options = config_manager.get().await; + self.workspace.reparse(&options).await; + } #[jsonrpc_method("window/workDoneProgress/cancel", kind = "notification")] - pub fn work_done_progress_cancel(&self, params: WorkDoneProgressCancelParams) { - self.action_manager.push(Action::CancelBuild(params.token)); + pub async fn work_done_progress_cancel(&self, params: WorkDoneProgressCancelParams) { + self.build_provider.cancel(params.token).await; } #[jsonrpc_method("textDocument/completion", kind = "request")] pub async fn completion(&self, params: CompletionParams) -> Result { - let request = self.make_feature_request(params.text_document_position.as_uri(), params)?; - let items = self.completion_provider.execute(&request).await; + let req = self + .make_feature_request(params.text_document_position.text_document.as_uri(), params) + .await?; + + self.last_position_by_uri.insert( + req.current().uri.clone(), + req.params.text_document_position.position, + ); + Ok(CompletionList { is_incomplete: true, - items, + items: self.completion_provider.execute(&req).await, }) } @@ -229,14 +264,15 @@ impl LatexLspServer { let data: CompletionItemData = serde_json::from_value(item.data.clone().unwrap()).unwrap(); match data { CompletionItemData::Package | CompletionItemData::Class => { - item.documentation = DATABASE + item.documentation = COMPONENT_DATABASE .documentation(&item.label) .map(Documentation::MarkupContent); } + #[cfg(feature = "citation")] CompletionItemData::Citation { uri, key } => { - let workspace = self.workspace_manager.get(); - if let Some(document) = workspace.find(&uri) { - if let SyntaxTree::Bibtex(tree) = &document.tree { + let snapshot = self.workspace.get().await; + if let Some(doc) = snapshot.find(&uri) { + if let DocumentContent::Bibtex(tree) = &doc.content { let markup = render_citation(&tree, &key); item.documentation = markup.map(Documentation::MarkupContent); } @@ -249,9 +285,14 @@ impl LatexLspServer { #[jsonrpc_method("textDocument/hover", kind = "request")] pub async fn hover(&self, params: TextDocumentPositionParams) -> Result> { - let request = self.make_feature_request(params.text_document.as_uri(), params)?; - let hover = self.hover_provider.execute(&request).await; - Ok(hover) + let req = self + .make_feature_request(params.text_document.as_uri(), params) + .await?; + + self.last_position_by_uri + .insert(req.current().uri.clone(), req.params.position); + + Ok(self.hover_provider.execute(&req).await) } #[jsonrpc_method("textDocument/definition", kind = "request")] @@ -259,9 +300,11 @@ impl LatexLspServer { &self, params: TextDocumentPositionParams, ) -> Result { - let request = self.make_feature_request(params.text_document.as_uri(), params)?; - let results = self.definition_provider.execute(&request).await; - let response = if request.client_capabilities.has_definition_link_support() { + let req = self + .make_feature_request(params.text_document.as_uri(), params) + .await?; + let results = self.definition_provider.execute(&req).await; + let response = if req.client_capabilities.has_definition_link_support() { DefinitionResponse::LocationLinks(results) } else { DefinitionResponse::Locations( @@ -277,9 +320,10 @@ impl LatexLspServer { #[jsonrpc_method("textDocument/references", kind = "request")] pub async fn references(&self, params: ReferenceParams) -> Result> { - let request = self.make_feature_request(params.text_document_position.as_uri(), params)?; - let results = self.reference_provider.execute(&request).await; - Ok(results) + let req = self + .make_feature_request(params.text_document_position.as_uri(), params) + .await?; + Ok(self.reference_provider.execute(&req).await) } #[jsonrpc_method("textDocument/documentHighlight", kind = "request")] @@ -287,9 +331,10 @@ impl LatexLspServer { &self, params: TextDocumentPositionParams, ) -> Result> { - let request = self.make_feature_request(params.text_document.as_uri(), params)?; - let results = self.highlight_provider.execute(&request).await; - Ok(results) + let req = self + .make_feature_request(params.text_document.as_uri(), params) + .await?; + Ok(self.highlight_provider.execute(&req).await) } #[jsonrpc_method("workspace/symbol", kind = "request")] @@ -297,22 +342,38 @@ impl LatexLspServer { &self, params: WorkspaceSymbolParams, ) -> Result> { - let distribution = Arc::clone(&self.distribution); - let client_capabilities = Arc::clone(&self.client_capabilities.get().unwrap()); - let workspace = self.workspace_manager.get(); - let symbols = - symbol::workspace_symbols(distribution, client_capabilities, workspace, ¶ms).await; + let distro = self.distro.clone(); + let client_capabilities = self.client_capabilities(); + let snapshot = self.workspace.get().await; + let options = self.config_manager().get().await; + let symbols = workspace_symbols( + distro, + client_capabilities, + snapshot, + &options, + Arc::clone(&self.current_dir), + ¶ms, + ) + .await; Ok(symbols) } #[jsonrpc_method("textDocument/documentSymbol", kind = "request")] - pub async fn document_symbol(&self, params: DocumentSymbolParams) -> Result { - let request = self.make_feature_request(params.text_document.as_uri(), params)?; - let symbols = self.symbol_provider.execute(&request).await; - let response = SymbolResponse::new( - &self.client_capabilities.get().unwrap(), - &request.view.workspace, - &request.document().uri, + pub async fn document_symbol( + &self, + params: DocumentSymbolParams, + ) -> Result { + let req = self + .make_feature_request(params.text_document.as_uri(), params) + .await?; + + let symbols = self.symbol_provider.execute(&req).await; + let response = document_symbols( + &req.client_capabilities, + req.snapshot(), + &req.current().uri, + &req.options, + &req.current_dir, symbols.into_iter().map(Into::into).collect(), ); Ok(response) @@ -320,69 +381,127 @@ impl LatexLspServer { #[jsonrpc_method("textDocument/documentLink", kind = "request")] pub async fn document_link(&self, params: DocumentLinkParams) -> Result> { - let request = self.make_feature_request(params.text_document.as_uri(), params)?; - let links = self.link_provider.execute(&request).await; - Ok(links) + let req = self + .make_feature_request(params.text_document.as_uri(), params) + .await?; + Ok(self.link_provider.execute(&req).await) } #[jsonrpc_method("textDocument/formatting", kind = "request")] pub async fn formatting(&self, params: DocumentFormattingParams) -> Result> { - let request = self.make_feature_request(params.text_document.as_uri(), params)?; + let req = self + .make_feature_request(params.text_document.as_uri(), params) + .await?; let mut edits = Vec::new(); - if let SyntaxTree::Bibtex(tree) = &request.document().tree { - let params = BibtexFormattingParams { - tab_size: request.params.options.tab_size as usize, - insert_spaces: request.params.options.insert_spaces, - options: self - .configuration::("bibtex.formatting") - .await, - }; + match &req.current().content { + DocumentContent::Latex(_) => { + Self::run_latexindent(&req.current().text, "tex", &mut edits).await; + } + DocumentContent::Bibtex(tree) => { + let options = req + .options + .bibtex + .clone() + .and_then(|opts| opts.formatting) + .unwrap_or_default(); + + match options.formatter.unwrap_or_default() { + BibtexFormatter::Texlab => { + let params = bibtex::FormattingParams { + tab_size: req.params.options.tab_size as usize, + insert_spaces: req.params.options.insert_spaces, + options: &options, + }; - for declaration in &tree.root.children { - let should_format = match declaration { - BibtexDeclaration::Comment(_) => false, - BibtexDeclaration::Preamble(_) | BibtexDeclaration::String(_) => true, - BibtexDeclaration::Entry(entry) => !entry.is_comment(), - }; - if should_format { - let text = bibtex::format_declaration(&declaration, ¶ms); - edits.push(TextEdit::new(declaration.range(), text)); + for node in tree.children(tree.root) { + let should_format = match &tree.graph[node] { + bibtex::Node::Preamble(_) | bibtex::Node::String(_) => true, + bibtex::Node::Entry(entry) => !entry.is_comment(), + _ => false, + }; + if should_format { + let text = bibtex::format(&tree, node, params); + edits.push(TextEdit::new(tree.graph[node].range(), text)); + } + } + } + BibtexFormatter::Latexindent => { + Self::run_latexindent(&req.current().text, "bib", &mut edits).await; + } } } } Ok(edits) } + async fn run_latexindent(old_text: &str, extension: &str, edits: &mut Vec) { + match latexindent::format(old_text, extension).await { + Ok(new_text) => { + let mut stream = CharStream::new(&old_text); + while stream.next().is_some() {} + let range = Range::new(Position::new(0, 0), stream.current_position); + edits.push(TextEdit::new(range, new_text)); + } + Err(why) => { + debug!("Failed to run latexindent.pl: {}", why); + } + } + } + #[jsonrpc_method("textDocument/prepareRename", kind = "request")] pub async fn prepare_rename( &self, params: TextDocumentPositionParams, ) -> Result> { - let request = self.make_feature_request(params.as_uri(), params)?; - let range = self.prepare_rename_provider.execute(&request).await; - Ok(range) + let req = self + .make_feature_request(params.text_document.as_uri(), params) + .await?; + Ok(self.prepare_rename_provider.execute(&req).await) } #[jsonrpc_method("textDocument/rename", kind = "request")] pub async fn rename(&self, params: RenameParams) -> Result> { - let request = self.make_feature_request(params.text_document_position.as_uri(), params)?; - let edit = self.rename_provider.execute(&request).await; - Ok(edit) + let req = self + .make_feature_request(params.text_document_position.text_document.as_uri(), params) + .await?; + Ok(self.rename_provider.execute(&req).await) } #[jsonrpc_method("textDocument/foldingRange", kind = "request")] pub async fn folding_range(&self, params: FoldingRangeParams) -> Result> { - let request = self.make_feature_request(params.text_document.as_uri(), params)?; - let foldings = self.folding_provider.execute(&request).await; - Ok(foldings) + let req = self + .make_feature_request(params.text_document.as_uri(), params) + .await?; + Ok(self.folding_provider.execute(&req).await) } #[jsonrpc_method("textDocument/build", kind = "request")] pub async fn build(&self, params: BuildParams) -> Result { - let request = self.make_feature_request(params.text_document.as_uri(), params)?; - let options = self.configuration::("latex.build").await; - let result = self.build_manager.build(request, options).await; - Ok(result) + let req = self + .make_feature_request(params.text_document.as_uri(), params) + .await?; + + let pos = self + .last_position_by_uri + .get(&req.current().uri) + .map(|pos| *pos) + .unwrap_or_default(); + + let res = self.build_provider.execute(&req).await; + + if req + .options + .latex + .and_then(|opts| opts.build) + .unwrap_or_default() + .forward_search_after() + && !self.build_provider.is_building() + { + let params = TextDocumentPositionParams::new(req.params.text_document, pos); + self.forward_search(params).await?; + } + + Ok(res) } #[jsonrpc_method("textDocument/forwardSearch", kind = "request")] @@ -390,240 +509,234 @@ impl LatexLspServer { &self, params: TextDocumentPositionParams, ) -> Result { - let request = self.make_feature_request(params.text_document.as_uri(), params)?; - let options = self - .configuration::("latex.forwardSearch") - .await; - - let tex_file = request.document().uri.to_file_path().unwrap(); - let parent = request - .workspace() - .find_parent(&request.document().uri) - .unwrap_or(request.view.document); - let parent = parent.uri.to_file_path().unwrap(); - forward_search::search(&tex_file, &parent, request.params.position.line, options) - .await - .ok_or_else(|| "Unable to execute forward search".into()) - } - - async fn configuration(&self, section: &'static str) -> T - where - T: DeserializeOwned + Default, - { - if !self - .client_capabilities - .get() - .and_then(|cap| cap.workspace.as_ref()) - .and_then(|cap| cap.configuration) - .unwrap_or(false) - { - return T::default(); - } - - let params = ConfigurationParams { - items: vec![ConfigurationItem { - section: Some(section.into()), - scope_uri: None, - }], - }; - - match self.client.configuration(params).await { - Ok(json) => match serde_json::from_value::>(json) { - Ok(config) => config.into_iter().next().unwrap(), - Err(_) => { - warn!("Invalid configuration: {}", section); - T::default() - } - }, - Err(why) => { - error!( - "Retrieving configuration for {} failed: {}", - section, why.message - ); - T::default() - } - } + let req = self + .make_feature_request(params.text_document.as_uri(), params) + .await?; + + forward_search::search( + &req.view.snapshot, + &req.current().uri, + req.params.position.line, + &req.options, + &self.current_dir, + ) + .await + .ok_or_else(|| "Unable to execute forward search".into()) + } + + #[jsonrpc_method("$/detectRoot", kind = "request")] + pub async fn detect_root(&self, params: TextDocumentIdentifier) -> Result<()> { + let options = self.config_manager().get().await; + let _ = self.workspace.detect_root(¶ms.as_uri(), &options).await; + Ok(()) } - fn make_feature_request

(&self, uri: Uri, params: P) -> Result> { - let workspace = self.workspace_manager.get(); - let client_capabilities = self - .client_capabilities - .get() - .expect("Failed to retrieve client capabilities"); - - if let Some(document) = workspace.find(&uri) { - Ok(FeatureRequest { + async fn make_feature_request

(&self, uri: Uri, params: P) -> Result> { + let options = self.pull_configuration().await; + let snapshot = self.workspace.get().await; + let client_capabilities = self.client_capabilities(); + match snapshot.find(&uri) { + Some(current) => Ok(FeatureRequest { params, - view: DocumentView::new(workspace, document), - client_capabilities: Arc::clone(&client_capabilities), - distribution: Arc::clone(&self.distribution), - }) - } else { - let msg = format!("Unknown document: {}", uri); - Err(msg) - } - } - - async fn detect_children(&self) { - loop { - let mut changed = false; - - let workspace = self.workspace_manager.get(); - for path in workspace.unresolved_includes() { - if path.exists() { - changed |= self.workspace_manager.load(&path).is_ok(); - } - } - - if !changed { - break; + view: DocumentView::analyze(snapshot, current, &options, &self.current_dir), + distro: self.distro.clone(), + client_capabilities, + options, + current_dir: Arc::clone(&self.current_dir), + }), + None => { + let msg = format!("Unknown document: {}", uri); + Err(msg) } } } - fn update_document(&self, document: &Document) -> std::result::Result<(), LoadError> { - if document.uri.scheme() != "file" { - return Ok(()); - } - - let path = document.uri.to_file_path().unwrap(); - let data = fs::metadata(&path).map_err(LoadError::IO)?; - if data.modified().map_err(LoadError::IO)? > document.modified { - self.workspace_manager.load(&path) - } else { - Ok(()) + async fn pull_configuration(&self) -> Options { + let config_manager = self.config_manager(); + let has_changed = config_manager.pull().await; + let options = config_manager.get().await; + if has_changed { + self.workspace.reparse(&options).await; } + options } async fn update_build_diagnostics(&self) { - let workspace = self.workspace_manager.get(); - let mut diagnostics_manager = self.diagnostics_manager.lock().await; - for document in &workspace.documents { - if document.uri.scheme() != "file" { - continue; - } - - if let SyntaxTree::Latex(tree) = &document.tree { - if tree.env.is_standalone { - match diagnostics_manager.build.update(&document.uri) { - Ok(true) => self.action_manager.push(Action::PublishDiagnostics), + let snapshot = self.workspace.get().await; + let options = self.config_manager().get().await; + + for doc in snapshot.0.iter().filter(|doc| doc.uri.scheme() == "file") { + if let DocumentContent::Latex(table) = &doc.content { + if table.is_standalone { + match self + .diagnostics_manager + .build + .update(&snapshot, &doc.uri, &options, &self.current_dir) + .await + { + Ok(true) => self.action_manager.push(Action::PublishDiagnostics).await, Ok(false) => (), - Err(why) => warn!( - "Unable to read log file ({}): {}", - why, - document.uri.as_str() - ), + Err(why) => { + warn!("Unable to read log file ({}): {}", why, doc.uri.as_str()) + } } } } } } + + 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; + }; + } } +#[async_trait] impl Middleware for LatexLspServer { - #[boxed] async fn before_message(&self) { - self.detect_children().await; - - let workspace = self.workspace_manager.get(); - for document in &workspace.documents { - drop(self.update_document(document)); + 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) { self.update_build_diagnostics().await; - for action in self.action_manager.take() { + for action in self.action_manager.take().await { match action { - Action::CheckInstalledDistribution => { - info!("Detected TeX distribution: {:?}", self.distribution.kind()); - if self.distribution.kind() == tex::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; - } + Action::LoadDistribution => { + self.load_distribution().await; + } + Action::RegisterCapabilities => { + let config_manager = self.config_manager(); + config_manager.register().await; + } + Action::PullConfiguration => { + self.pull_configuration().await; } Action::DetectRoot(uri) => { - if uri.scheme() == "file" { - let mut path = uri.to_file_path().unwrap(); - while path.pop() { - let workspace = self.workspace_manager.get(); - if workspace.find_parent(&uri).is_some() { - break; - } - - for entry in WalkDir::new(&path) - .min_depth(1) - .max_depth(1) - .into_iter() - .filter_map(std::result::Result::ok) - .filter(|entry| entry.file_type().is_file()) - .filter(|entry| { - entry - .path() - .extension() - .and_then(OsStr::to_str) - .and_then(Language::by_extension) - .is_some() - }) - { - if let Ok(parent_uri) = Uri::from_file_path(entry.path()) { - if workspace.find(&parent_uri).is_none() { - drop(self.workspace_manager.load(entry.path())); - } - } - } - } - } + let options = self.config_manager().get().await; + let _ = self.workspace.detect_root(&uri, &options).await; } Action::PublishDiagnostics => { - let workspace = self.workspace_manager.get(); - for document in &workspace.documents { - let diagnostics = { - let manager = self.diagnostics_manager.lock().await; - manager.get(&document) - }; - + let snapshot = self.workspace.get().await; + for doc in &snapshot.0 { + let diagnostics = self.diagnostics_manager.get(doc).await; let params = PublishDiagnosticsParams { - uri: document.uri.clone().into(), + uri: doc.uri.clone().into(), diagnostics, + version: None, }; self.client.publish_diagnostics(params).await; } } + Action::Build(uri) => { + let options = self + .config_manager() + .get() + .await + .latex + .and_then(|opts| opts.build) + .unwrap_or_default(); + + if options.on_save() { + let text_document = TextDocumentIdentifier::new(uri.into()); + self.build(BuildParams { text_document }).await.unwrap(); + } + } Action::RunLinter(uri, reason) => { - let config: LatexLintOptions = self.configuration("latex.lint").await; + let options = self + .config_manager() + .get() + .await + .latex + .and_then(|opts| opts.lint) + .unwrap_or_default(); + let should_lint = match reason { - LintReason::Change => config.on_change(), - LintReason::Save => config.on_save(), + LintReason::Change => options.on_change(), + LintReason::Save => options.on_save() || options.on_change(), }; + if should_lint { - let workspace = self.workspace_manager.get(); - if let Some(document) = workspace.find(&uri) { - if let SyntaxTree::Latex(_) = &document.tree { - let mut diagnostics_manager = self.diagnostics_manager.lock().await; - diagnostics_manager.latex.update(&uri, &document.text); + let snapshot = self.workspace.get().await; + if let Some(doc) = snapshot.find(&uri) { + if let DocumentContent::Latex(_) = &doc.content { + self.diagnostics_manager.latex.update(&uri, &doc.text).await; } } } } - Action::Build(uri) => { - let config: BuildOptions = self.configuration("latex.build").await; - if config.on_save() { - let text_document = TextDocumentIdentifier::new(uri.into()); - self.build(BuildParams { text_document }).await.unwrap(); - } - } - Action::CancelBuild(token) => { - self.build_manager.cancel(token).await; - } } } } } + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum LintReason { + Change, + Save, +} + +#[derive(Debug, PartialEq, Clone)] +enum Action { + LoadDistribution, + RegisterCapabilities, + PullConfiguration, + DetectRoot(Uri), + PublishDiagnostics, + Build(Uri), + RunLinter(Uri, LintReason), +} + +#[derive(Debug, Default)] +struct ActionManager { + actions: Mutex>, +} + +impl ActionManager { + pub async fn push(&self, action: Action) { + let mut actions = self.actions.lock().await; + actions.push(action); + } + + pub async fn take(&self) -> Vec { + let mut actions = self.actions.lock().await; + mem::replace(&mut *actions, Vec::new()) + } +} -- cgit v1.2.3