use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Instant;
use crossbeam_channel::{Receiver, Sender};
use lsp_server::{Message, Notification, Request, RequestId, Response};
use lsp_types::{Diagnostic, MessageType, Uri};
use super::DocumentState;
use super::LspRuntimeSettings;
use super::config::{load_config, load_config_with_source};
use super::task_pool::{TaskPool, default_pool_size};
use crate::Config;
use crate::config::ConfigSource;
use crate::syntax::SyntaxNode;
pub(crate) type DocumentMap = HashMap<String, DocumentState>;
pub(crate) const DIAGNOSTICS_DEBOUNCE: std::time::Duration = std::time::Duration::from_millis(200);
#[derive(Clone)]
pub(crate) struct ClientSender {
sender: Sender<Message>,
}
impl ClientSender {
pub(crate) fn new(sender: Sender<Message>) -> Self {
Self { sender }
}
pub(crate) fn publish_diagnostics(
&self,
uri: Uri,
diagnostics: Vec<Diagnostic>,
version: Option<i32>,
) {
self.notify::<lsp_types::notification::PublishDiagnostics>(
lsp_types::PublishDiagnosticsParams {
uri,
diagnostics,
version,
},
);
}
pub(crate) fn log_message(&self, typ: MessageType, message: impl Into<String>) {
self.notify::<lsp_types::notification::LogMessage>(lsp_types::LogMessageParams {
typ,
message: message.into(),
});
}
pub(crate) fn notify<N: lsp_types::notification::Notification>(&self, params: N::Params) {
self.send(Message::Notification(Notification::new(
N::METHOD.to_owned(),
params,
)));
}
pub(crate) fn send(&self, message: Message) {
if let Err(err) = self.sender.send(message) {
log::warn!("LSP client channel closed; dropping message: {err}");
}
}
}
pub(crate) struct StateSnapshot {
analysis: crate::salsa::Analysis,
pub(crate) document_map: Arc<DocumentMap>,
pub(crate) workspace_root: Option<PathBuf>,
}
impl StateSnapshot {
pub(crate) fn db(&self) -> &dyn crate::salsa::Db {
self.analysis.db()
}
pub(crate) fn document_state(&self, uri: &Uri) -> Option<DocumentState> {
self.document_map.get(&uri.to_string()).cloned()
}
pub(crate) fn document_content(&self, uri: &Uri) -> Option<String> {
let state = self.document_map.get(&uri.to_string())?;
Some(state.salsa_file.content_or_empty(self.db()).to_string())
}
pub(crate) fn document_content_and_tree(&self, uri: &Uri) -> Option<(String, SyntaxNode)> {
let state = self.document_map.get(&uri.to_string())?;
Some((
state.salsa_file.content_or_empty(self.db()).to_string(),
SyntaxNode::new_root(state.tree.clone()),
))
}
pub(crate) fn parsed_tree(&self, uri: &Uri) -> Option<SyntaxNode> {
let state = self.document_map.get(&uri.to_string())?;
Some(crate::salsa::parsed_tree_root(
self.db(),
state.salsa_file,
state.salsa_config,
))
}
pub(crate) fn config(&self, uri: &Uri) -> Config {
load_config(&self.workspace_root, Some(uri))
}
pub(crate) fn document_and_config(&self, uri: &Uri) -> Option<(String, Config)> {
let content = self.document_content(uri)?;
Some((content, self.config(uri)))
}
pub(crate) fn document_config_and_source(
&self,
uri: &Uri,
) -> Option<(String, Config, ConfigSource, Option<PathBuf>)> {
let content = self.document_content(uri)?;
let (config, source) = load_config_with_source(&self.workspace_root, Some(uri));
Some((content, config, source, self.workspace_root.clone()))
}
pub(crate) fn definition_index_with_includes(
&self,
uri: &Uri,
) -> crate::salsa::DefinitionIndex {
let Some(state) = self.document_map.get(&uri.to_string()) else {
return crate::salsa::DefinitionIndex::default();
};
let (salsa_file, salsa_config) = (state.salsa_file, state.salsa_config);
let db = self.db();
let graph = crate::salsa::project_structure(db, salsa_file, salsa_config).clone();
let mut index = crate::salsa::definition_index(db, salsa_file, salsa_config).clone();
for path in graph.documents().iter() {
if let Some(include_file) = db.file_text(path.clone()) {
let include_index = crate::salsa::definition_index(db, include_file, salsa_config);
index.merge_from(include_index);
}
}
index
}
}
pub(crate) enum Task {
Response(Response),
Diagnostics {
generation: u64,
key: String,
publishes: Vec<(Uri, Option<i32>, Vec<Diagnostic>)>,
},
}
pub(crate) struct GlobalState {
pub(crate) sender: ClientSender,
pub(crate) document_map: Arc<DocumentMap>,
pub(crate) workspace_root: Option<PathBuf>,
pub(crate) runtime_settings: LspRuntimeSettings,
pub(crate) salsa: crate::salsa::SalsaDb,
pub(crate) pool: TaskPool<Task>,
pub(crate) fmt_pool: TaskPool<Task>,
pub(crate) task_receiver: Receiver<Task>,
pub(crate) in_flight: HashSet<RequestId>,
pub(crate) cancelled: HashSet<RequestId>,
pub(crate) outgoing: HashMap<RequestId, &'static str>,
pub(crate) next_outgoing_id: i32,
pub(crate) lint_deadlines: HashMap<String, Instant>,
pub(crate) lint_generations: HashMap<String, u64>,
}
impl GlobalState {
pub(crate) fn new(sender: ClientSender) -> Self {
let (task_tx, task_receiver) = crossbeam_channel::unbounded::<Task>();
let pool = TaskPool::new(task_tx.clone(), default_pool_size());
let fmt_pool = TaskPool::new(task_tx, 1);
Self {
sender,
document_map: Arc::new(DocumentMap::new()),
workspace_root: None,
runtime_settings: LspRuntimeSettings::default(),
salsa: crate::salsa::SalsaDb::default(),
pool,
fmt_pool,
task_receiver,
in_flight: HashSet::new(),
cancelled: HashSet::new(),
outgoing: HashMap::new(),
next_outgoing_id: 1,
lint_deadlines: HashMap::new(),
lint_generations: HashMap::new(),
}
}
pub(crate) fn snapshot(&self) -> StateSnapshot {
StateSnapshot {
analysis: crate::salsa::Analysis::new(self.salsa.clone()),
document_map: Arc::clone(&self.document_map),
workspace_root: self.workspace_root.clone(),
}
}
pub(crate) fn document_map_mut(&mut self) -> &mut DocumentMap {
Arc::make_mut(&mut self.document_map)
}
pub(crate) fn respond(&mut self, response: Response) {
self.in_flight.remove(&response.id);
self.cancelled.remove(&response.id);
self.sender.send(Message::Response(response));
}
pub(crate) fn send_request<R: lsp_types::request::Request>(&mut self, params: R::Params) {
let id = RequestId::from(self.next_outgoing_id);
self.next_outgoing_id += 1;
self.outgoing.insert(id.clone(), R::METHOD);
self.sender.send(Message::Request(Request::new(
id,
R::METHOD.to_owned(),
params,
)));
}
pub(crate) fn on_client_response(&mut self, response: Response) {
if let Some(method) = self.outgoing.remove(&response.id)
&& let Some(err) = response.error
{
log::warn!("server request {method} failed: {}", err.message);
}
}
pub(crate) fn schedule_lint(&mut self, uri: &Uri) {
let key = uri.to_string();
self.lint_deadlines
.insert(key.clone(), Instant::now() + DIAGNOSTICS_DEBOUNCE);
*self.lint_generations.entry(key).or_default() += 1;
}
pub(crate) fn forget_lint(&mut self, uri: &Uri) {
let key = uri.to_string();
self.lint_deadlines.remove(&key);
self.lint_generations.remove(&key);
}
}