use std::{ path::PathBuf, sync::{Arc, Mutex}, }; use anyhow::Result; use crossbeam_channel::{Receiver, Sender}; use log::{error, info, warn}; use lsp_server::{Connection, Message, RequestId}; use lsp_types::{notification::*, request::*, *}; use serde::Serialize; use threadpool::ThreadPool; use crate::{ client::{send_notification, send_request}, diagnostics::{DiagnosticsDebouncer, DiagnosticsManager, DiagnosticsMessage}, dispatch::{NotificationDispatcher, RequestDispatcher}, distro::Distribution, features::{ execute_command, 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, BuildEngine, BuildParams, BuildResult, BuildStatus, FeatureRequest, ForwardSearchResult, ForwardSearchStatus, }, req_queue::{IncomingData, ReqQueue}, ClientCapabilitiesExt, DocumentLanguage, Environment, LineIndex, LineIndexExt, Options, Workspace, WorkspaceEvent, }; #[derive(Debug)] enum InternalMessage { SetDistro(Distribution), SetOptions(Options), } #[derive(Clone)] pub struct Server { connection: Arc, internal_tx: Sender, internal_rx: Receiver, req_queue: Arc>, workspace: Workspace, static_debouncer: Arc, chktex_debouncer: Arc, pool: Arc>, load_resolver: bool, build_engine: Arc, } impl Server { pub fn with_connection( connection: Connection, current_dir: PathBuf, load_resolver: bool, ) -> Self { let req_queue = Arc::default(); let workspace = Workspace::new(Environment::new(Arc::new(current_dir))); let diag_manager = Arc::new(Mutex::new(DiagnosticsManager::default())); let static_debouncer = Arc::new(create_static_debouncer( Arc::clone(&diag_manager), &connection, )); let chktex_debouncer = Arc::new(create_chktex_debouncer(diag_manager, &connection)); let (internal_tx, internal_rx) = crossbeam_channel::unbounded(); Self { connection: Arc::new(connection), internal_tx, internal_rx, req_queue, workspace, static_debouncer, chktex_debouncer, pool: Arc::new(Mutex::new(threadpool::Builder::new().build())), load_resolver, build_engine: Arc::default(), } } fn spawn(&self, job: impl FnOnce(Self) + Send + 'static) { let server = self.clone(); self.pool.lock().unwrap().execute(move || job(server)); } fn capabilities(&self) -> ServerCapabilities { ServerCapabilities { text_document_sync: Some(TextDocumentSyncCapability::Options( TextDocumentSyncOptions { open_close: Some(true), change: Some(TextDocumentSyncKind::INCREMENTAL), will_save: None, will_save_wait_until: None, save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions { include_text: Some(false), })), }, )), 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![ "\\".into(), "{".into(), "}".into(), "@".into(), "/".into(), " ".into(), ]), ..CompletionOptions::default() }), 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_highlight_provider: Some(OneOf::Left(true)), document_formatting_provider: Some(OneOf::Left(true)), execute_command_provider: Some(ExecuteCommandOptions { commands: vec![ "texlab.cleanAuxiliary".into(), "texlab.cleanArtifacts".into(), ], ..Default::default() }), ..ServerCapabilities::default() } } fn initialize(&mut self) -> Result<()> { let (id, params) = self.connection.initialize_start()?; let params: InitializeParams = serde_json::from_value(params)?; self.workspace.environment.client_capabilities = Arc::new(params.capabilities); self.workspace.environment.client_info = params.client_info.map(Arc::new); 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)?)?; if self.load_resolver { self.spawn(move |server| { let distro = Distribution::detect(); info!("Detected distribution: {}", distro.kind); server .internal_tx .send(InternalMessage::SetDistro(distro)) .unwrap(); }); } self.register_diagnostics_handler(); self.spawn(move |server| { server.register_config_capability(); server.register_file_watching(); server.pull_config(); }); Ok(()) } fn register_file_watching(&self) { if self .workspace .environment .client_capabilities .has_file_watching_support() { let options = DidChangeWatchedFilesRegistrationOptions { watchers: vec![FileSystemWatcher { glob_pattern: "**/*.{aux,log}".into(), kind: Some(WatchKind::Create | WatchKind::Change | WatchKind::Delete), }], }; let reg = Registration { id: "build-watch".to_string(), method: DidChangeWatchedFiles::METHOD.to_string(), register_options: Some(serde_json::to_value(options).unwrap()), }; let params = RegistrationParams { registrations: vec![reg], }; if let Err(why) = send_request::(&self.req_queue, &self.connection.sender, params) { error!( "Failed to register \"{}\" notification: {}", DidChangeWatchedFiles::METHOD, why ); } } } fn register_config_capability(&self) { if self .workspace .environment .client_capabilities .has_push_configuration_support() { let reg = Registration { id: "pull-config".to_string(), method: DidChangeConfiguration::METHOD.to_string(), register_options: None, }; let params = RegistrationParams { registrations: vec![reg], }; if let Err(why) = send_request::(&self.req_queue, &self.connection.sender, params) { error!( "Failed to register \"{}\" notification: {}", DidChangeConfiguration::METHOD, why ); } } } fn register_diagnostics_handler(&mut self) { let (event_sender, event_receiver) = crossbeam_channel::unbounded(); let diag_sender = self.static_debouncer.sender.clone(); std::thread::spawn(move || { for event in event_receiver { match event { WorkspaceEvent::Changed(workspace, document) => { let message = DiagnosticsMessage::Analyze { workspace, document, }; if diag_sender.send(message).is_err() { break; } } }; } }); self.workspace.listeners.push(event_sender); } fn register_incoming_request(&self, id: RequestId) { let mut req_queue = self.req_queue.lock().unwrap(); req_queue.incoming.register(id, IncomingData); } fn pull_config(&self) { if !self .workspace .environment .client_capabilities .has_pull_configuration_support() { return; } let params = ConfigurationParams { items: vec![ConfigurationItem { section: Some("texlab".to_string()), scope_uri: None, }], }; match send_request::( &self.req_queue, &self.connection.sender, params, ) { Ok(mut json) => { let value = json.pop().expect("invalid configuration request"); let options = match serde_json::from_value(value) { Ok(new_options) => new_options, Err(why) => { warn!("Invalid configuration section \"texlab\": {}", why); Options::default() } }; self.internal_tx .send(InternalMessage::SetOptions(options)) .unwrap(); } Err(why) => { error!("Retrieving configuration failed: {}", why); } }; } 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(); req_queue.incoming.complete(id); Ok(()) } fn did_change_watched_files(&mut self, params: DidChangeWatchedFilesParams) -> Result<()> { for change in params.changes { if let Ok(path) = change.uri.to_file_path() { match change.typ { FileChangeType::CREATED | FileChangeType::CHANGED => { self.workspace.reload(path)?; } FileChangeType::DELETED => { self.workspace.documents_by_uri.remove(&change.uri); } _ => {} } } } Ok(()) } fn did_change_configuration(&mut self, params: DidChangeConfigurationParams) -> Result<()> { if self .workspace .environment .client_capabilities .has_pull_configuration_support() { self.spawn(move |server| { server.pull_config(); }); } else { match serde_json::from_value(params.settings) { Ok(options) => { self.workspace.environment.options = Arc::new(options); } Err(why) => { error!("Invalid configuration: {}", why); } }; self.reparse_all()?; } Ok(()) } fn did_open(&mut 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), Arc::new(params.text_document.text), language.unwrap_or(DocumentLanguage::Latex), )?; self.workspace.viewport.insert(Arc::clone(&document.uri)); if self.workspace.environment.options.chktex.on_open_and_save { self.chktex_debouncer .sender .send(DiagnosticsMessage::Analyze { workspace: self.workspace.clone(), document, })?; } Ok(()) } fn did_change(&mut self, params: DidChangeTextDocumentParams) -> Result<()> { let uri = Arc::new(params.text_document.uri); match self.workspace.documents_by_uri.get(&uri).cloned() { Some(old_document) => { let mut text = old_document.text.to_string(); apply_document_edit(&mut text, params.content_changes); let language = old_document.data.language(); let new_document = self.workspace .open(Arc::clone(&uri), Arc::new(text), language)?; self.workspace .viewport .insert(Arc::clone(&new_document.uri)); self.build_engine.positions_by_uri.insert( Arc::clone(&uri), Position::new( old_document .text .lines() .zip(new_document.text.lines()) .position(|(a, b)| a != b) .unwrap_or_default() as u32, 0, ), ); if self.workspace.environment.options.chktex.on_edit { self.chktex_debouncer .sender .send(DiagnosticsMessage::Analyze { workspace: self.workspace.clone(), document: new_document, })?; }; } None => match uri.to_file_path() { Ok(path) => { self.workspace.load(path)?; } Err(_) => return Ok(()), }, }; Ok(()) } fn did_save(&self, params: DidSaveTextDocumentParams) -> Result<()> { let uri = params.text_document.uri; if let Some(request) = self .workspace .documents_by_uri .get(&uri) .filter(|_| self.workspace.environment.options.build.on_save) .map(|document| { self.feature_request( Arc::clone(&document.uri), BuildParams { text_document: TextDocumentIdentifier::new(uri.clone()), }, ) }) { self.spawn(move |server| { server .build_engine .build(request, &server.req_queue, &server.connection.sender) .unwrap_or_else(|why| { error!("Build failed: {}", why); BuildResult { status: BuildStatus::FAILURE, } }); }); } if let Some(document) = self .workspace .documents_by_uri .get(&uri) .filter(|_| self.workspace.environment.options.chktex.on_open_and_save) .cloned() { self.chktex_debouncer .sender .send(DiagnosticsMessage::Analyze { workspace: self.workspace.clone(), document, })?; }; Ok(()) } fn did_close(&mut self, params: DidCloseTextDocumentParams) -> Result<()> { self.workspace.close(¶ms.text_document.uri); Ok(()) } fn feature_request

