summaryrefslogtreecommitdiff
path: root/support/texlab/src/server.rs
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/server.rs')
-rw-r--r--support/texlab/src/server.rs1156
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 = &params.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(&params.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, &params);
- 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, &params));
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, &params.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, &params.options)
+ });
+
Ok(())
}
- fn execute_command(&self, id: RequestId, params: ExecuteCommandParams) -> Result<()> {
- self.spawn(move |server| {
- let result = execute_command(&server.workspace, &params.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, &params.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,
+}