diff options
Diffstat (limited to 'support/texlab/src/server.rs')
-rw-r--r-- | support/texlab/src/server.rs | 1156 |
1 files changed, 611 insertions, 545 deletions
diff --git a/support/texlab/src/server.rs b/support/texlab/src/server.rs index 0d5d1f8fe1..93b35c69c6 100644 --- a/support/texlab/src/server.rs +++ b/support/texlab/src/server.rs @@ -1,83 +1,89 @@ +mod dispatch; +mod query; + use std::{ path::PathBuf, sync::{Arc, Mutex}, - time::Duration, }; use anyhow::Result; use crossbeam_channel::{Receiver, Sender}; -use log::{error, info, warn}; -use lsp_server::{Connection, Message, RequestId}; +use log::{error, info}; +use lsp_server::{Connection, ErrorCode, Message, RequestId}; use lsp_types::{notification::*, request::*, *}; -use serde::Serialize; +use once_cell::sync::Lazy; +use rowan::{ast::AstNode, TextSize}; +use rustc_hash::FxHashSet; +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; use threadpool::ThreadPool; use crate::{ - client::{send_notification, send_request}, - debouncer, - diagnostics::DiagnosticManager, - dispatch::{NotificationDispatcher, RequestDispatcher}, - distro::Distribution, + citation, + client::LspClient, + db::{self, discover_dependencies, Document, Language, Owner, Workspace}, + distro::Distro, 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, + build::{self, BuildParams, BuildResult, BuildStatus}, + completion::{self, builder::CompletionItemData}, + definition, folding, formatting, forward_search, highlight, hover, inlay_hint, link, + reference, rename, symbol, workspace_command, + }, + normalize_uri, + syntax::bibtex, + util::{ + self, capabilities::ClientCapabilitiesExt, components::COMPONENT_DATABASE, + line_index_ext::LineIndexExt, }, - req_queue::{IncomingData, ReqQueue}, - ClientCapabilitiesExt, Document, DocumentData, DocumentLanguage, Environment, LineIndex, - LineIndexExt, Options, Workspace, WorkspaceEvent, + Db, Options, StartupOptions, }; #[derive(Debug)] enum InternalMessage { - SetDistro(Distribution), - SetOptions(Arc<Options>), + SetDistro(Distro), + SetOptions(Options), + FileEvent(notify::Event), + ForwardSearch(Url), + Diagnostics, + ChktexResult(Url, Vec<db::diagnostics::Diagnostic>), } -#[derive(Clone)] pub struct Server { connection: Arc<Connection>, internal_tx: Sender<InternalMessage>, internal_rx: Receiver<InternalMessage>, - req_queue: Arc<Mutex<ReqQueue>>, - workspace: Workspace, - diagnostic_tx: debouncer::Sender<Workspace>, - diagnostic_manager: DiagnosticManager, - pool: Arc<Mutex<ThreadPool>>, - load_resolver: bool, - build_engine: Arc<BuildEngine>, + client: LspClient, + engine: query::Engine, + watcher: FileWatcher, + pool: ThreadPool, } 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))); + pub fn new(connection: Connection) -> Self { + let client = LspClient::new(connection.sender.clone()); let (internal_tx, internal_rx) = crossbeam_channel::unbounded(); - let diagnostic_manager = DiagnosticManager::default(); - let diagnostic_tx = create_debouncer(connection.sender.clone(), diagnostic_manager.clone()); + let watcher = FileWatcher::new(internal_tx.clone()).expect("init file watcher"); Self { connection: Arc::new(connection), internal_tx, internal_rx, - req_queue, - workspace, - diagnostic_tx, - diagnostic_manager, - pool: Arc::new(Mutex::new(threadpool::Builder::new().build())), - load_resolver, - build_engine: Arc::default(), + client, + engine: query::Engine::default(), + watcher, + pool: threadpool::Builder::new().build(), } } - fn spawn(&self, job: impl FnOnce(Self) + Send + 'static) { - let server = self.clone(); - self.pool.lock().unwrap().execute(move || job(server)); + fn run_with_db<R, Q>(&self, id: RequestId, query: Q) + where + R: Serialize, + Q: FnOnce(&dyn Db) -> R + Send + 'static, + { + let client = self.client.clone(); + self.engine.fork(move |db| { + let response = lsp_server::Response::new_ok(id, query(db)); + client.send_response(response).unwrap(); + }); } fn capabilities(&self) -> ServerCapabilities { @@ -101,7 +107,6 @@ impl Server { 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![ @@ -129,6 +134,7 @@ impl Server { ], ..Default::default() }), + inlay_hint_provider: Some(OneOf::Left(true)), ..ServerCapabilities::default() } } @@ -137,8 +143,29 @@ impl Server { 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 db = self.engine.write(); + let workspace = Workspace::get(db); + workspace + .set_client_capabilities(db) + .with_durability(salsa::Durability::HIGH) + .to(params.capabilities); + + workspace + .set_client_info(db) + .with_durability(salsa::Durability::HIGH) + .to(params.client_info); + + let root_dirs = params + .workspace_folders + .unwrap_or_default() + .into_iter() + .map(|folder| db::Location::new(db, folder.uri)) + .collect(); + + workspace + .set_root_dirs(db) + .with_durability(salsa::Durability::HIGH) + .to(root_dirs); let result = InitializeResult { capabilities: self.capabilities(), @@ -146,132 +173,109 @@ impl Server { name: "TexLab".to_owned(), version: Some(env!("CARGO_PKG_VERSION").to_owned()), }), + offset_encoding: None, }; 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); + let StartupOptions { skip_distro } = + serde_json::from_value(params.initialization_options.unwrap_or_default()) + .unwrap_or_default(); - server - .internal_tx - .send(InternalMessage::SetDistro(distro)) - .unwrap(); + if !skip_distro { + let sender = self.internal_tx.clone(); + self.pool.execute(move || { + let distro = Distro::detect().unwrap_or_else(|why| { + log::warn!("Unable to load distro files: {}", why); + Distro::default() + }); + + info!("Detected distribution: {:?}", distro.kind); + sender.send(InternalMessage::SetDistro(distro)).unwrap(); }); } - self.register_diagnostics_handler(); - - self.spawn(move |server| { - server.register_config_capability(); - server.register_file_watching(); - let _ = server.pull_config(); - }); - + self.register_configuration(); + self.pull_options(); 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::<RegisterCapability>(&self.req_queue, &self.connection.sender, params) - { - error!( - "Failed to register \"{}\" notification: {}", - DidChangeWatchedFiles::METHOD, - why - ); - } - } - } + fn register_configuration(&mut self) { + let db = self.engine.read(); - fn register_config_capability(&self) { - if self - .workspace - .environment - .client_capabilities + if Workspace::get(db) + .client_capabilities(db) .has_push_configuration_support() { - let reg = Registration { + let registration = Registration { id: "pull-config".to_string(), method: DidChangeConfiguration::METHOD.to_string(), register_options: None, }; let params = RegistrationParams { - registrations: vec![reg], + registrations: vec![registration], }; - if let Err(why) = - send_request::<RegisterCapability>(&self.req_queue, &self.connection.sender, params) - { - error!( - "Failed to register \"{}\" notification: {}", - DidChangeConfiguration::METHOD, - why - ); - } + let client = self.client.clone(); + self.pool.execute(move || { + if let Err(why) = client.send_request::<RegisterCapability>(params) { + log::error!( + "Failed to register \"{}\" notification: {}", + DidChangeConfiguration::METHOD, + why + ); + } + }); } } - fn register_diagnostics_handler(&mut self) { - let (event_sender, event_receiver) = crossbeam_channel::unbounded(); - let diagnostic_tx = self.diagnostic_tx.clone(); - let diagnostic_manager = self.diagnostic_manager.clone(); - std::thread::spawn(move || { - for event in event_receiver { - match event { - WorkspaceEvent::Changed(workspace, document) => { - diagnostic_manager.push_syntax(&workspace, &document.uri); - let delay = workspace.environment.options.diagnostics_delay; - diagnostic_tx - .send(workspace, Duration::from_millis(delay)) - .unwrap(); - } - }; - } - }); + fn update_workspace(&mut self) { + let db = self.engine.write(); + discover_dependencies(db); + self.watcher.watch(db); + self.publish_diagnostics_with_delay(); + } + + fn publish_diagnostics(&mut self) -> Result<()> { + let db = self.engine.read(); + + let all_diagnostics = db::diagnostics::collect_filtered(db, Workspace::get(db)); + + for (document, diagnostics) in all_diagnostics { + let uri = document.location(db).uri(db).clone(); + let version = None; + let params = PublishDiagnosticsParams { + uri, + diagnostics, + version, + }; + + self.client + .send_notification::<PublishDiagnostics>(params)?; + } - self.workspace.listeners.push(event_sender); + Ok(()) } - fn register_incoming_request(&self, id: RequestId) { - let mut req_queue = self.req_queue.lock().unwrap(); - req_queue.incoming.register(id, IncomingData); + fn publish_diagnostics_with_delay(&mut self) { + let db = self.engine.read(); + let sender = self.internal_tx.clone(); + let delay = Workspace::get(db).options(db).diagnostics_delay.0; + self.pool.execute(move || { + std::thread::sleep(delay); + sender.send(InternalMessage::Diagnostics).unwrap(); + }); } - fn pull_config(&self) -> Result<()> { - if !self - .workspace - .environment - .client_capabilities + fn pull_options(&mut self) { + let db = self.engine.read(); + let workspace = Workspace::get(db); + if !workspace + .client_capabilities(db) .has_pull_configuration_support() { - return Ok(()); + return; } let params = ConfigurationParams { @@ -281,108 +285,81 @@ impl Server { }], }; - match send_request::<WorkspaceConfiguration>( - &self.req_queue, - &self.connection.sender, - params, - ) { - Ok(mut json) => { - let value = json.pop().expect("invalid configuration request"); - let options = self.parse_options(value)?; - self.internal_tx - .send(InternalMessage::SetOptions(Arc::new(options))) - .unwrap(); - } - Err(why) => { - error!("Retrieving configuration failed: {}", why); - } - }; + let client = self.client.clone(); + let sender = self.internal_tx.clone(); + self.pool.execute(move || { + match client.send_request::<WorkspaceConfiguration>(params) { + Ok(mut json) => { + let options = client + .parse_options(json.pop().expect("invalid configuration request")) + .unwrap(); - Ok(()) + sender.send(InternalMessage::SetOptions(options)).unwrap(); + } + Err(why) => { + error!("Retrieving configuration failed: {}", why); + } + }; + }); } - fn parse_options(&self, value: serde_json::Value) -> Result<Options> { - let options = match serde_json::from_value(value) { - Ok(new_options) => new_options, - Err(why) => { - send_notification::<ShowMessage>( - &self.connection.sender, - ShowMessageParams { - message: format!( - "The texlab configuration is invalid; using the default settings instead.\nDetails: {why}" - ), - typ: MessageType::WARNING, - }, - )?; - - Options::default() - } - }; + fn update_options(&mut self, options: Options) { + let db = self.engine.write(); + let workspace = Workspace::get(db); + workspace + .set_options(db) + .with_durability(salsa::Durability::MEDIUM) + .to(options); - Ok(options) + self.watcher.watch(db); } - 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); - + fn cancel(&self, _params: CancelParams) -> Result<()> { 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); - } - _ => {} - } - } - } - + fn did_change_watched_files(&mut self, _params: DidChangeWatchedFilesParams) -> Result<()> { Ok(()) } fn did_change_configuration(&mut self, params: DidChangeConfigurationParams) -> Result<()> { - if self - .workspace - .environment - .client_capabilities + let db = self.engine.read(); + let workspace = Workspace::get(db); + if workspace + .client_capabilities(db) .has_pull_configuration_support() { - self.spawn(move |server| { - let _ = server.pull_config(); - }); + self.pull_options(); } else { - let options = self.parse_options(params.settings)?; - self.workspace.environment.options = Arc::new(options); - self.reparse_all()?; + let options = self.client.parse_options(params.settings)?; + self.update_options(options); } Ok(()) } - fn did_open(&mut self, params: DidOpenTextDocumentParams) -> Result<()> { + fn did_open(&mut self, mut params: DidOpenTextDocumentParams) -> Result<()> { + normalize_uri(&mut params.text_document.uri); + + let db = self.engine.write(); + let workspace = Workspace::get(db); 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), - )?; + let language = Language::from_id(language_id).unwrap_or(Language::Tex); + let document = workspace.open( + db, + params.text_document.uri, + params.text_document.text, + language, + Owner::Client, + ); - self.workspace.viewport.insert(Arc::clone(&document.uri)); + self.update_workspace(); - if self.workspace.environment.options.chktex.on_open_and_save { + if workspace + .options(self.engine.read()) + .chktex + .on_open_and_save + { self.run_chktex(document); } @@ -390,315 +367,299 @@ impl Server { } 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.run_chktex(new_document); - }; - } - None => match uri.to_file_path() { - Ok(path) => { - self.workspace.load(path)?; - } - Err(_) => return Ok(()), - }, + let mut uri = params.text_document.uri; + normalize_uri(&mut uri); + + let db = self.engine.write(); + let workspace = Workspace::get(db); + let document = match workspace.lookup_uri(db, &uri) { + Some(document) => document, + None => return Ok(()), }; + for change in params.content_changes { + match change.range { + Some(range) => { + let range = document.contents(db).line_index(db).offset_lsp_range(range); + document.edit(db, range, &change.text); + } + None => { + document + .contents(db) + .set_text(db) + .with_durability(salsa::Durability::LOW) + .to(change.text); + + document + .set_cursor(db) + .with_durability(salsa::Durability::LOW) + .to(TextSize::from(0)); + } + }; + } + + self.update_workspace(); + + if workspace.options(self.engine.read()).chktex.on_edit { + self.run_chktex(document); + } + Ok(()) } fn did_save(&mut 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, - } - }); - }); + let mut uri = params.text_document.uri; + normalize_uri(&mut uri); + + let db = self.engine.read(); + let workspace = Workspace::get(db); + if workspace.options(db).build.on_save { + self.build_internal(uri.clone(), |_| ())?; } - if let Some(document) = self - .workspace - .documents_by_uri - .get(&uri) - .filter(|_| self.workspace.environment.options.chktex.on_open_and_save) - .cloned() - { - self.run_chktex(document); + self.publish_diagnostics_with_delay(); + + let db = self.engine.read(); + if let Some(document) = workspace.lookup_uri(db, &uri) { + if workspace.options(db).chktex.on_open_and_save { + self.run_chktex(document); + } } Ok(()) } fn did_close(&mut self, params: DidCloseTextDocumentParams) -> Result<()> { - self.workspace.close(¶ms.text_document.uri); + let mut uri = params.text_document.uri; + normalize_uri(&mut uri); + + let db = self.engine.write(); + if let Some(document) = Workspace::get(db).lookup_uri(db, &uri) { + document + .set_owner(db) + .with_durability(salsa::Durability::LOW) + .to(Owner::Server); + } + + self.publish_diagnostics_with_delay(); Ok(()) } fn run_chktex(&mut self, document: Document) { - self.spawn(move |server| { - server - .diagnostic_manager - .push_chktex(&server.workspace, &document.uri); - - let delay = server.workspace.environment.options.diagnostics_delay; - server - .diagnostic_tx - .send(server.workspace.clone(), Duration::from_millis(delay)) - .unwrap(); - }); - } - - fn feature_request<P>(&self, uri: Arc<Url>, params: P) -> FeatureRequest<P> { - FeatureRequest { - params, - workspace: self.workspace.slice(&uri), - uri, - } - } - - fn handle_feature_request<P, R, H>( - &self, - id: RequestId, - params: P, - uri: Arc<Url>, - handler: H, - ) -> Result<()> - where - P: Send + 'static, - R: Serialize, - H: FnOnce(FeatureRequest<P>) -> 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()) + let db = self.engine.read(); + if let Some(command) = util::chktex::Command::new(db, document) { + let sender = self.internal_tx.clone(); + let uri = document.location(db).uri(db).clone(); + self.pool.execute(move || { + let diagnostics = command.run().unwrap_or_default(); + sender + .send(InternalMessage::ChktexResult(uri, diagnostics)) .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)?; + let mut uri = params.text_document.uri; + normalize_uri(&mut uri); + self.run_with_db(id, move |db| link::find_all(db, &uri).unwrap_or_default()); 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)?; + let mut uri = params.text_document.uri; + normalize_uri(&mut uri); + self.run_with_db(id, move |db| symbol::find_document_symbols(db, &uri)); 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(); - }); + self.run_with_db(id, move |db| symbol::find_workspace_symbols(db, ¶ms)); 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)?; + fn completion(&mut self, id: RequestId, params: CompletionParams) -> Result<()> { + let mut uri = params.text_document_position.text_document.uri; + normalize_uri(&mut uri); + let position = params.text_document_position.position; + self.run_with_db(id, move |db| completion::complete(db, &uri, position)); 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 => { + self.run_with_db(id, move |db| { + match item + .data + .clone() + .map(|data| serde_json::from_value(data).unwrap()) + { + Some(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, - }) - }); - } + Some(CompletionItemData::Citation { uri, key }) => { + if let Some(root) = Workspace::get(db) + .lookup_uri(db, &uri) + .and_then(|document| document.parse(db).as_bib().map(|data| data.root(db))) + { + 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, + }) + }); } } - _ => {} + None => {} }; - server - .connection - .sender - .send(lsp_server::Response::new_ok(id, item).into()) - .unwrap(); + item }); + 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)?; + let mut uri = params.text_document.uri; + normalize_uri(&mut uri); + self.run_with_db(id, move |db| { + folding::find_all(db, &uri).unwrap_or_default() + }); 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)?; + let mut uri = params.text_document_position.text_document.uri; + normalize_uri(&mut uri); + let position = params.text_document_position.position; + self.run_with_db(id, move |db| { + reference::find_all(db, &uri, position, ¶ms.context).unwrap_or_default() + }); + 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, - ); + fn hover(&mut self, id: RequestId, params: HoverParams) -> Result<()> { + let mut uri = params.text_document_position_params.text_document.uri; + normalize_uri(&mut uri); - self.handle_feature_request(id, params, uri, find_hover)?; + let db = self.engine.write(); + let workspace = Workspace::get(db); + if let Some(document) = workspace.lookup_uri(db, &uri) { + let position = document + .contents(db) + .line_index(db) + .offset_lsp(params.text_document_position_params.position); + + document + .set_cursor(db) + .with_durability(salsa::Durability::LOW) + .to(position); + } + + let position = params.text_document_position_params.position; + self.run_with_db(id, move |db| hover::find(db, &uri, position)); 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)?; + let mut uri = params.text_document_position_params.text_document.uri; + normalize_uri(&mut uri); + let position = params.text_document_position_params.position; + self.run_with_db(id, move |db| { + definition::goto_definition(db, &uri, position) + }); + 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)?; + let mut uri = params.text_document.uri; + normalize_uri(&mut uri); + self.run_with_db(id, move |db| { + rename::prepare_rename_all(db, &uri, params.position) + }); + 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)?; + let mut uri = params.text_document_position.text_document.uri; + normalize_uri(&mut uri); + let position = params.text_document_position.position; + self.run_with_db(id, move |db| { + rename::rename_all(db, &uri, position, params.new_name) + }); + 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)?; + let mut uri = params.text_document_position_params.text_document.uri; + normalize_uri(&mut uri); + let position = params.text_document_position_params.position; + self.run_with_db(id, move |db| { + highlight::find_all(db, &uri, position).unwrap_or_default() + }); 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)?; + let mut uri = params.text_document.uri; + normalize_uri(&mut uri); + self.run_with_db(id, move |db| { + formatting::format_source_code(db, &uri, ¶ms.options) + }); + 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(), - ), - }; + fn execute_command(&mut self, id: RequestId, params: ExecuteCommandParams) -> Result<()> { + let db = self.engine.read(); + match workspace_command::select(db, ¶ms.command, params.arguments) { + Ok(command) => { + let client = self.client.clone(); + self.pool.execute(move || { + match command.run() { + Ok(()) => { + client + .send_response(lsp_server::Response::new_ok(id, ())) + .unwrap(); + } + Err(why) => { + client + .send_error(id, ErrorCode::InternalError, why.to_string()) + .unwrap(); + } + }; + }); + } + Err(why) => { + self.client + .send_error(id, ErrorCode::InvalidParams, why.to_string()) + .unwrap(); + } + }; + + Ok(()) + } - server.connection.sender.send(response.into()).unwrap(); + fn inlay_hints(&self, id: RequestId, params: InlayHintParams) -> Result<()> { + let mut uri = params.text_document.uri; + normalize_uri(&mut uri); + self.run_with_db(id, move |db| { + inlay_hint::find_all(db, &uri, params.range).unwrap_or_default() }); + Ok(()) + } + fn inlay_hint_resolve(&self, id: RequestId, hint: InlayHint) -> Result<()> { + let response = lsp_server::Response::new_ok(id, hint); + self.connection.sender.send(response.into()).unwrap(); Ok(()) } @@ -710,52 +671,144 @@ impl Server { 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, - } - }) + fn build(&mut self, id: RequestId, params: BuildParams) -> Result<()> { + let mut uri = params.text_document.uri; + normalize_uri(&mut uri); + + let client = self.client.clone(); + self.build_internal(uri, move |status| { + let result = BuildResult { status }; + client + .send_response(lsp_server::Response::new_ok(id, result)) + .unwrap(); })?; + + Ok(()) + } + + fn build_internal( + &mut self, + uri: Url, + callback: impl FnOnce(BuildStatus) + Send + 'static, + ) -> Result<()> { + static LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(())); + + let db = self.engine.read(); + let compiler = match build::Command::new(db, uri.clone(), self.client.clone()) { + Some(compiler) => compiler, + None => { + callback(BuildStatus::FAILURE); + return Ok(()); + } + }; + + let forward_search_after = Workspace::get(db).options(db).build.forward_search_after; + + let sender = self.internal_tx.clone(); + self.pool.execute(move || { + let guard = LOCK.lock().unwrap(); + + let status = compiler.run(); + if forward_search_after { + sender.send(InternalMessage::ForwardSearch(uri)).unwrap(); + } + + drop(guard); + callback(status); + }); + 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, - }) + fn forward_search(&mut self, id: RequestId, params: TextDocumentPositionParams) -> Result<()> { + let mut uri = params.text_document.uri; + normalize_uri(&mut uri); + + let client = self.client.clone(); + self.forward_search_internal(uri, Some(params.position), move |status| { + let result = ForwardSearchResult { status }; + client + .send_response(lsp_server::Response::new_ok(id, result)) + .unwrap(); })?; + Ok(()) } - fn reparse_all(&mut self) -> Result<()> { - for document in self - .workspace - .documents_by_uri - .values() - .cloned() - .collect::<Vec<_>>() - { - self.workspace.open( - Arc::clone(&document.uri), - document.text.clone(), - document.data.language(), - )?; - } + fn forward_search_internal( + &mut self, + uri: Url, + position: Option<Position>, + callback: impl FnOnce(ForwardSearchStatus) + Send + 'static, + ) -> Result<()> { + let db = self.engine.read(); + let command = match forward_search::Command::configure(db, &uri, position) { + Ok(command) => command, + Err(why) => { + log::error!("Forward search failed: {}", why); + callback(why.into()); + return Ok(()); + } + }; + + self.pool.execute(move || { + let status = command + .run() + .map_or_else(ForwardSearchStatus::from, |()| ForwardSearchStatus::SUCCESS); + + callback(status); + }); Ok(()) } + fn handle_file_event(&mut self, event: notify::Event) { + let mut changed = false; + + let db = self.engine.write(); + let workspace = Workspace::get(db); + match event.kind { + notify::EventKind::Create(_) | notify::EventKind::Modify(_) => { + for path in event.paths { + if workspace + .lookup_path(db, &path) + .map_or(true, |document| document.owner(db) == Owner::Server) + { + if let Some(language) = Language::from_path(&path) { + workspace.load(db, &path, language, Owner::Server); + changed = true; + } + } + } + } + notify::EventKind::Remove(_) => { + for path in event.paths { + if let Some(document) = workspace.lookup_path(db, &path) { + if document.owner(db) == Owner::Server { + let mut documents = workspace + .set_documents(db) + .with_durability(salsa::Durability::LOW) + .to(FxHashSet::default()); + + documents.remove(&document); + workspace + .set_documents(db) + .with_durability(salsa::Durability::MEDIUM) + .to(documents); + + changed = true; + } + } + } + } + notify::EventKind::Any | notify::EventKind::Access(_) | notify::EventKind::Other => {} + }; + + if changed { + self.publish_diagnostics_with_delay(); + } + } + fn process_messages(&mut self) -> Result<()> { loop { crossbeam_channel::select! { @@ -766,8 +819,7 @@ impl Server { return Ok(()); } - self.register_incoming_request(request.id.clone()); - if let Some(response) = RequestDispatcher::new(request) + if let Some(response) = dispatch::RequestDispatcher::new(request) .on::<DocumentLinkRequest, _>(|id, params| self.document_link(id, params))? .on::<FoldingRangeRequest, _>(|id, params| self.folding_range(id, params))? .on::<References, _>(|id, params| self.references(id, params))? @@ -777,12 +829,10 @@ impl Server { })? .on::<WorkspaceSymbol, _>(|id, params| self.workspace_symbols(id, params))? .on::<Completion, _>(|id, params| { - #[cfg(feature = "completion")] self.completion(id, params)?; Ok(()) })? .on::<ResolveCompletionItem, _>(|id, params| { - #[cfg(feature = "completion")] self.completion_resolve(id, params)?; Ok(()) })? @@ -803,13 +853,19 @@ impl Server { .on::<SemanticTokensRangeRequest, _>(|id, params| { self.semantic_tokens_range(id, params) })? + .on::<InlayHintRequest, _>(|id,params| { + self.inlay_hints(id, params) + })? + .on::<InlayHintResolveRequest,_>(|id, params| { + self.inlay_hint_resolve(id, params) + })? .default() { self.connection.sender.send(response.into())?; } } Message::Notification(notification) => { - NotificationDispatcher::new(notification) + dispatch::NotificationDispatcher::new(notification) .on::<Cancel, _>(|params| self.cancel(params))? .on::<DidChangeConfiguration, _>(|params| { self.did_change_configuration(params) @@ -824,26 +880,39 @@ impl Server { .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)?; - } + self.client.recv_response(response)?; } }; }, recv(&self.internal_rx) -> msg => { match msg? { InternalMessage::SetDistro(distro) => { - self.workspace.environment.resolver = Arc::new(distro.resolver); - self.reparse_all()?; + let db = self.engine.write(); + Workspace::get(db) + .set_file_name_db(db) + .with_durability(salsa::Durability::HIGH) + .to(distro.file_name_db); } InternalMessage::SetOptions(options) => { - self.workspace.environment.options = options; - self.reparse_all()?; + self.update_options(options); + } + InternalMessage::FileEvent(event) => { + self.handle_file_event(event); + } + InternalMessage::ForwardSearch(uri) => { + self.forward_search_internal(uri, None, |_| ())?; + } + InternalMessage::Diagnostics => { + self.publish_diagnostics()?; + } + InternalMessage::ChktexResult(uri, diagnostics) => { + let db = self.engine.write(); + let workspace = Workspace::get(db); + if let Some(document) = workspace.lookup_uri(db, &uri) { + document.linter(db).set_chktex(db).to(diagnostics); + } + + self.publish_diagnostics()?; } }; } @@ -854,63 +923,34 @@ impl Server { pub fn run(mut self) -> Result<()> { self.initialize()?; self.process_messages()?; - self.pool.lock().unwrap().join(); + self.pool.join(); + self.engine.finish(); Ok(()) } } -fn create_debouncer( - lsp_sender: Sender<Message>, - diagnostic_manager: DiagnosticManager, -) -> debouncer::Sender<Workspace> { - let (tx, rx) = debouncer::unbounded(); - std::thread::spawn(move || { - while let Ok(workspace) = rx.recv() { - if let Err(why) = publish_diagnostics(&lsp_sender, &diagnostic_manager, &workspace) { - warn!("Failed to publish diagnostics: {}", why); - } - } - }); - - tx +struct FileWatcher { + watcher: notify::RecommendedWatcher, + watched_dirs: FxHashSet<PathBuf>, } -fn publish_diagnostics( - lsp_sender: &Sender<lsp_server::Message>, - diagnostic_manager: &DiagnosticManager, - workspace: &Workspace, -) -> Result<()> { - for document in workspace.documents_by_uri.values() { - if matches!(document.data, DocumentData::BuildLog(_)) { - continue; - } +impl FileWatcher { + pub fn new(sender: Sender<InternalMessage>) -> Result<Self> { + let handle = move |event| { + if let Ok(event) = event { + sender.send(InternalMessage::FileEvent(event)).unwrap(); + } + }; - let diagnostics = diagnostic_manager.publish(workspace, &document.uri); - send_notification::<PublishDiagnostics>( - lsp_sender, - PublishDiagnosticsParams { - uri: document.uri.as_ref().clone(), - version: None, - diagnostics, - }, - )?; + Ok(Self { + watcher: notify::recommended_watcher(handle)?, + watched_dirs: FxHashSet::default(), + }) } - Ok(()) -} - -fn apply_document_edit(old_text: &mut String, changes: Vec<TextDocumentContentChangeEvent>) { - for change in changes { - let line_index = LineIndex::new(old_text); - match change.range { - Some(range) => { - let range = std::ops::Range::<usize>::from(line_index.offset_lsp_range(range)); - old_text.replace_range(range, &change.text); - } - None => { - *old_text = change.text; - } - }; + pub fn watch(&mut self, db: &dyn Db) { + let workspace = Workspace::get(db); + workspace.watch(db, &mut self.watcher, &mut self.watched_dirs); } } @@ -933,3 +973,29 @@ impl lsp_types::request::Request for ForwardSearchRequest { const METHOD: &'static str = "textDocument/forwardSearch"; } + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr)] +#[repr(i32)] +pub enum ForwardSearchStatus { + SUCCESS = 0, + ERROR = 1, + FAILURE = 2, + UNCONFIGURED = 3, +} + +impl From<forward_search::Error> for ForwardSearchStatus { + fn from(err: forward_search::Error) -> Self { + match err { + forward_search::Error::TexNotFound(_) => ForwardSearchStatus::FAILURE, + forward_search::Error::PdfNotFound(_) => ForwardSearchStatus::ERROR, + forward_search::Error::NoLocalFile(_) => ForwardSearchStatus::FAILURE, + forward_search::Error::Unconfigured => ForwardSearchStatus::UNCONFIGURED, + forward_search::Error::Spawn(_) => ForwardSearchStatus::ERROR, + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct ForwardSearchResult { + pub status: ForwardSearchStatus, +} |