(&self, uri: Arc, params: P) -> FeatureRequest

{ FeatureRequest { params, workspace: self.workspace.slice(&uri), uri, } } fn handle_feature_request( &self, id: RequestId, params: P, uri: Arc, handler: H, ) -> Result<()> where P: Send + 'static, R: Serialize, H: FnOnce(FeatureRequest

) -> R + Send + 'static, { self.spawn(move |server| { let request = server.feature_request(uri, params); if request.workspace.documents_by_uri.is_empty() { let code = lsp_server::ErrorCode::InvalidRequest as i32; let message = "unknown document".to_string(); let response = lsp_server::Response::new_err(id, code, message); server.connection.sender.send(response.into()).unwrap(); } else { let result = handler(request); server .connection .sender .send(lsp_server::Response::new_ok(id, result).into()) .unwrap(); } }); Ok(()) } fn document_link(&self, id: RequestId, params: DocumentLinkParams) -> Result<()> { let uri = Arc::new(params.text_document.uri.clone()); self.handle_feature_request(id, params, uri, find_document_links)?; Ok(()) } fn document_symbols(&self, id: RequestId, params: DocumentSymbolParams) -> Result<()> { let uri = Arc::new(params.text_document.uri.clone()); self.handle_feature_request(id, params, uri, find_document_symbols)?; Ok(()) } fn workspace_symbols(&self, id: RequestId, params: WorkspaceSymbolParams) -> Result<()> { self.spawn(move |server| { let result = find_workspace_symbols(&server.workspace, ¶ms); server .connection .sender .send(lsp_server::Response::new_ok(id, result).into()) .unwrap(); }); Ok(()) } #[cfg(feature = "completion")] fn completion(&self, id: RequestId, params: CompletionParams) -> Result<()> { let uri = Arc::new(params.text_document_position.text_document.uri.clone()); self.build_engine .positions_by_uri .insert(Arc::clone(&uri), params.text_document_position.position); self.handle_feature_request(id, params, uri, crate::features::complete)?; Ok(()) } #[cfg(feature = "completion")] fn completion_resolve(&self, id: RequestId, mut item: CompletionItem) -> Result<()> { use rowan::ast::AstNode; use crate::{ citation, component_db::COMPONENT_DATABASE, features::CompletionItemData, syntax::bibtex, }; self.spawn(move |server| { match serde_json::from_value(item.data.clone().unwrap()).unwrap() { CompletionItemData::Package | CompletionItemData::Class => { item.documentation = COMPONENT_DATABASE .documentation(&item.label) .map(Documentation::MarkupContent); } CompletionItemData::Citation { uri, key } => { if let Some(document) = server.workspace.documents_by_uri.get(&uri) { if let Some(data) = document.data.as_bibtex() { let root = bibtex::SyntaxNode::new_root(data.green.clone()); item.documentation = bibtex::Root::cast(root) .and_then(|root| root.find_entry(&key)) .and_then(|entry| citation::render(&entry)) .map(|value| { Documentation::MarkupContent(MarkupContent { kind: MarkupKind::Markdown, value, }) }); } } } _ => {} }; server .connection .sender .send(lsp_server::Response::new_ok(id, item).into()) .unwrap(); }); Ok(()) } fn folding_range(&self, id: RequestId, params: FoldingRangeParams) -> Result<()> { let uri = Arc::new(params.text_document.uri.clone()); self.handle_feature_request(id, params, uri, find_foldings)?; Ok(()) } fn references(&self, id: RequestId, params: ReferenceParams) -> Result<()> { let uri = Arc::new(params.text_document_position.text_document.uri.clone()); self.handle_feature_request(id, params, uri, find_all_references)?; Ok(()) } fn hover(&self, id: RequestId, params: HoverParams) -> Result<()> { let uri = Arc::new( params .text_document_position_params .text_document .uri .clone(), ); self.build_engine.positions_by_uri.insert( Arc::clone(&uri), params.text_document_position_params.position, ); self.handle_feature_request(id, params, uri, find_hover)?; Ok(()) } fn goto_definition(&self, id: RequestId, params: GotoDefinitionParams) -> Result<()> { let uri = Arc::new( params .text_document_position_params .text_document .uri .clone(), ); self.handle_feature_request(id, params, uri, goto_definition)?; Ok(()) } fn prepare_rename(&self, id: RequestId, params: TextDocumentPositionParams) -> Result<()> { let uri = Arc::new(params.text_document.uri.clone()); self.handle_feature_request(id, params, uri, prepare_rename_all)?; Ok(()) } fn rename(&self, id: RequestId, params: RenameParams) -> Result<()> { let uri = Arc::new(params.text_document_position.text_document.uri.clone()); self.handle_feature_request(id, params, uri, rename_all)?; Ok(()) } fn document_highlight(&self, id: RequestId, params: DocumentHighlightParams) -> Result<()> { let uri = Arc::new( params .text_document_position_params .text_document .uri .clone(), ); self.handle_feature_request(id, params, uri, find_document_highlights)?; Ok(()) } fn formatting(&self, id: RequestId, params: DocumentFormattingParams) -> Result<()> { let uri = Arc::new(params.text_document.uri.clone()); self.handle_feature_request(id, params, uri, format_source_code)?; Ok(()) } fn execute_command(&self, id: RequestId, params: ExecuteCommandParams) -> Result<()> { self.spawn(move |server| { let result = execute_command(&server.workspace, ¶ms.command, params.arguments); let response = match result { Ok(()) => lsp_server::Response::new_ok(id, ()), Err(why) => lsp_server::Response::new_err( id, lsp_server::ErrorCode::InternalError as i32, why.to_string(), ), }; server.connection.sender.send(response.into()).unwrap(); }); Ok(()) } fn semantic_tokens_range( &self, _id: RequestId, _params: SemanticTokensRangeParams, ) -> Result<()> { Ok(()) } fn build(&self, id: RequestId, params: BuildParams) -> Result<()> { let uri = Arc::new(params.text_document.uri.clone()); let lsp_sender = self.connection.sender.clone(); let req_queue = Arc::clone(&self.req_queue); let build_engine = Arc::clone(&self.build_engine); self.handle_feature_request(id, params, uri, move |request| { build_engine .build(request, &req_queue, &lsp_sender) .unwrap_or_else(|why| { error!("Build failed: {}", why); BuildResult { status: BuildStatus::FAILURE, } }) })?; Ok(()) } fn forward_search(&self, id: RequestId, params: TextDocumentPositionParams) -> Result<()> { let uri = Arc::new(params.text_document.uri.clone()); self.handle_feature_request(id, params, uri, |req| { crate::features::execute_forward_search(req).unwrap_or(ForwardSearchResult { status: ForwardSearchStatus::ERROR, }) })?; Ok(()) } fn reparse_all(&mut self) -> Result<()> { for document in self .workspace .documents_by_uri .values() .cloned() .collect::>() { self.workspace.open( Arc::clone(&document.uri), document.text.clone(), document.data.language(), )?; } Ok(()) } fn process_messages(&mut self) -> Result<()> { loop { crossbeam_channel::select! { recv(&self.connection.receiver) -> msg => { match msg? { Message::Request(request) => { if self.connection.handle_shutdown(&request)? { return Ok(()); } self.register_incoming_request(request.id.clone()); if let Some(response) = RequestDispatcher::new(request) .on::(|id, params| self.document_link(id, params))? .on::(|id, params| self.folding_range(id, params))? .on::(|id, params| self.references(id, params))? .on::(|id, params| self.hover(id, params))? .on::(|id, params| { self.document_symbols(id, params) })? .on::(|id, params| self.workspace_symbols(id, params))? .on::(|id, params| { #[cfg(feature = "completion")] self.completion(id, params)?; Ok(()) })? .on::(|id, params| { #[cfg(feature = "completion")] self.completion_resolve(id, params)?; Ok(()) })? .on::(|id, params| self.goto_definition(id, params))? .on::(|id, params| { self.prepare_rename(id, params) })? .on::(|id, params| self.rename(id, params))? .on::(|id, params| { self.document_highlight(id, params) })? .on::(|id, params| self.formatting(id, params))? .on::(|id, params| self.build(id, params))? .on::(|id, params| { self.forward_search(id, params) })? .on::(|id, params| self.execute_command(id, params))? .on::(|id, params| { self.semantic_tokens_range(id, params) })? .default() { self.connection.sender.send(response.into())?; } } Message::Notification(notification) => { NotificationDispatcher::new(notification) .on::(|params| self.cancel(params))? .on::(|params| { self.did_change_configuration(params) })? .on::(|params| { self.did_change_watched_files(params) })? .on::(|params| self.did_open(params))? .on::(|params| self.did_change(params))? .on::(|params| self.did_save(params))? .on::(|params| self.did_close(params))? .default(); } Message::Response(response) => { let mut req_queue = self.req_queue.lock().unwrap(); if let Some(data) = req_queue.outgoing.complete(response.id) { let result = match response.error { Some(error) => Err(error), None => Ok(response.result.unwrap_or_default()), }; data.sender.send(result)?; } } }; }, recv(&self.internal_rx) -> msg => { match msg? { InternalMessage::SetDistro(distro) => { self.workspace.environment.resolver = Arc::new(distro.resolver); self.reparse_all()?; } InternalMessage::SetOptions(options) => { self.workspace.environment.options = Arc::new(options); self.reparse_all()?; } }; } }; } } pub fn run(mut self) -> Result<()> { self.initialize()?; self.process_messages()?; drop(self.static_debouncer); drop(self.chktex_debouncer); self.pool.lock().unwrap().join(); Ok(()) } } fn create_static_debouncer( manager: Arc>, conn: &Connection, ) -> DiagnosticsDebouncer { let sender = conn.sender.clone(); DiagnosticsDebouncer::launch(move |workspace, document| { let mut manager = manager.lock().unwrap(); manager.update_static(&workspace, Arc::clone(&document.uri)); if let Err(why) = publish_diagnostics(&sender, &workspace, &manager) { warn!("Failed to publish diagnostics: {}", why); } }) } fn create_chktex_debouncer( manager: Arc>, conn: &Connection, ) -> DiagnosticsDebouncer { let sender = conn.sender.clone(); DiagnosticsDebouncer::launch(move |workspace, document| { let mut manager = manager.lock().unwrap(); manager.update_chktex(&workspace, &document.uri, &workspace.environment.options); if let Err(why) = publish_diagnostics(&sender, &workspace, &manager) { warn!("Failed to publish diagnostics: {}", why); } }) } fn publish_diagnostics( sender: &Sender, workspace: &Workspace, diag_manager: &DiagnosticsManager, ) -> Result<()> { for document in workspace.documents_by_uri.values() { let diagnostics = diag_manager.publish(&document.uri); send_notification::( sender, PublishDiagnosticsParams { uri: document.uri.as_ref().clone(), version: None, diagnostics, }, )?; } Ok(()) } fn apply_document_edit(old_text: &mut String, changes: Vec) { for change in changes { let line_index = LineIndex::new(old_text); match change.range { Some(range) => { let range = std::ops::Range::::from(line_index.offset_lsp_range(range)); old_text.replace_range(range, &change.text); } None => { *old_text = change.text; } }; } } 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"; }