| // Copyright 2017 The Rust Project Developers. See the COPYRIGHT |
| // file at the top-level directory of this distribution and at |
| // http://rust-lang.org/COPYRIGHT. |
| // |
| // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| // option. This file may not be copied, modified, or distributed |
| // except according to those terms. |
| |
| //! Implementation of the server loop, and traits for extending server |
| //! interactions (for example, to add support for handling new types of |
| //! requests). |
| |
| use analysis::AnalysisHost; |
| use jsonrpc_core::{self as jsonrpc, Id}; |
| use vfs::Vfs; |
| use serde; |
| use serde_json; |
| use serde::Deserialize; |
| |
| use version; |
| use lsp_data::*; |
| use actions::{notifications, requests, ActionContext}; |
| use config::Config; |
| pub use server::io::{MessageReader, Output}; |
| use server::io::{StdioMsgReader, StdioOutput}; |
| use server::dispatch::Dispatcher; |
| pub use server::dispatch::{RequestAction, ResponseError}; |
| |
| use std::fmt; |
| use std::marker::PhantomData; |
| use std::path::PathBuf; |
| use std::sync::{Arc, Mutex}; |
| use std::sync::atomic::{AtomicBool, Ordering}; |
| use std::time::Instant; |
| |
| mod io; |
| mod dispatch; |
| |
| /// Run the Rust Language Server. |
| pub fn run_server(analysis: Arc<AnalysisHost>, vfs: Arc<Vfs>) { |
| debug!("Language Server starting up. Version: {}", version()); |
| let service = LsService::new( |
| analysis, |
| vfs, |
| Arc::new(Mutex::new(Config::default())), |
| Box::new(StdioMsgReader), |
| StdioOutput::new(), |
| ); |
| LsService::run(service); |
| debug!("Server shutting down"); |
| } |
| |
| /// A response that just acknowledges receipt of its request. |
| #[derive(Debug, Serialize)] |
| pub struct Ack; |
| |
| /// The lack of a response to a request. |
| #[derive(Debug)] |
| pub struct NoResponse; |
| |
| /// Empty extra parameters to some request or notification. |
| #[derive(Debug, Serialize, PartialEq)] |
| pub struct NoParams; |
| |
| impl<'de> Deserialize<'de> for NoParams { |
| fn deserialize<D>(_deserializer: D) -> Result<NoParams, D::Error> |
| where |
| D: serde::Deserializer<'de>, |
| { |
| Ok(NoParams) |
| } |
| } |
| |
| /// A response to some request. |
| pub trait Response { |
| /// Send the response along the given output. |
| fn send<O: Output>(&self, id: usize, out: &O); |
| } |
| |
| impl Response for NoResponse { |
| fn send<O: Output>(&self, _id: usize, _out: &O) {} |
| } |
| |
| impl<R: ::serde::Serialize + fmt::Debug> Response for R { |
| fn send<O: Output>(&self, id: usize, out: &O) { |
| out.success(id, &self); |
| } |
| } |
| |
| /// An action taken by the Rust Language Server. |
| pub trait Action { |
| /// Extra parameters that the action expects to receive. |
| type Params: serde::Serialize + for<'de> serde::Deserialize<'de> + Send; |
| |
| /// The well-known language server method string that identifies this |
| /// action's kind of request or notification. |
| const METHOD: &'static str; |
| } |
| |
| /// An action taken in response to some notification from the client. |
| /// Blocks stdin whilst being handled. |
| pub trait BlockingNotificationAction<'a>: Action { |
| /// |
| fn new(state: &'a mut LsState) -> Self; |
| |
| /// Handle this notification. |
| fn handle<O: Output>( |
| &mut self, |
| params: Self::Params, |
| ctx: &mut ActionContext, |
| out: O, |
| ) -> Result<(), ()>; |
| } |
| |
| /// A request that blocks stdin whilst being handled |
| pub trait BlockingRequestAction<'a>: Action { |
| /// |
| type Response: Response + fmt::Debug; |
| |
| /// |
| fn new(state: &'a mut LsState) -> Self; |
| |
| /// Handle request and send its response back along the given output. |
| fn handle<O: Output>( |
| &mut self, |
| id: usize, |
| params: Self::Params, |
| ctx: &mut ActionContext, |
| out: O, |
| ) -> Result<Self::Response, ()>; |
| } |
| |
| /// A request that gets JSON serialized in the language server protocol. |
| pub struct Request<A: Action> { |
| /// The unique request id. |
| pub id: usize, |
| /// The time the request was received / processed by the main stdin reading thread. |
| pub received: Instant, |
| /// The extra action-specific parameters. |
| pub params: A::Params, |
| /// This request's handler action. |
| pub _action: PhantomData<A>, |
| } |
| |
| /// A notification that gets JSON serialized in the language server protocol. |
| #[derive(Debug, PartialEq)] |
| pub struct Notification<A: Action> { |
| /// The extra action-specific parameters. |
| pub params: A::Params, |
| /// The action responsible for this notification. |
| pub _action: PhantomData<A>, |
| } |
| |
| impl<'a, A: BlockingRequestAction<'a>> Request<A> { |
| fn blocking_dispatch<O: Output>( |
| self, |
| state: &'a mut LsState, |
| ctx: &mut ActionContext, |
| out: &O, |
| ) -> Result<A::Response, ()> { |
| let mut action = A::new(state); |
| let result = action.handle(self.id, self.params, ctx, out.clone())?; |
| result.send(self.id, out); |
| Ok(result) |
| } |
| } |
| |
| impl<'a, A: BlockingNotificationAction<'a>> Notification<A> { |
| fn dispatch<O: Output>( |
| self, |
| state: &'a mut LsState, |
| ctx: &mut ActionContext, |
| out: O, |
| ) -> Result<(), ()> { |
| let mut action = A::new(state); |
| action.handle(self.params, ctx, out)?; |
| Ok(()) |
| } |
| } |
| |
| impl<'a, A: Action> fmt::Display for Request<A> { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| json!({ |
| "jsonrpc": "2.0", |
| "id": self.id, |
| "method": A::METHOD, |
| "params": self.params, |
| }).fmt(f) |
| } |
| } |
| |
| impl<'a, A: BlockingNotificationAction<'a>> fmt::Display for Notification<A> { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| json!({ |
| "jsonrpc": "2.0", |
| "method": A::METHOD, |
| "params": self.params, |
| }).fmt(f) |
| } |
| } |
| |
| /// A service implementing a language server. |
| pub struct LsService<O: Output> { |
| msg_reader: Box<MessageReader + Send + Sync>, |
| output: O, |
| ctx: ActionContext, |
| /// The public shared state for this language server. |
| pub state: LsState, |
| dispatcher: Dispatcher, |
| } |
| |
| /// Public shared state for this language server. |
| #[derive(Debug)] |
| pub struct LsState { |
| shut_down: AtomicBool, |
| } |
| |
| /// A request to shutdown the language server and perform clean up, but not to |
| /// exit the process. After receiving a response to this request, the client |
| /// will send an `exit` notification, at which point we will actually exit the |
| /// process. |
| pub struct ShutdownRequest<'a> { |
| state: &'a mut LsState, |
| } |
| |
| impl<'a> Action for ShutdownRequest<'a> { |
| type Params = NoParams; |
| const METHOD: &'static str = "shutdown"; |
| } |
| |
| impl<'a> BlockingRequestAction<'a> for ShutdownRequest<'a> { |
| type Response = Ack; |
| |
| fn new(state: &'a mut LsState) -> Self { |
| ShutdownRequest { state } |
| } |
| |
| fn handle<O: Output>( |
| &mut self, |
| _id: usize, |
| _params: Self::Params, |
| _ctx: &mut ActionContext, |
| _out: O, |
| ) -> Result<Self::Response, ()> { |
| self.state.shut_down.store(true, Ordering::SeqCst); |
| Ok(Ack) |
| } |
| } |
| |
| /// Notification that it is time for the language server to exit its process. |
| #[derive(Debug)] |
| pub struct ExitNotification<'a> { |
| state: &'a mut LsState, |
| } |
| |
| impl<'a> Action for ExitNotification<'a> { |
| type Params = NoParams; |
| const METHOD: &'static str = "exit"; |
| } |
| |
| impl<'a> BlockingNotificationAction<'a> for ExitNotification<'a> { |
| fn new(state: &'a mut LsState) -> Self { |
| ExitNotification { state } |
| } |
| |
| fn handle<O: Output>( |
| &mut self, |
| _params: Self::Params, |
| _ctx: &mut ActionContext, |
| _out: O, |
| ) -> Result<(), ()> { |
| let shut_down = self.state.shut_down.load(Ordering::SeqCst); |
| ::std::process::exit(if shut_down { 0 } else { 1 }); |
| } |
| } |
| |
| fn get_root_path(params: &InitializeParams) -> PathBuf { |
| params |
| .root_uri |
| .as_ref() |
| .map(|uri| { |
| assert!(uri.scheme() == "file"); |
| uri.to_file_path().expect("Could not convert URI to path") |
| }) |
| .unwrap_or_else(|| { |
| params |
| .root_path |
| .as_ref() |
| .map(PathBuf::from) |
| .expect("No root path or URI") |
| }) |
| } |
| |
| /// A request to initialize this server. |
| pub struct InitializeRequest; |
| |
| impl<'a> Action for InitializeRequest { |
| const METHOD: &'static str = "initialize"; |
| type Params = InitializeParams; |
| } |
| |
| impl<'a> BlockingRequestAction<'a> for InitializeRequest { |
| type Response = NoResponse; |
| |
| fn new(_: &'a mut LsState) -> Self { |
| InitializeRequest |
| } |
| |
| fn handle<O: Output>( |
| &mut self, |
| id: usize, |
| params: Self::Params, |
| ctx: &mut ActionContext, |
| out: O, |
| ) -> Result<NoResponse, ()> { |
| let init_options: InitializationOptions = params |
| .initialization_options |
| .as_ref() |
| .and_then(|options| serde_json::from_value(options.to_owned()).ok()) |
| .unwrap_or_default(); |
| |
| trace!("init: {:?}", init_options); |
| |
| let result = InitializeResult { |
| capabilities: ServerCapabilities { |
| text_document_sync: Some(TextDocumentSyncKind::Incremental), |
| hover_provider: Some(true), |
| completion_provider: Some(CompletionOptions { |
| resolve_provider: Some(true), |
| trigger_characters: vec![".".to_string(), ":".to_string()], |
| }), |
| definition_provider: Some(true), |
| references_provider: Some(true), |
| document_highlight_provider: Some(true), |
| document_symbol_provider: Some(true), |
| workspace_symbol_provider: Some(true), |
| code_action_provider: Some(true), |
| document_formatting_provider: Some(true), |
| execute_command_provider: Some(ExecuteCommandOptions { |
| commands: vec![ |
| "rls.applySuggestion".to_owned(), |
| "rls.deglobImports".to_owned(), |
| ], |
| }), |
| rename_provider: Some(true), |
| // These are supported if the `unstable_features` option is set. |
| // We'll update these capabilities dynamically when we get config |
| // info from the client. |
| document_range_formatting_provider: Some(false), |
| |
| code_lens_provider: None, |
| document_on_type_formatting_provider: None, |
| signature_help_provider: None, |
| }, |
| }; |
| out.success(id, &result); |
| |
| ctx.init(get_root_path(¶ms), &init_options, out); |
| |
| Ok(NoResponse) |
| } |
| } |
| |
| /// How should the server proceed? |
| #[derive(Eq, PartialEq, Debug, Clone, Copy)] |
| pub enum ServerStateChange { |
| /// Continue serving responses to requests and sending notifications to the |
| /// client. |
| Continue, |
| /// Stop the server. |
| Break, |
| } |
| |
| impl<O: Output> LsService<O> { |
| /// Construct a new language server service. |
| pub fn new( |
| analysis: Arc<AnalysisHost>, |
| vfs: Arc<Vfs>, |
| config: Arc<Mutex<Config>>, |
| reader: Box<MessageReader + Send + Sync>, |
| output: O, |
| ) -> LsService<O> { |
| let dispatcher = Dispatcher::new(output.clone()); |
| |
| LsService { |
| msg_reader: reader, |
| output: output, |
| ctx: ActionContext::new(analysis, vfs, config), |
| state: LsState { |
| shut_down: AtomicBool::new(false), |
| }, |
| dispatcher, |
| } |
| } |
| |
| /// Run this language service. |
| pub fn run(mut self) { |
| while self.handle_message() == ServerStateChange::Continue {} |
| } |
| |
| fn parse_message(&mut self, msg: &str) -> Result<Option<RawMessage>, jsonrpc::Error> { |
| // Parse the message. |
| let ls_command: serde_json::Value = |
| serde_json::from_str(msg).map_err(|_| jsonrpc::Error::parse_error())?; |
| |
| // Per JSON-RPC/LSP spec, Requests must have id, whereas Notifications can't |
| let id = ls_command |
| .get("id") |
| .map(|id| serde_json::from_value(id.to_owned()).unwrap()); |
| |
| let method = match ls_command.get("method") { |
| Some(method) => method, |
| // No method means this is a response to one of our requests. FIXME: we should |
| // confirm these, but currently just ignore them. |
| None => return Ok(None), |
| }; |
| |
| let method = method |
| .as_str() |
| .ok_or_else(|| jsonrpc::Error::invalid_request())? |
| .to_owned(); |
| |
| // Representing internally a missing parameter as Null instead of None, |
| // (Null being unused value of param by the JSON-RPC 2.0 spec) |
| // to unify the type handling – now the parameter type implements Deserialize. |
| let params = match ls_command.get("params").map(|p| p.to_owned()) { |
| Some(params @ serde_json::Value::Object(..)) => params, |
| Some(params @ serde_json::Value::Array(..)) => params, |
| None => serde_json::Value::Null, |
| // Null as input value is not allowed by JSON-RPC 2.0, |
| //but including it for robustness |
| Some(serde_json::Value::Null) => serde_json::Value::Null, |
| _ => return Err(jsonrpc::Error::invalid_request()), |
| }; |
| |
| Ok(Some(RawMessage { method, id, params })) |
| } |
| |
| fn dispatch_message(&mut self, msg: &RawMessage) -> Result<(), jsonrpc::Error> { |
| macro_rules! match_action { |
| ( |
| $method: expr; |
| notifications: $($n_action: ty),*; |
| blocking_requests: $($br_action: ty),*; |
| requests: $($request: ty),*; |
| ) => { |
| let mut handled = false; |
| trace!("Handling `{}`", $method); |
| $( |
| if $method == <$n_action as Action>::METHOD { |
| let notification = msg.parse_as_notification::<$n_action>()?; |
| if let Err(_) = notification.dispatch(&mut self.state, &mut self.ctx, self.output.clone()) { |
| debug!("Error handling notification: {:?}", msg); |
| } |
| handled = true; |
| } |
| )* |
| $( |
| if $method == <$br_action as Action>::METHOD { |
| let request = msg.parse_as_request::<$br_action>()?; |
| |
| // block until all nonblocking requests have been handled ensuring ordering |
| self.dispatcher.await_all_dispatched(); |
| |
| if let Err(_) = request.blocking_dispatch( |
| &mut self.state, |
| &mut self.ctx, |
| &self.output |
| ) { |
| debug!("Error handling request: {:?}", msg); |
| } |
| handled = true; |
| } |
| )* |
| $( |
| if $method == <$request as Action>::METHOD { |
| let request = (msg.parse_as_request::<$request>()?, self.ctx.inited()); |
| self.dispatcher.dispatch(request); |
| handled = true; |
| } |
| )* |
| if !handled { |
| debug!("Method not found: {}", $method); |
| } |
| } |
| } |
| |
| match_action!( |
| msg.method; |
| notifications: |
| ExitNotification, |
| notifications::Initialized, |
| notifications::DidOpen, |
| notifications::DidChange, |
| notifications::DidSave, |
| notifications::DidChangeConfiguration, |
| notifications::DidChangeWatchedFiles, |
| notifications::Cancel; |
| blocking_requests: |
| ShutdownRequest, |
| InitializeRequest, |
| requests::ResolveCompletion, |
| requests::ExecuteCommand, |
| requests::Formatting, |
| requests::RangeFormatting; |
| requests: |
| requests::Rename, |
| requests::CodeAction, |
| requests::DocumentHighlight, |
| requests::FindImpls, |
| requests::Symbols, |
| requests::Hover, |
| requests::WorkspaceSymbol, |
| requests::Definition, |
| requests::References, |
| requests::Completion; |
| ); |
| Ok(()) |
| } |
| |
| /// Read a message from the language server reader input and handle it with |
| /// the appropriate action. Returns a `ServerStateChange` that describes how |
| /// the service should proceed now that the message has been handled. |
| pub fn handle_message(&mut self) -> ServerStateChange { |
| let msg_string = match self.msg_reader.read_message() { |
| Some(m) => m, |
| None => { |
| debug!("Can't read message"); |
| self.output.failure(Id::Null, jsonrpc::Error::parse_error()); |
| return ServerStateChange::Break; |
| } |
| }; |
| |
| trace!("Read message `{}`", msg_string); |
| |
| let raw_message = match self.parse_message(&msg_string) { |
| Ok(Some(rm)) => rm, |
| Ok(None) => return ServerStateChange::Continue, |
| Err(e) => { |
| debug!("parsing error, {:?}", e); |
| self.output.failure(Id::Null, jsonrpc::Error::parse_error()); |
| return ServerStateChange::Break; |
| } |
| }; |
| |
| trace!("Parsed message `{:?}`", raw_message); |
| |
| // If we're in shutdown mode, ignore any messages other than 'exit'. |
| // This is not actually in the spec, I'm not sure we should do this, |
| // but it kinda makes sense. |
| { |
| let shut_down = self.state.shut_down.load(Ordering::SeqCst); |
| if shut_down && raw_message.method != ExitNotification::METHOD { |
| trace!("In shutdown mode, ignoring {:?}!", raw_message); |
| return ServerStateChange::Continue; |
| } |
| } |
| |
| if let Err(e) = self.dispatch_message(&raw_message) { |
| debug!("dispatch error, {:?}", e); |
| self.output.failure(raw_message.id.unwrap_or(Id::Null), e); |
| return ServerStateChange::Break; |
| } |
| |
| ServerStateChange::Continue |
| } |
| } |
| |
| #[derive(Debug)] |
| struct RawMessage { |
| method: String, |
| id: Option<Id>, |
| params: serde_json::Value, |
| } |
| |
| impl RawMessage { |
| fn parse_as_request<T: Action>(&self) -> Result<Request<T>, jsonrpc::Error> { |
| // FIXME: We only support numeric responses, ideally we should switch from using parsed usize |
| // to using jsonrpc_core::Id |
| let parsed_numeric_id = match &self.id { |
| &Some(Id::Num(n)) => Some(n as usize), |
| &Some(Id::Str(ref s)) => usize::from_str_radix(s, 10).ok(), |
| _ => None, |
| }; |
| |
| let params = T::Params::deserialize(&self.params).map_err(|e| { |
| debug!("error when parsing as request: {}", e); |
| jsonrpc::Error::invalid_request() |
| })?; |
| |
| match parsed_numeric_id { |
| Some(id) => Ok(Request { |
| id, |
| params, |
| received: Instant::now(), |
| _action: PhantomData, |
| }), |
| None => return Err(jsonrpc::Error::invalid_request()), |
| } |
| } |
| |
| fn parse_as_notification<T: Action>(&self) -> Result<Notification<T>, jsonrpc::Error> { |
| use serde::Deserialize; |
| |
| let params = T::Params::deserialize(&self.params).map_err(|e| { |
| debug!("error when parsing as notification: {}", e); |
| jsonrpc::Error::invalid_request() |
| })?; |
| |
| Ok(Notification { |
| params, |
| _action: PhantomData, |
| }) |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use url::Url; |
| |
| fn get_default_params() -> InitializeParams { |
| InitializeParams { |
| process_id: None, |
| root_path: None, |
| root_uri: None, |
| initialization_options: None, |
| capabilities: ClientCapabilities { |
| workspace: None, |
| text_document: None, |
| experimental: None, |
| }, |
| trace: TraceOption::Off, |
| } |
| } |
| |
| fn make_platform_path(path: &'static str) -> PathBuf { |
| if cfg!(windows) { |
| PathBuf::from(format!("C:/{}", path)) |
| } else { |
| PathBuf::from(format!("/{}", path)) |
| } |
| } |
| |
| #[test] |
| fn test_use_root_uri() { |
| let mut params = get_default_params(); |
| |
| let root_path = make_platform_path("path/a"); |
| let root_uri = make_platform_path("path/b"); |
| params.root_path = Some(root_path.to_str().unwrap().to_owned()); |
| params.root_uri = Some(Url::from_directory_path(&root_uri).unwrap()); |
| |
| assert_eq!(get_root_path(¶ms), root_uri); |
| } |
| |
| #[test] |
| fn test_use_root_path() { |
| let mut params = get_default_params(); |
| |
| let root_path = make_platform_path("path/a"); |
| params.root_path = Some(root_path.to_str().unwrap().to_owned()); |
| params.root_uri = None; |
| |
| assert_eq!(get_root_path(¶ms), root_path); |
| } |
| |
| #[test] |
| fn test_parse_as_notification() { |
| let raw = RawMessage { |
| method: "initialize".to_owned(), |
| id: None, |
| params: serde_json::Value::Null, |
| }; |
| let notification = raw.parse_as_notification::<notifications::Initialized>(); |
| |
| assert_eq!( |
| notification, |
| Ok(Notification::<notifications::Initialized> { |
| params: NoParams {}, |
| _action: PhantomData, |
| }) |
| ); |
| } |
| } |