diff options
author | Norbert Preining <norbert@preining.info> | 2021-05-23 03:00:39 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2021-05-23 03:00:39 +0000 |
commit | f1261b349e875b842745b63258c3e338cb1fe3bf (patch) | |
tree | b5d402b3e80818cde2c079a42249f3dcb9732247 /support/texlab/src/server.rs | |
parent | 58aa1ac09b1d9e4769d0a0661cf12e2b2db41b14 (diff) |
CTAN sync 202105230300
Diffstat (limited to 'support/texlab/src/server.rs')
-rw-r--r-- | support/texlab/src/server.rs | 1369 |
1 files changed, 727 insertions, 642 deletions
diff --git a/support/texlab/src/server.rs b/support/texlab/src/server.rs index 9c5e1b857b..5a78d5e2e2 100644 --- a/support/texlab/src/server.rs +++ b/support/texlab/src/server.rs @@ -1,131 +1,113 @@ -#[cfg(feature = "citation")] -use crate::citeproc::render_citation; +use std::{ + path::PathBuf, + sync::{Arc, Mutex}, + thread, +}; + +use anyhow::Result; +use cancellation::{CancellationToken, CancellationTokenSource}; +use crossbeam_channel::Sender; +use log::{info, warn}; +use lsp_server::{Connection, ErrorCode, Message, RequestId}; +use lsp_types::{ + notification::{ + Cancel, DidChangeConfiguration, DidChangeTextDocument, DidOpenTextDocument, + DidSaveTextDocument, LogMessage, PublishDiagnostics, + }, + request::{ + DocumentLinkRequest, FoldingRangeRequest, Formatting, GotoDefinition, PrepareRenameRequest, + References, Rename, SemanticTokensRangeRequest, + }, + *, +}; +use notification::DidCloseTextDocument; +use request::{ + Completion, DocumentHighlightRequest, DocumentSymbolRequest, HoverRequest, + ResolveCompletionItem, WorkspaceSymbol, +}; +use serde::Serialize; +use threadpool::ThreadPool; use crate::{ - build::BuildProvider, - completion::{CompletionItemData, CompletionProvider, COMPLETION_LIMIT}, - 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}, + client::send_notification, + component_db::COMPONENT_DATABASE, + config::{pull_config, push_config, register_config_capability}, + create_workspace_full, + diagnostics::{DiagnosticsDebouncer, DiagnosticsManager, DiagnosticsMessage}, + dispatch::{NotificationDispatcher, RequestDispatcher}, + distro::Distribution, + features::{ + build_document, find_all_references, find_document_highlights, find_document_links, + find_document_symbols, find_foldings, find_hover, find_workspace_symbols, + format_source_code, goto_definition, prepare_rename_all, rename_all, BuildParams, + BuildResult, FeatureRequest, ForwardSearchResult, + }, + req_queue::{IncomingData, ReqQueue}, + DocumentLanguage, ServerContext, Uri, Workspace, WorkspaceSource, }; -use async_trait::async_trait; -use chashmap::CHashMap; -use futures::lock::Mutex; -use jsonrpc::{server::Result, Middleware}; -use jsonrpc_derive::{jsonrpc_method, jsonrpc_server}; -use log::{debug, error, info, warn}; -use once_cell::sync::{Lazy, OnceCell}; -use std::{mem, path::PathBuf, sync::Arc}; - -pub struct LatexLspServer<C> { - distro: Arc<dyn Distribution>, - client: Arc<C>, - client_capabilities: OnceCell<Arc<ClientCapabilities>>, - client_info: OnceCell<Option<ClientInfo>>, - current_dir: Arc<PathBuf>, - config_manager: OnceCell<ConfigManager<C>>, - action_manager: ActionManager, - workspace: Workspace, - build_provider: BuildProvider<C>, - completion_provider: CompletionProvider, - definition_provider: DefinitionProvider, - folding_provider: FoldingProvider, - highlight_provider: HighlightProvider, - 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>, + +pub struct Server { + connection: Connection, + context: Arc<ServerContext>, + req_queue: Arc<Mutex<ReqQueue>>, + workspace: Arc<dyn Workspace>, + static_debouncer: DiagnosticsDebouncer, + chktex_debouncer: DiagnosticsDebouncer, + pool: ThreadPool, + load_resolver: bool, } -#[jsonrpc_server] -impl<C: LspClient + Send + Sync + 'static> LatexLspServer<C> { - pub fn new(distro: Arc<dyn Distribution>, client: Arc<C>, current_dir: Arc<PathBuf>) -> Self { - let workspace = Workspace::new(distro.clone(), Arc::clone(¤t_dir)); - Self { - distro, - client: Arc::clone(&client), - client_capabilities: OnceCell::new(), - client_info: OnceCell::new(), - current_dir, - config_manager: OnceCell::new(), - action_manager: ActionManager::default(), +impl Server { + pub fn with_connection( + connection: Connection, + current_dir: PathBuf, + load_resolver: bool, + ) -> Result<Self> { + let context = Arc::new(ServerContext::new(current_dir)); + let req_queue = Arc::default(); + let workspace = Arc::new(create_workspace_full(Arc::clone(&context))?); + let diag_manager = Arc::new(Mutex::new(DiagnosticsManager::default())); + + let static_debouncer = + create_static_debouncer(Arc::clone(&diag_manager), &connection, Arc::clone(&context)); + + let chktex_debouncer = + create_chktex_debouncer(diag_manager, &connection, Arc::clone(&context)); + + Ok(Self { + connection, + context, + req_queue, workspace, - build_provider: BuildProvider::new(client), - completion_provider: CompletionProvider::new(), - definition_provider: DefinitionProvider::new(), - folding_provider: FoldingProvider::new(), - highlight_provider: HighlightProvider::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(), - } - } - - fn client_capabilities(&self) -> Arc<ClientCapabilities> { - Arc::clone( - self.client_capabilities - .get() - .expect("initialize has not been called"), - ) - } - - fn config_manager(&self) -> &ConfigManager<C> { - self.config_manager - .get() - .expect("initialize has not been called") + static_debouncer, + chktex_debouncer, + pool: threadpool::Builder::new().build(), + load_resolver, + }) } - #[jsonrpc_method("initialize", kind = "request")] - pub async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> { - self.client_capabilities - .set(Arc::new(params.capabilities)) - .expect("initialize was called two times"); - - self.client_info - .set(params.client_info) - .expect("initialize was called two times"); - - let _ = self.config_manager.set(ConfigManager::new( - Arc::clone(&self.client), - self.client_capabilities(), - )); - - let capabilities = ServerCapabilities { + fn capabilities(&self) -> ServerCapabilities { + ServerCapabilities { text_document_sync: Some(TextDocumentSyncCapability::Options( TextDocumentSyncOptions { open_close: Some(true), change: Some(TextDocumentSyncKind::Full), will_save: None, will_save_wait_until: None, - save: Some(SaveOptions { + save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions { include_text: Some(false), - }), + })), }, )), - hover_provider: Some(true), + document_link_provider: Some(DocumentLinkOptions { + resolve_provider: Some(false), + work_done_progress_options: WorkDoneProgressOptions::default(), + }), + folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)), + definition_provider: Some(OneOf::Left(true)), + references_provider: Some(OneOf::Left(true)), + hover_provider: Some(HoverProviderCapability::Simple(true)), + #[cfg(feature = "completion")] completion_provider: Some(CompletionOptions { resolve_provider: Some(true), trigger_characters: Some(vec![ @@ -138,625 +120,728 @@ impl<C: LspClient + Send + Sync + 'static> LatexLspServer<C> { ]), ..CompletionOptions::default() }), - definition_provider: Some(true), - references_provider: Some(true), - document_highlight_provider: Some(true), - document_symbol_provider: Some(true), - workspace_symbol_provider: Some(true), - document_formatting_provider: Some(true), - rename_provider: Some(RenameProviderCapability::Options(RenameOptions { + document_symbol_provider: Some(OneOf::Left(true)), + workspace_symbol_provider: Some(OneOf::Left(true)), + rename_provider: Some(OneOf::Right(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(), - }), - folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)), + document_highlight_provider: Some(OneOf::Left(true)), + document_formatting_provider: Some(OneOf::Left(true)), + #[cfg(feature = "semantic")] + semantic_tokens_provider: Some( + SemanticTokensServerCapabilities::SemanticTokensOptions(SemanticTokensOptions { + full: None, + range: Some(true), + legend: SemanticTokensLegend { + token_types: crate::features::legend::SUPPORTED_TYPES.to_vec(), + token_modifiers: crate::features::legend::SUPPORTED_MODIFIERS.to_vec(), + }, + work_done_progress_options: WorkDoneProgressOptions::default(), + }), + ), ..ServerCapabilities::default() - }; + } + } + + fn initialize(&mut self) -> Result<()> { + let (id, params) = self.connection.initialize_start()?; + let params: InitializeParams = serde_json::from_value(params)?; - Lazy::force(&COMPONENT_DATABASE); - Ok(InitializeResult { - capabilities, + *self.context.client_capabilities.lock().unwrap() = params.capabilities; + *self.context.client_info.lock().unwrap() = params.client_info; + + let result = InitializeResult { + capabilities: self.capabilities(), server_info: Some(ServerInfo { name: "TexLab".to_owned(), version: Some(env!("CARGO_PKG_VERSION").to_owned()), }), - }) + }; + self.connection + .initialize_finish(id, serde_json::to_value(result)?)?; + + let cx = Arc::clone(&self.context); + if self.load_resolver { + self.pool.execute(move || { + let distro = Distribution::detect(); + info!("Detected distribution: {}", distro.kind); + *cx.resolver.lock().unwrap() = distro.resolver; + }); + } + + self.register_diagnostics_handler(); + + let req_queue = Arc::clone(&self.req_queue); + let sender = self.connection.sender.clone(); + let context = Arc::clone(&self.context); + self.pool.execute(move || { + register_config_capability(&req_queue, &sender, &context.client_capabilities); + pull_config( + &req_queue, + &sender, + &context.options, + &context.client_capabilities.lock().unwrap(), + ); + }); + Ok(()) } - #[jsonrpc_method("initialized", kind = "notification")] - 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; + fn register_diagnostics_handler(&mut self) { + let sender = self.static_debouncer.sender.clone(); + self.workspace + .register_open_handler(Arc::new(move |workspace, document| { + let message = DiagnosticsMessage::Analyze { + workspace, + document, + }; + sender.send(message).unwrap(); + })); + } + + fn register_incoming_request(&self, id: RequestId) -> Arc<CancellationToken> { + let token_source = CancellationTokenSource::new(); + let token = Arc::clone(token_source.token()); + let mut req_queue = self.req_queue.lock().unwrap(); + req_queue + .incoming + .register(id.clone(), IncomingData { token_source }); + token + } + + fn cancel(&self, params: CancelParams) -> Result<()> { + let id = match params.id { + NumberOrString::Number(id) => RequestId::from(id), + NumberOrString::String(id) => RequestId::from(id), + }; + + let mut req_queue = self.req_queue.lock().unwrap(); + if let Some(data) = req_queue.incoming.complete(id.clone()) { + data.token_source.cancel(); + } + + Ok(()) } - #[jsonrpc_method("shutdown", kind = "request")] - pub async fn shutdown(&self, _params: ()) -> Result<()> { + fn did_change_configuration(&self, params: DidChangeConfigurationParams) -> Result<()> { + push_config(&self.context.options, params.settings); Ok(()) } - #[jsonrpc_method("exit", kind = "notification")] - pub async fn exit(&self, _params: ()) {} - - #[jsonrpc_method("$/cancelRequest", kind = "notification")] - pub async fn cancel_request(&self, _params: CancelParams) {} - - #[jsonrpc_method("textDocument/didOpen", kind = "notification")] - 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; - 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 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; - } - 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 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::RunLinter( - params.text_document.uri.into(), - LintReason::Save, - )) - .await; - self.action_manager.push(Action::PublishDiagnostics).await; - } - - #[jsonrpc_method("textDocument/didClose", kind = "notification")] - 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 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 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, + fn did_open(&self, params: DidOpenTextDocumentParams) -> Result<()> { + let language_id = ¶ms.text_document.language_id; + let language = DocumentLanguage::by_language_id(language_id); + let document = self.workspace.open( + Arc::new(params.text_document.uri.into()), + params.text_document.text, + language.unwrap_or(DocumentLanguage::Latex), + WorkspaceSource::Client, ); - let items = self.completion_provider.execute(&req).await; - let is_incomplete = if self - .client_info - .get() - .and_then(|info| info.as_ref()) - .map(|info| info.name.as_str()) - .unwrap_or_default() - == "vscode" + let should_lint = { self.context.options.read().unwrap().chktex.on_open_and_save }; + if let Some(document) = self + .workspace + .get(document.uri.as_ref()) + .filter(|_| should_lint) { - true - } else { - items.len() >= COMPLETION_LIMIT + self.chktex_debouncer + .sender + .send(DiagnosticsMessage::Analyze { + workspace: Arc::clone(&self.workspace), + document, + })?; }; + Ok(()) + } - Ok(CompletionList { - is_incomplete, - items, - }) + fn did_change(&self, mut params: DidChangeTextDocumentParams) -> Result<()> { + let uri = params.text_document.uri.into(); + assert_eq!(params.content_changes.len(), 1); + let text = params.content_changes.pop().unwrap().text; + let language = self + .workspace + .get(&uri) + .map(|document| document.data.language()) + .unwrap_or(DocumentLanguage::Latex); + + let document = self + .workspace + .open(Arc::new(uri), text, language, WorkspaceSource::Client); + + let should_lint = { self.context.options.read().unwrap().chktex.on_edit }; + if let Some(document) = self + .workspace + .get(document.uri.as_ref()) + .filter(|_| should_lint) + { + self.chktex_debouncer + .sender + .send(DiagnosticsMessage::Analyze { + workspace: Arc::clone(&self.workspace), + document, + })?; + }; + + Ok(()) } - #[jsonrpc_method("completionItem/resolve", kind = "request")] - pub async fn completion_resolve(&self, mut item: CompletionItem) -> Result<CompletionItem> { - let data: CompletionItemData = serde_json::from_value(item.data.clone().unwrap()).unwrap(); - match data { - CompletionItemData::Package | CompletionItemData::Class => { - item.documentation = COMPONENT_DATABASE - .documentation(&item.label) - .map(Documentation::MarkupContent); - } - #[cfg(feature = "citation")] - CompletionItemData::Citation { uri, key } => { - 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); - } - } - } - _ => {} + fn did_save(&self, params: DidSaveTextDocumentParams) -> Result<()> { + let uri = params.text_document.uri.into(); + let should_lint = { self.context.options.read().unwrap().chktex.on_open_and_save }; + if let Some(document) = self.workspace.get(&uri).filter(|_| should_lint) { + self.chktex_debouncer + .sender + .send(DiagnosticsMessage::Analyze { + workspace: Arc::clone(&self.workspace), + document, + })?; }; - Ok(item) + Ok(()) } - #[jsonrpc_method("textDocument/hover", kind = "request")] - pub async fn hover(&self, params: TextDocumentPositionParams) -> Result<Option<Hover>> { - let req = self - .make_feature_request(params.text_document.as_uri(), params) - .await?; + fn did_close(&self, params: DidCloseTextDocumentParams) -> Result<()> { + let uri = params.text_document.uri.into(); + self.workspace.close(&uri); + Ok(()) + } - self.last_position_by_uri - .insert(req.current().uri.clone(), req.params.position); + fn feature_request<P>(&self, uri: Arc<Uri>, params: P) -> Option<FeatureRequest<P>> { + let req_queue = Arc::clone(&self.req_queue); + let sender = self.connection.sender.clone(); + let cx = Arc::clone(&self.context); + self.pool.execute(move || { + pull_config( + &req_queue, + &sender, + &cx.options, + &cx.client_capabilities.lock().unwrap(), + ); + }); + + Some(FeatureRequest { + context: Arc::clone(&self.context), + params, + workspace: Arc::clone(&self.workspace), + subset: self.workspace.subset(uri)?, + }) + } - Ok(self.hover_provider.execute(&req).await) + fn send_feature_error(&self, id: RequestId) -> Result<()> { + let resp = lsp_server::Response::new_err( + id, + ErrorCode::InternalError as i32, + "unknown document URI".to_string(), + ); + self.connection.sender.send(resp.into())?; + Ok(()) } - #[jsonrpc_method("textDocument/definition", kind = "request")] - pub async fn definition( + fn handle_feature_request<P, R, H>( &self, - params: TextDocumentPositionParams, - ) -> Result<DefinitionResponse> { - 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( - results - .into_iter() - .map(|link| Location::new(link.target_uri, link.target_selection_range)) - .collect(), - ) + id: RequestId, + params: P, + uri: Arc<Uri>, + token: &Arc<CancellationToken>, + handler: H, + ) -> Result<()> + where + P: Send + 'static, + R: Serialize, + H: FnOnce(FeatureRequest<P>, &CancellationToken) -> R + Send + 'static, + { + match self.feature_request(uri, params) { + Some(req) => { + let sender = self.connection.sender.clone(); + let token = Arc::clone(token); + self.pool.execute(move || { + let result = handler(req, &token); + if token.is_canceled() { + sender.send(cancel_response(id).into()).unwrap(); + } else { + sender + .send(lsp_server::Response::new_ok(id, result).into()) + .unwrap(); + } + }); + } + None => { + self.send_feature_error(id)?; + } }; - - Ok(response) + Ok(()) } - #[jsonrpc_method("textDocument/references", kind = "request")] - pub async fn references(&self, params: ReferenceParams) -> Result<Vec<Location>> { - let req = self - .make_feature_request(params.text_document_position.as_uri(), params) - .await?; - Ok(self.reference_provider.execute(&req).await) + fn document_link( + &self, + id: RequestId, + params: DocumentLinkParams, + token: &Arc<CancellationToken>, + ) -> Result<()> { + let uri = Arc::new(params.text_document.uri.clone().into()); + self.handle_feature_request(id, params, uri, token, find_document_links)?; + Ok(()) } - #[jsonrpc_method("textDocument/documentHighlight", kind = "request")] - pub async fn document_highlight( + fn document_symbols( &self, - params: TextDocumentPositionParams, - ) -> Result<Vec<DocumentHighlight>> { - let req = self - .make_feature_request(params.text_document.as_uri(), params) - .await?; - Ok(self.highlight_provider.execute(&req).await) + id: RequestId, + params: DocumentSymbolParams, + token: &Arc<CancellationToken>, + ) -> Result<()> { + let uri = Arc::new(params.text_document.uri.clone().into()); + self.handle_feature_request(id, params, uri, token, find_document_symbols)?; + Ok(()) } - #[jsonrpc_method("workspace/symbol", kind = "request")] - pub async fn workspace_symbol( + fn workspace_symbols( &self, + id: RequestId, params: WorkspaceSymbolParams, - ) -> Result<Vec<SymbolInformation>> { - 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( + token: &Arc<CancellationToken>, + ) -> Result<()> { + let sender = self.connection.sender.clone(); + let workspace = Arc::clone(&self.workspace); + let token = Arc::clone(token); + self.pool.execute(move || { + let result = find_workspace_symbols(workspace.as_ref(), ¶ms, &token); + if token.is_canceled() { + sender.send(cancel_response(id).into()).unwrap(); + } else { + sender + .send(lsp_server::Response::new_ok(id, result).into()) + .unwrap(); + } + }); + Ok(()) + } + + #[cfg(feature = "completion")] + fn completion( &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(), + id: RequestId, + params: CompletionParams, + token: &Arc<CancellationToken>, + ) -> Result<()> { + let uri = Arc::new( + params + .text_document_position + .text_document + .uri + .clone() + .into(), ); - Ok(response) - } - - #[jsonrpc_method("textDocument/documentLink", kind = "request")] - pub async fn document_link(&self, params: DocumentLinkParams) -> Result<Vec<DocumentLink>> { - 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 req = self - .make_feature_request(params.text_document.as_uri(), params) - .await?; - let mut edits = Vec::new(); - 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 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)); - } + self.handle_feature_request(id, params, uri, token, crate::features::complete)?; + Ok(()) + } + + #[cfg(feature = "completion")] + fn completion_resolve( + &self, + id: RequestId, + mut item: CompletionItem, + token: &Arc<CancellationToken>, + ) -> Result<()> { + let sender = self.connection.sender.clone(); + let token = Arc::clone(token); + let workspace = Arc::clone(&self.workspace); + self.pool.execute(move || { + match serde_json::from_value(item.data.clone().unwrap()).unwrap() { + crate::features::CompletionItemData::Package + | crate::features::CompletionItemData::Class => { + item.documentation = COMPONENT_DATABASE + .documentation(&item.label) + .map(Documentation::MarkupContent); + } + #[cfg(feature = "citeproc")] + crate::features::CompletionItemData::Citation { uri, key } => { + if let Some(document) = workspace.get(&uri) { + if let Some(data) = document.data.as_bibtex() { + let markup = crate::citation::render_citation(&data.root, &key); + item.documentation = markup.map(Documentation::MarkupContent); } } - BibtexFormatter::Latexindent => { - Self::run_latexindent(&req.current().text, "bib", &mut edits).await; - } } + _ => {} + }; + + drop(workspace); + if token.is_canceled() { + sender.send(cancel_response(id).into()).unwrap(); + } else { + sender + .send(lsp_server::Response::new_ok(id, item).into()) + .unwrap(); } - } - Ok(edits) + }); + Ok(()) } - 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); - } - } + fn folding_range( + &self, + id: RequestId, + params: FoldingRangeParams, + token: &Arc<CancellationToken>, + ) -> Result<()> { + let uri = Arc::new(params.text_document.uri.clone().into()); + self.handle_feature_request(id, params, uri, token, find_foldings)?; + Ok(()) } - #[jsonrpc_method("textDocument/prepareRename", kind = "request")] - pub async fn prepare_rename( + fn references( &self, - params: TextDocumentPositionParams, - ) -> Result<Option<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 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 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 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?; - } + id: RequestId, + params: ReferenceParams, + token: &Arc<CancellationToken>, + ) -> Result<()> { + let uri = Arc::new( + params + .text_document_position + .text_document + .uri + .clone() + .into(), + ); + self.handle_feature_request(id, params, uri, token, find_all_references)?; + Ok(()) + } + + fn hover( + &self, + id: RequestId, + params: HoverParams, + token: &Arc<CancellationToken>, + ) -> Result<()> { + let uri = Arc::new( + params + .text_document_position_params + .text_document + .uri + .clone() + .into(), + ); + self.handle_feature_request(id, params, uri, token, find_hover)?; + Ok(()) + } - Ok(res) + fn goto_definition( + &self, + id: RequestId, + params: GotoDefinitionParams, + token: &Arc<CancellationToken>, + ) -> Result<()> { + let uri = Arc::new( + params + .text_document_position_params + .text_document + .uri + .clone() + .into(), + ); + self.handle_feature_request(id, params, uri, token, goto_definition)?; + Ok(()) } - #[jsonrpc_method("textDocument/forwardSearch", kind = "request")] - pub async fn forward_search( + fn prepare_rename( &self, + id: RequestId, params: TextDocumentPositionParams, - ) -> Result<ForwardSearchResult> { - 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; + token: &Arc<CancellationToken>, + ) -> Result<()> { + let uri = Arc::new(params.text_document.uri.clone().into()); + self.handle_feature_request(id, params, uri, token, prepare_rename_all)?; Ok(()) } - 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::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 rename( + &self, + id: RequestId, + params: RenameParams, + token: &Arc<CancellationToken>, + ) -> Result<()> { + let uri = Arc::new( + params + .text_document_position + .text_document + .uri + .clone() + .into(), + ); + self.handle_feature_request(id, params, uri, token, rename_all)?; + 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 + fn document_highlight( + &self, + id: RequestId, + params: DocumentHighlightParams, + token: &Arc<CancellationToken>, + ) -> Result<()> { + let uri = Arc::new( + params + .text_document_position_params + .text_document + .uri + .clone() + .into(), + ); + self.handle_feature_request(id, params, uri, token, find_document_highlights)?; + Ok(()) } - async fn update_build_diagnostics(&self) { - let snapshot = self.workspace.get().await; - let options = self.config_manager().get().await; + fn formatting( + &self, + id: RequestId, + params: DocumentFormattingParams, + token: &Arc<CancellationToken>, + ) -> Result<()> { + let uri = Arc::new(params.text_document.uri.clone().into()); + self.handle_feature_request(id, params, uri, token, format_source_code)?; + Ok(()) + } - 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, doc.uri.as_str()) - } - } - } - } - } + #[cfg(feature = "semantic")] + fn semantic_tokens_range( + &self, + id: RequestId, + params: SemanticTokensRangeParams, + token: &Arc<CancellationToken>, + ) -> Result<()> { + let uri = Arc::new(params.text_document.uri.clone().into()); + self.handle_feature_request( + id, + params, + uri, + token, + crate::features::find_semantic_tokens_range, + )?; + Ok(()) } - 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; - } + #[cfg(not(feature = "semantic"))] + fn semantic_tokens_range( + &self, + _id: RequestId, + _params: SemanticTokensRangeParams, + _token: &Arc<CancellationToken>, + ) -> Result<()> { + Ok(()) + } - 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'" + fn build( + &self, + id: RequestId, + params: BuildParams, + token: &Arc<CancellationToken>, + ) -> Result<()> { + let uri = Arc::new(params.text_document.uri.clone().into()); + let lsp_sender = self.connection.sender.clone(); + self.handle_feature_request(id, params, uri, token, |request, token| { + let (log_sender, log_receiver) = crossbeam_channel::unbounded(); + + thread::spawn(move || { + for message in &log_receiver { + send_notification::<LogMessage>( + &lsp_sender, + LogMessageParams { + message, + typ: MessageType::Log, + }, + ) + .unwrap(); } - }; - let params = ShowMessageParams { - message: message.into(), - typ: MessageType::Error, - }; - self.client.show_message(params).await; - }; + }); + + build_document(request, token, log_sender) + })?; + Ok(()) } -} -#[async_trait] -impl<C: LspClient + Send + Sync + 'static> Middleware for LatexLspServer<C> { - 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; - } + fn forward_search( + &self, + id: RequestId, + params: TextDocumentPositionParams, + token: &Arc<CancellationToken>, + ) -> Result<()> { + let uri = Arc::new(params.text_document.uri.clone().into()); + self.handle_feature_request( + id, + params, + uri, + token, + crate::features::execute_forward_search, + )?; + Ok(()) } - async fn after_message(&self) { - self.update_build_diagnostics().await; - 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 => { - self.pull_configuration().await; - } - Action::DetectRoot(uri) => { - let options = self.config_manager().get().await; - let _ = self.workspace.detect_root(&uri, &options).await; - } - Action::PublishDiagnostics => { - let snapshot = self.workspace.get().await; - for doc in &snapshot.0 { - let diagnostics = self.diagnostics_manager.get(doc).await; - let params = PublishDiagnosticsParams { - uri: doc.uri.clone().into(), - diagnostics, - version: None, - }; - self.client.publish_diagnostics(params).await; + fn process_messages(&self) -> Result<()> { + for msg in &self.connection.receiver { + match msg { + Message::Request(request) => { + if self.connection.handle_shutdown(&request)? { + return Ok(()); } - } - 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(); + + let token = self.register_incoming_request(request.id.clone()); + if let Some(response) = RequestDispatcher::new(request) + .on::<DocumentLinkRequest, _>(|id, params| { + self.document_link(id, params, &token) + })? + .on::<FoldingRangeRequest, _>(|id, params| { + self.folding_range(id, params, &token) + })? + .on::<References, _>(|id, params| self.references(id, params, &token))? + .on::<HoverRequest, _>(|id, params| self.hover(id, params, &token))? + .on::<DocumentSymbolRequest, _>(|id, params| { + self.document_symbols(id, params, &token) + })? + .on::<WorkspaceSymbol, _>(|id, params| { + self.workspace_symbols(id, params, &token) + })? + .on::<Completion, _>(|id, params| { + #[cfg(feature = "completion")] + self.completion(id, params, &token)?; + Ok(()) + })? + .on::<ResolveCompletionItem, _>(|id, params| { + #[cfg(feature = "completion")] + self.completion_resolve(id, params, &token)?; + Ok(()) + })? + .on::<GotoDefinition, _>(|id, params| { + self.goto_definition(id, params, &token) + })? + .on::<PrepareRenameRequest, _>(|id, params| { + self.prepare_rename(id, params, &token) + })? + .on::<Rename, _>(|id, params| self.rename(id, params, &token))? + .on::<DocumentHighlightRequest, _>(|id, params| { + self.document_highlight(id, params, &token) + })? + .on::<Formatting, _>(|id, params| self.formatting(id, params, &token))? + .on::<BuildRequest, _>(|id, params| self.build(id, params, &token))? + .on::<ForwardSearchRequest, _>(|id, params| { + self.forward_search(id, params, &token) + })? + .on::<SemanticTokensRangeRequest, _>(|id, params| { + self.semantic_tokens_range(id, params, &token) + })? + .default() + { + self.connection.sender.send(response.into())?; } } - Action::RunLinter(uri, reason) => { - let options = self - .config_manager() - .get() - .await - .latex - .and_then(|opts| opts.lint) - .unwrap_or_default(); - - let should_lint = match reason { - LintReason::Change => options.on_change(), - LintReason::Save => options.on_save() || options.on_change(), + Message::Notification(notification) => { + NotificationDispatcher::new(notification) + .on::<Cancel, _>(|params| self.cancel(params))? + .on::<DidChangeConfiguration, _>(|params| { + self.did_change_configuration(params) + })? + .on::<DidOpenTextDocument, _>(|params| self.did_open(params))? + .on::<DidChangeTextDocument, _>(|params| self.did_change(params))? + .on::<DidSaveTextDocument, _>(|params| self.did_save(params))? + .on::<DidCloseTextDocument, _>(|params| self.did_close(params))? + .default(); + } + Message::Response(response) => { + let mut req_queue = self.req_queue.lock().unwrap(); + let data = req_queue.outgoing.complete(response.id); + let result = match response.result { + Some(result) => Ok(result), + None => Err(response + .error + .expect("response without result or error received")), }; - - if should_lint { - 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; - } - } - } + data.sender.send(result)?; } } } + Ok(()) + } + + pub fn run(mut self) -> Result<()> { + self.initialize()?; + self.process_messages()?; + drop(self.static_debouncer); + drop(self.chktex_debouncer); + self.pool.join(); + Ok(()) } } -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -enum LintReason { - Change, - Save, +fn create_static_debouncer( + manager: Arc<Mutex<DiagnosticsManager>>, + conn: &Connection, + context: Arc<ServerContext>, +) -> DiagnosticsDebouncer { + let sender = conn.sender.clone(); + DiagnosticsDebouncer::launch(context, move |workspace, document| { + let mut manager = manager.lock().unwrap(); + manager.update_static(workspace.as_ref(), Arc::clone(&document.uri)); + if let Err(why) = publish_diagnostics(&sender, workspace.as_ref(), &manager) { + warn!("Failed to publish diagnostics: {}", why); + } + }) } -#[derive(Debug, PartialEq, Clone)] -enum Action { - LoadDistribution, - RegisterCapabilities, - PullConfiguration, - DetectRoot(Uri), - PublishDiagnostics, - Build(Uri), - RunLinter(Uri, LintReason), +fn create_chktex_debouncer( + manager: Arc<Mutex<DiagnosticsManager>>, + conn: &Connection, + context: Arc<ServerContext>, +) -> DiagnosticsDebouncer { + let sender = conn.sender.clone(); + DiagnosticsDebouncer::launch(Arc::clone(&context), move |workspace, document| { + let options = { context.options.read().unwrap().clone() }; + let mut manager = manager.lock().unwrap(); + manager.update_chktex(workspace.as_ref(), Arc::clone(&document.uri), &options); + if let Err(why) = publish_diagnostics(&sender, workspace.as_ref(), &manager) { + warn!("Failed to publish diagnostics: {}", why); + } + }) } -#[derive(Debug, Default)] -struct ActionManager { - actions: Mutex<Vec<Action>>, +fn publish_diagnostics( + sender: &Sender<lsp_server::Message>, + workspace: &dyn Workspace, + diag_manager: &DiagnosticsManager, +) -> Result<()> { + for document in workspace.documents() { + let diagnostics = diag_manager.publish(Arc::clone(&document.uri)); + send_notification::<PublishDiagnostics>( + sender, + PublishDiagnosticsParams { + uri: document.uri.as_ref().clone().into(), + version: None, + diagnostics, + }, + )?; + } + Ok(()) } -impl ActionManager { - pub async fn push(&self, action: Action) { - let mut actions = self.actions.lock().await; - actions.push(action); - } +fn cancel_response(id: RequestId) -> lsp_server::Response { + lsp_server::Response::new_err( + id, + ErrorCode::RequestCanceled as i32, + "canceled by client".to_string(), + ) +} - pub async fn take(&self) -> Vec<Action> { - let mut actions = self.actions.lock().await; - mem::replace(&mut *actions, Vec::new()) - } +struct BuildRequest; + +impl lsp_types::request::Request for BuildRequest { + type Params = BuildParams; + + type Result = BuildResult; + + const METHOD: &'static str = "textDocument/build"; +} + +struct ForwardSearchRequest; + +impl lsp_types::request::Request for ForwardSearchRequest { + type Params = TextDocumentPositionParams; + + type Result = ForwardSearchResult; + + const METHOD: &'static str = "textDocument/forwardSearch"; } |