#![allow(clippy::mutable_key_type)]
use std::path::PathBuf;
use std::time::{Duration, Instant};
use crossbeam_channel::select;
use lsp_server::{Connection, Message};
use lsp_types::InitializeParams;
use lsp_types::notification::Notification as _;
use rowan::GreenNode;
mod config;
mod context;
mod conversions;
mod dispatch;
mod documents;
pub(crate) mod global_state;
mod handlers;
mod helpers;
mod navigation;
mod symbols;
mod task_pool;
#[doc(hidden)]
pub mod testing;
mod uri_ext;
pub(crate) use global_state::{ClientSender, GlobalState};
#[doc(hidden)]
pub use testing::{LspTester, WorkspaceSymbolSummary};
#[doc(hidden)]
pub use uri_ext::UriExt;
#[derive(Clone)]
pub struct DocumentState {
pub path: Option<PathBuf>,
pub salsa_file: crate::salsa::FileText,
pub salsa_config: crate::salsa::FileConfig,
pub tree: GreenNode,
}
#[derive(Debug, Clone, Default)]
pub struct LspRuntimeSettings {
pub experimental_incremental_parsing: bool,
}
fn to_io<E: std::fmt::Display>(e: E) -> std::io::Error {
std::io::Error::other(e.to_string())
}
pub fn run() -> std::io::Result<()> {
let (connection, io_threads) = Connection::stdio();
let (id, init_value) = connection.initialize_start().map_err(to_io)?;
let init_result = serde_json::json!({
"capabilities": dispatch::server_capabilities(),
"serverInfo": dispatch::server_info(),
});
connection
.initialize_finish(id, init_result)
.map_err(to_io)?;
let init_params: InitializeParams = serde_json::from_value(init_value).map_err(to_io)?;
let mut gs = GlobalState::new(ClientSender::new(connection.sender.clone()));
gs.on_initialize(init_params);
gs.on_initialized();
main_loop(&mut gs, &connection)?;
drop(gs);
drop(connection);
io_threads.join()?;
Ok(())
}
const IDLE_TICK: Duration = Duration::from_secs(3600);
const SLOW_STEP: Duration = Duration::from_millis(50);
fn log_if_slow(label: &str, start: Instant) {
let elapsed = start.elapsed();
if elapsed >= SLOW_STEP {
log::warn!("main-loop step blocked {elapsed:?}: {label}");
}
}
fn main_loop(gs: &mut GlobalState, conn: &Connection) -> std::io::Result<()> {
let task_rx = gs.task_receiver.clone();
loop {
let timeout = gs.next_lint_timeout().unwrap_or(IDLE_TICK);
select! {
recv(conn.receiver) -> msg => {
let Ok(msg) = msg else { break };
match msg {
Message::Request(req) => {
if conn.handle_shutdown(&req).map_err(to_io)? {
break;
}
let label = format!("request {} (id {})", req.method, req.id);
let t = Instant::now();
gs.on_request(req);
log_if_slow(&label, t);
}
Message::Notification(not) => {
if not.method == lsp_types::notification::Exit::METHOD {
break;
}
let label = format!("notification {}", not.method);
let t = Instant::now();
gs.on_notification(not);
log_if_slow(&label, t);
}
Message::Response(resp) => {
let t = Instant::now();
gs.on_client_response(resp);
log_if_slow("client response", t);
}
}
}
recv(task_rx) -> task => {
if let Ok(task) = task {
let t = Instant::now();
gs.on_task(task);
log_if_slow("worker task result", t);
}
}
default(timeout) => {}
}
let t = Instant::now();
gs.dispatch_due_lints();
log_if_slow("dispatch_due_lints", t);
}
Ok(())
}