//! See [RequestDispatcher]. use std::{fmt, panic, thread}; use serde::{de::DeserializeOwned, Serialize}; use crate::{ global_state::{GlobalState, GlobalStateSnapshot}, lsp_utils::is_cancelled, main_loop::Task, LspError, Result, }; /// A visitor for routing a raw JSON request to an appropriate handler function. /// /// Most requests are read-only and async and are handled on the threadpool /// (`on` method). /// /// Some read-only requests are latency sensitive, and are immediately handled /// on the main loop thread (`on_sync`). These are typically typing-related /// requests. /// /// Some requests modify the state, and are run on the main thread to get /// `&mut` (`on_sync_mut`). /// /// Read-only requests are wrapped into `catch_unwind` -- they don't modify the /// state, so it's OK to recover from their failures. pub(crate) struct RequestDispatcher<'a> { pub(crate) req: Option, pub(crate) global_state: &'a mut GlobalState, } impl<'a> RequestDispatcher<'a> { /// Dispatches the request onto the current thread, given full access to /// mutable global state. Unlike all other methods here, this one isn't /// guarded by `catch_unwind`, so, please, don't make bugs :-) pub(crate) fn on_sync_mut( &mut self, f: fn(&mut GlobalState, R::Params) -> Result, ) -> Result<&mut Self> where R: lsp_types::request::Request + 'static, R::Params: DeserializeOwned + panic::UnwindSafe + fmt::Debug + 'static, R::Result: Serialize + 'static, { let (id, params, panic_context) = match self.parse::() { Some(it) => it, None => return Ok(self), }; let _pctx = stdx::panic_context::enter(panic_context); let result = f(&mut self.global_state, params); let response = result_to_response::(id, result); self.global_state.respond(response); Ok(self) } /// Dispatches the request onto the current thread. pub(crate) fn on_sync( &mut self, f: fn(GlobalStateSnapshot, R::Params) -> Result, ) -> Result<&mut Self> where R: lsp_types::request::Request + 'static, R::Params: DeserializeOwned + panic::UnwindSafe + fmt::Debug + 'static, R::Result: Serialize + 'static, { let (id, params, panic_context) = match self.parse::() { Some(it) => it, None => return Ok(self), }; let global_state_snapshot = self.global_state.snapshot(); let result = panic::catch_unwind(move || { let _pctx = stdx::panic_context::enter(panic_context); f(global_state_snapshot, params) }); let response = thread_result_to_response::(id, result); self.global_state.respond(response); Ok(self) } /// Dispatches the request onto thread pool pub(crate) fn on( &mut self, f: fn(GlobalStateSnapshot, R::Params) -> Result, ) -> &mut Self where R: lsp_types::request::Request + 'static, R::Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug + 'static, R::Result: Serialize + 'static, { let (id, params, panic_context) = match self.parse::() { Some(it) => it, None => return self, }; self.global_state.task_pool.handle.spawn({ let world = self.global_state.snapshot(); move || { let result = panic::catch_unwind(move || { let _pctx = stdx::panic_context::enter(panic_context); f(world, params) }); let response = thread_result_to_response::(id, result); Task::Response(response) } }); self } pub(crate) fn finish(&mut self) { if let Some(req) = self.req.take() { tracing::error!("unknown request: {:?}", req); let response = lsp_server::Response::new_err( req.id, lsp_server::ErrorCode::MethodNotFound as i32, "unknown request".to_string(), ); self.global_state.respond(response); } } fn parse(&mut self) -> Option<(lsp_server::RequestId, R::Params, String)> where R: lsp_types::request::Request + 'static, R::Params: DeserializeOwned + fmt::Debug + 'static, { let req = match &self.req { Some(req) if req.method == R::METHOD => self.req.take().unwrap(), _ => return None, }; let res = crate::from_json(R::METHOD, req.params); match res { Ok(params) => { let panic_context = format!("\nversion: {}\nrequest: {} {:#?}", env!("REV"), R::METHOD, params); Some((req.id, params, panic_context)) } Err(err) => { let response = lsp_server::Response::new_err( req.id, lsp_server::ErrorCode::InvalidParams as i32, err.to_string(), ); self.global_state.respond(response); None } } } } fn thread_result_to_response( id: lsp_server::RequestId, result: thread::Result>, ) -> lsp_server::Response where R: lsp_types::request::Request + 'static, R::Params: DeserializeOwned + 'static, R::Result: Serialize + 'static, { match result { Ok(result) => result_to_response::(id, result), Err(panic) => { let mut message = "server panicked".to_string(); let panic_message = panic .downcast_ref::() .map(String::as_str) .or_else(|| panic.downcast_ref::<&str>().copied()); if let Some(panic_message) = panic_message { message.push_str(": "); message.push_str(panic_message) }; lsp_server::Response::new_err(id, lsp_server::ErrorCode::InternalError as i32, message) } } } fn result_to_response( id: lsp_server::RequestId, result: Result, ) -> lsp_server::Response where R: lsp_types::request::Request + 'static, R::Params: DeserializeOwned + 'static, R::Result: Serialize + 'static, { match result { Ok(resp) => lsp_server::Response::new_ok(id, &resp), Err(e) => match e.downcast::() { Ok(lsp_error) => lsp_server::Response::new_err(id, lsp_error.code, lsp_error.message), Err(e) => { if is_cancelled(&*e) { lsp_server::Response::new_err( id, lsp_server::ErrorCode::ContentModified as i32, "content modified".to_string(), ) } else { lsp_server::Response::new_err( id, lsp_server::ErrorCode::InternalError as i32, e.to_string(), ) } } }, } } pub(crate) struct NotificationDispatcher<'a> { pub(crate) not: Option, pub(crate) global_state: &'a mut GlobalState, } impl<'a> NotificationDispatcher<'a> { pub(crate) fn on( &mut self, f: fn(&mut GlobalState, N::Params) -> Result<()>, ) -> Result<&mut Self> where N: lsp_types::notification::Notification + 'static, N::Params: DeserializeOwned + Send + 'static, { let not = match self.not.take() { Some(it) => it, None => return Ok(self), }; let params = match not.extract::(N::METHOD) { Ok(it) => it, Err(not) => { self.not = Some(not); return Ok(self); } }; let _pctx = stdx::panic_context::enter(format!( "\nversion: {}\nnotification: {}", env!("REV"), N::METHOD )); f(self.global_state, params)?; Ok(self) } pub(crate) fn finish(&mut self) { if let Some(not) = &self.not { if !not.method.starts_with("$/") { tracing::error!("unhandled notification: {:?}", not); } } } }