summaryrefslogtreecommitdiff
path: root/support/texlab/src/server.rs
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/server.rs')
-rw-r--r--support/texlab/src/server.rs835
1 files changed, 474 insertions, 361 deletions
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<C> {
- distribution: Arc<Box<dyn tex::Distribution>>,
- build_manager: BuildManager<C>,
+ distro: Arc<dyn Distribution>,
client: Arc<C>,
client_capabilities: OnceCell<Arc<ClientCapabilities>>,
- workspace_manager: WorkspaceManager,
+ current_dir: Arc<PathBuf>,
+ config_manager: OnceCell<ConfigManager<C>>,
action_manager: ActionManager,
- diagnostics_manager: Mutex<DiagnosticsManager>,
+ workspace: Workspace,
+ build_provider: BuildProvider<C>,
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<Uri, Position>,
}
#[jsonrpc_server]
impl<C: LspClient + Send + Sync + 'static> LatexLspServer<C> {
- pub fn new(distribution: Arc<Box<dyn tex::Distribution>>, client: Arc<C>) -> Self {
- LatexLspServer {
- distribution,
- build_manager: BuildManager::new(Arc::clone(&client)),
- client,
+ pub fn new(distro: Arc<dyn Distribution>, client: Arc<C>, current_dir: Arc<PathBuf>) -> Self {
+ let workspace = Workspace::new(distro.clone(), Arc::clone(&current_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<ClientCapabilities> {
+ 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<Output = T>,
- A: FnOnce(&'a Self) -> F,
- {
- self.before_message().await;
- let result = action(&self).await;
- self.after_message().await;
- result
+ fn config_manager(&self) -> &ConfigManager<C> {
+ self.config_manager
+ .get()
+ .expect("initialize has not been called")
}
#[jsonrpc_method("initialize", kind = "request")]
pub async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
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<C: LspClient + Send + Sync + 'static> LatexLspServer<C> {
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<C: LspClient + Send + Sync + 'static> LatexLspServer<C> {
}
#[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<CompletionList> {
- 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<C: LspClient + Send + Sync + 'static> LatexLspServer<C> {
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<C: LspClient + Send + Sync + 'static> LatexLspServer<C> {
#[jsonrpc_method("textDocument/hover", kind = "request")]
pub async fn hover(&self, params: TextDocumentPositionParams) -> Result<Option<Hover>> {
- 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<C: LspClient + Send + Sync + 'static> LatexLspServer<C> {
&self,
params: TextDocumentPositionParams,
) -> Result<DefinitionResponse> {
- 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<C: LspClient + Send + Sync + 'static> LatexLspServer<C> {
#[jsonrpc_method("textDocument/references", kind = "request")]
pub async fn references(&self, params: ReferenceParams) -> Result<Vec<Location>> {
- 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<C: LspClient + Send + Sync + 'static> LatexLspServer<C> {
&self,
params: TextDocumentPositionParams,
) -> Result<Vec<DocumentHighlight>> {
- 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<C: LspClient + Send + Sync + 'static> LatexLspServer<C> {
&self,
params: WorkspaceSymbolParams,
) -> Result<Vec<SymbolInformation>> {
- 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, &params).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),
+ &params,
+ )
+ .await;
Ok(symbols)
}
#[jsonrpc_method("textDocument/documentSymbol", kind = "request")]
- pub async fn document_symbol(&self, params: DocumentSymbolParams) -> Result<SymbolResponse> {
- 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<DocumentSymbolResponse> {
+ 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<C: LspClient + Send + Sync + 'static> LatexLspServer<C> {
#[jsonrpc_method("textDocument/documentLink", kind = "request")]
pub async fn document_link(&self, params: DocumentLinkParams) -> Result<Vec<DocumentLink>> {
- 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<Vec<TextEdit>> {
- 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::<BibtexFormattingOptions>("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, &params);
- 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<TextEdit>) {
+ 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<Option<Range>> {
- 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<Option<WorkspaceEdit>> {
- 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<Vec<FoldingRange>> {
- 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<BuildResult> {
- let request = self.make_feature_request(params.text_document.as_uri(), params)?;
- let options = self.configuration::<BuildOptions>("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<C: LspClient + Send + Sync + 'static> LatexLspServer<C> {
&self,
params: TextDocumentPositionParams,
) -> Result<ForwardSearchResult> {
- let request = self.make_feature_request(params.text_document.as_uri(), params)?;
- let options = self
- .configuration::<ForwardSearchOptions>("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<T>(&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::<Vec<T>>(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(&params.as_uri(), &options).await;
+ Ok(())
}
- fn make_feature_request<P>(&self, uri: Uri, params: P) -> Result<FeatureRequest<P>> {
- 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<P>(&self, uri: Uri, params: P) -> Result<FeatureRequest<P>> {
+ 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<C: LspClient + Send + Sync + 'static> Middleware for LatexLspServer<C> {
- #[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<Vec<Action>>,
+}
+
+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<Action> {
+ let mut actions = self.actions.lock().await;
+ mem::replace(&mut *actions, Vec::new())
+ }
+}