diff options
Diffstat (limited to 'cli/lsp/dispatch.rs')
-rw-r--r-- | cli/lsp/dispatch.rs | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/cli/lsp/dispatch.rs b/cli/lsp/dispatch.rs new file mode 100644 index 000000000..774bdcef9 --- /dev/null +++ b/cli/lsp/dispatch.rs @@ -0,0 +1,185 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +use super::state::ServerState; +use super::state::ServerStateSnapshot; +use super::state::Task; +use super::utils::from_json; +use super::utils::is_canceled; + +use deno_core::error::custom_error; +use deno_core::error::AnyError; +use lsp_server::ErrorCode; +use lsp_server::Notification; +use lsp_server::Request; +use lsp_server::RequestId; +use lsp_server::Response; +use serde::de::DeserializeOwned; +use serde::Serialize; +use std::fmt; +use std::panic; + +pub struct NotificationDispatcher<'a> { + pub notification: Option<Notification>, + pub server_state: &'a mut ServerState, +} + +impl<'a> NotificationDispatcher<'a> { + pub fn on<N>( + &mut self, + f: fn(&mut ServerState, N::Params) -> Result<(), AnyError>, + ) -> Result<&mut Self, AnyError> + where + N: lsp_types::notification::Notification + 'static, + N::Params: DeserializeOwned + Send + 'static, + { + let notification = match self.notification.take() { + Some(it) => it, + None => return Ok(self), + }; + let params = match notification.extract::<N::Params>(N::METHOD) { + Ok(it) => it, + Err(notification) => { + self.notification = Some(notification); + return Ok(self); + } + }; + f(self.server_state, params)?; + Ok(self) + } + + pub fn finish(&mut self) { + if let Some(notification) = &self.notification { + if !notification.method.starts_with("$/") { + error!("unhandled notification: {:?}", notification); + } + } + } +} + +fn result_to_response<R>( + id: RequestId, + result: Result<R::Result, AnyError>, +) -> Response +where + R: lsp_types::request::Request + 'static, + R::Params: DeserializeOwned + 'static, + R::Result: Serialize + 'static, +{ + match result { + Ok(response) => Response::new_ok(id, &response), + Err(err) => { + if is_canceled(&*err) { + Response::new_err( + id, + ErrorCode::ContentModified as i32, + "content modified".to_string(), + ) + } else { + Response::new_err(id, ErrorCode::InternalError as i32, err.to_string()) + } + } + } +} + +pub struct RequestDispatcher<'a> { + pub request: Option<Request>, + pub server_state: &'a mut ServerState, +} + +impl<'a> RequestDispatcher<'a> { + pub fn finish(&mut self) { + if let Some(request) = self.request.take() { + error!("unknown request: {:?}", request); + let response = Response::new_err( + request.id, + ErrorCode::MethodNotFound as i32, + "unknown request".to_string(), + ); + self.server_state.respond(response); + } + } + + /// Handle a request which will respond to the LSP client asynchronously via + /// a spawned thread. + pub fn on<R>( + &mut self, + f: fn(ServerStateSnapshot, R::Params) -> Result<R::Result, AnyError>, + ) -> &mut Self + where + R: lsp_types::request::Request + 'static, + R::Params: DeserializeOwned + Send + fmt::Debug + 'static, + R::Result: Serialize + 'static, + { + let (id, params) = match self.parse::<R>() { + Some(it) => it, + None => return self, + }; + self.server_state.spawn({ + let state = self.server_state.snapshot(); + move || { + let result = f(state, params); + Task::Response(result_to_response::<R>(id, result)) + } + }); + + self + } + + /// Handle a request which will respond synchronously, returning a result if + /// the request cannot be handled or has issues. + pub fn on_sync<R>( + &mut self, + f: fn(&mut ServerState, R::Params) -> Result<R::Result, AnyError>, + ) -> Result<&mut Self, AnyError> + where + R: lsp_types::request::Request + 'static, + R::Params: DeserializeOwned + panic::UnwindSafe + fmt::Debug + 'static, + R::Result: Serialize + 'static, + { + let (id, params) = match self.parse::<R>() { + Some(it) => it, + None => return Ok(self), + }; + let state = panic::AssertUnwindSafe(&mut *self.server_state); + + let response = panic::catch_unwind(move || { + let result = f(state.0, params); + result_to_response::<R>(id, result) + }) + .map_err(|_err| { + custom_error( + "SyncTaskPanic", + format!("sync task {:?} panicked", R::METHOD), + ) + })?; + self.server_state.respond(response); + Ok(self) + } + + fn parse<R>(&mut self) -> Option<(RequestId, R::Params)> + where + R: lsp_types::request::Request + 'static, + R::Params: DeserializeOwned + 'static, + { + let request = match &self.request { + Some(request) if request.method == R::METHOD => { + self.request.take().unwrap() + } + _ => return None, + }; + + let response = from_json(R::METHOD, request.params); + match response { + Ok(params) => Some((request.id, params)), + Err(err) => { + let response = Response::new_err( + request.id, + ErrorCode::InvalidParams as i32, + err.to_string(), + ); + self.server_state.respond(response); + None + } + } + } +} |