diff options
author | Norbert Preining <norbert@preining.info> | 2019-12-03 03:01:24 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2019-12-03 03:01:24 +0000 |
commit | b8d4bb76703bcb15578e2b23c5d256532180b894 (patch) | |
tree | bedd1df7a00521a2bd986b3c0289d6556a59e39b /support/texlab/src/server.rs | |
parent | 02e4625a78a5029e8b5dc2a4ec70193b232f497e (diff) |
CTAN sync 201912030301
Diffstat (limited to 'support/texlab/src/server.rs')
-rw-r--r-- | support/texlab/src/server.rs | 629 |
1 files changed, 629 insertions, 0 deletions
diff --git a/support/texlab/src/server.rs b/support/texlab/src/server.rs new file mode 100644 index 0000000000..23bf973a3a --- /dev/null +++ b/support/texlab/src/server.rs @@ -0,0 +1,629 @@ +use crate::action::{Action, ActionManager, LintReason}; +use crate::build::*; +use crate::capabilities::ClientCapabilitiesExt; +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 futures::lock::Mutex; +use futures_boxed::boxed; +use jsonrpc::server::Middleware; +use jsonrpc::server::Result; +use jsonrpc_derive::{jsonrpc_method, jsonrpc_server}; +use log::*; +use lsp_types::*; +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; + +pub struct LatexLspServer<C> { + distribution: Arc<Box<dyn tex::Distribution>>, + build_manager: BuildManager<C>, + client: Arc<C>, + client_capabilities: OnceCell<Arc<ClientCapabilities>>, + workspace_manager: WorkspaceManager, + action_manager: ActionManager, + diagnostics_manager: Mutex<DiagnosticsManager>, + 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, +} + +#[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, + client_capabilities: OnceCell::new(), + workspace_manager: WorkspaceManager::default(), + action_manager: ActionManager::default(), + diagnostics_manager: Mutex::new(DiagnosticsManager::default()), + 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(), + } + } + + 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 + } + + 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 + } + + #[jsonrpc_method("initialize", kind = "request")] + pub async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> { + self.client_capabilities + .set(Arc::new(params.capabilities)) + .unwrap(); + let capabilities = 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 { + include_text: Some(false), + }), + }, + )), + hover_provider: Some(true), + completion_provider: Some(CompletionOptions { + resolve_provider: Some(true), + trigger_characters: Some(vec![ + "\\".to_owned(), + "{".to_owned(), + "}".to_owned(), + "@".to_owned(), + "/".to_owned(), + " ".to_owned(), + ]), + }), + 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), + })), + document_link_provider: Some(DocumentLinkOptions { + resolve_provider: Some(false), + }), + color_provider: None, + folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)), + execute_command_provider: None, + workspace: None, + selection_range_provider: None, + }; + + Lazy::force(&DATABASE); + Ok(InitializeResult { capabilities }) + } + + #[jsonrpc_method("initialized", kind = "notification")] + pub fn initialized(&self, _params: InitializedParams) { + self.action_manager.push(Action::CheckInstalledDistribution); + self.action_manager.push(Action::PublishDiagnostics); + } + + #[jsonrpc_method("shutdown", kind = "request")] + pub async fn shutdown(&self, _params: ()) -> Result<()> { + Ok(()) + } + + #[jsonrpc_method("exit", kind = "notification")] + pub fn exit(&self, _params: ()) {} + + #[jsonrpc_method("$/cancelRequest", kind = "notification")] + pub fn cancel_request(&self, _params: CancelParams) {} + + #[jsonrpc_method("textDocument/didOpen", kind = "notification")] + pub 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); + } + + #[jsonrpc_method("textDocument/didChange", kind = "notification")] + pub fn did_change(&self, params: DidChangeTextDocumentParams) { + for change in params.content_changes { + let uri = params.text_document.uri.clone(); + self.workspace_manager.update(uri.into(), change.text); + } + self.action_manager.push(Action::RunLinter( + params.text_document.uri.into(), + LintReason::Change, + )); + self.action_manager.push(Action::PublishDiagnostics); + } + + #[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); + self.action_manager + .push(Action::Build(params.text_document.uri.into())); + } + + #[jsonrpc_method("textDocument/didClose", kind = "notification")] + pub fn did_close(&self, _params: DidCloseTextDocumentParams) {} + + #[jsonrpc_method("window/workDoneProgress/cancel", kind = "notification")] + pub fn work_done_progress_cancel(&self, params: WorkDoneProgressCancelParams) { + self.action_manager.push(Action::CancelBuild(params.token)); + } + + #[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; + Ok(CompletionList { + is_incomplete: true, + items, + }) + } + + #[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 = DATABASE + .documentation(&item.label) + .map(Documentation::MarkupContent); + } + 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 markup = render_citation(&tree, &key); + item.documentation = markup.map(Documentation::MarkupContent); + } + } + } + _ => {} + }; + Ok(item) + } + + #[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) + } + + #[jsonrpc_method("textDocument/definition", kind = "request")] + pub async fn definition( + &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() { + DefinitionResponse::LocationLinks(results) + } else { + DefinitionResponse::Locations( + results + .into_iter() + .map(|link| Location::new(link.target_uri, link.target_selection_range)) + .collect(), + ) + }; + + Ok(response) + } + + #[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) + } + + #[jsonrpc_method("textDocument/documentHighlight", kind = "request")] + pub async fn document_highlight( + &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) + } + + #[jsonrpc_method("workspace/symbol", kind = "request")] + pub async fn workspace_symbol( + &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, ¶ms).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, + symbols.into_iter().map(Into::into).collect(), + ); + Ok(response) + } + + #[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) + } + + #[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 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, + }; + + 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)); + } + } + } + Ok(edits) + } + + #[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) + } + + #[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) + } + + #[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) + } + + #[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) + } + + #[jsonrpc_method("textDocument/forwardSearch", kind = "request")] + pub async fn forward_search( + &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() + } + } + } + + 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 { + 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; + } + } + } + + 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 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), + Ok(false) => (), + Err(why) => warn!( + "Unable to read log file ({}): {}", + why, + document.uri.as_str() + ), + } + } + } + } + } +} + +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)); + } + } + + #[boxed] + async fn after_message(&self) { + self.update_build_diagnostics().await; + for action in self.action_manager.take() { + 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::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())); + } + } + } + } + } + } + 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 params = PublishDiagnosticsParams { + uri: document.uri.clone().into(), + diagnostics, + }; + self.client.publish_diagnostics(params).await; + } + } + Action::RunLinter(uri, reason) => { + let config: LatexLintOptions = self.configuration("latex.lint").await; + let should_lint = match reason { + LintReason::Change => config.on_change(), + LintReason::Save => config.on_save(), + }; + 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); + } + } + } + } + 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; + } + } + } + } +} |