diff --git a/README.md b/README.md index 34257b08..9dc9ae61 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,9 @@ impl LanguageServer for Backend { } async fn initialized(&self, _: InitializedParams) { - self.client.log_message(MessageType::Info, "server initialized!"); + self.client + .log_message(MessageType::Info, "server initialized!") + .await; } async fn shutdown(&self) -> Result<()> { diff --git a/examples/custom_notification.rs b/examples/custom_notification.rs index d4d28cd9..8f43ec0b 100644 --- a/examples/custom_notification.rs +++ b/examples/custom_notification.rs @@ -20,11 +20,11 @@ impl CustomNotificationParams { } } -#[derive(Debug)] enum CustomNotification {} impl Notification for CustomNotification { type Params = CustomNotificationParams; + const METHOD: &'static str = "custom/notification"; } @@ -54,13 +54,17 @@ impl LanguageServer for Backend { async fn execute_command(&self, params: ExecuteCommandParams) -> Result> { if params.command == "custom.notification" { - self.client.send_custom_notification::( - CustomNotificationParams::new("Hello", "Message"), - ); - self.client.log_message( - MessageType::Info, - format!("Command executed with params: {:?}", params), - ); + self.client + .send_custom_notification::(CustomNotificationParams::new( + "Hello", "Message", + )) + .await; + self.client + .log_message( + MessageType::Info, + format!("Command executed with params: {:?}", params), + ) + .await; Ok(None) } else { Err(Error::invalid_request()) diff --git a/examples/stdio.rs b/examples/stdio.rs index 5794bf87..ff49bf83 100644 --- a/examples/stdio.rs +++ b/examples/stdio.rs @@ -40,7 +40,9 @@ impl LanguageServer for Backend { } async fn initialized(&self, _: InitializedParams) { - self.client.log_message(MessageType::Info, "initialized!"); + self.client + .log_message(MessageType::Info, "initialized!") + .await; } async fn shutdown(&self) -> Result<()> { @@ -49,46 +51,58 @@ impl LanguageServer for Backend { async fn did_change_workspace_folders(&self, _: DidChangeWorkspaceFoldersParams) { self.client - .log_message(MessageType::Info, "workspace folders changed!"); + .log_message(MessageType::Info, "workspace folders changed!") + .await; } async fn did_change_configuration(&self, _: DidChangeConfigurationParams) { self.client - .log_message(MessageType::Info, "configuration changed!"); + .log_message(MessageType::Info, "configuration changed!") + .await; } async fn did_change_watched_files(&self, _: DidChangeWatchedFilesParams) { self.client - .log_message(MessageType::Info, "watched files have changed!"); + .log_message(MessageType::Info, "watched files have changed!") + .await; } async fn execute_command(&self, _: ExecuteCommandParams) -> Result> { self.client - .log_message(MessageType::Info, "command executed!"); + .log_message(MessageType::Info, "command executed!") + .await; match self.client.apply_edit(WorkspaceEdit::default()).await { - Ok(res) if res.applied => self.client.log_message(MessageType::Info, "edit applied"), - Ok(_) => self.client.log_message(MessageType::Info, "edit rejected"), - Err(err) => self.client.log_message(MessageType::Error, err), + Ok(res) if res.applied => self.client.log_message(MessageType::Info, "applied").await, + Ok(_) => self.client.log_message(MessageType::Info, "rejected").await, + Err(err) => self.client.log_message(MessageType::Error, err).await, } Ok(None) } async fn did_open(&self, _: DidOpenTextDocumentParams) { - self.client.log_message(MessageType::Info, "file opened!"); + self.client + .log_message(MessageType::Info, "file opened!") + .await; } async fn did_change(&self, _: DidChangeTextDocumentParams) { - self.client.log_message(MessageType::Info, "file changed!"); + self.client + .log_message(MessageType::Info, "file changed!") + .await; } async fn did_save(&self, _: DidSaveTextDocumentParams) { - self.client.log_message(MessageType::Info, "file saved!"); + self.client + .log_message(MessageType::Info, "file saved!") + .await; } async fn did_close(&self, _: DidCloseTextDocumentParams) { - self.client.log_message(MessageType::Info, "file closed!"); + self.client + .log_message(MessageType::Info, "file closed!") + .await; } async fn completion(&self, _: CompletionParams) -> Result> { diff --git a/examples/tcp.rs b/examples/tcp.rs index 89e79b24..0b6bf98f 100644 --- a/examples/tcp.rs +++ b/examples/tcp.rs @@ -41,7 +41,9 @@ impl LanguageServer for Backend { } async fn initialized(&self, _: InitializedParams) { - self.client.log_message(MessageType::Info, "initialized!"); + self.client + .log_message(MessageType::Info, "initialized!") + .await; } async fn shutdown(&self) -> Result<()> { @@ -50,46 +52,58 @@ impl LanguageServer for Backend { async fn did_change_workspace_folders(&self, _: DidChangeWorkspaceFoldersParams) { self.client - .log_message(MessageType::Info, "workspace folders changed!"); + .log_message(MessageType::Info, "workspace folders changed!") + .await; } async fn did_change_configuration(&self, _: DidChangeConfigurationParams) { self.client - .log_message(MessageType::Info, "configuration changed!"); + .log_message(MessageType::Info, "configuration changed!") + .await; } async fn did_change_watched_files(&self, _: DidChangeWatchedFilesParams) { self.client - .log_message(MessageType::Info, "watched files have changed!"); + .log_message(MessageType::Info, "watched files have changed!") + .await; } async fn execute_command(&self, _: ExecuteCommandParams) -> Result> { self.client - .log_message(MessageType::Info, "command executed!"); + .log_message(MessageType::Info, "command executed!") + .await; match self.client.apply_edit(WorkspaceEdit::default()).await { - Ok(res) if res.applied => self.client.log_message(MessageType::Info, "edit applied"), - Ok(_) => self.client.log_message(MessageType::Info, "edit rejected"), - Err(err) => self.client.log_message(MessageType::Error, err), + Ok(res) if res.applied => self.client.log_message(MessageType::Info, "applied").await, + Ok(_) => self.client.log_message(MessageType::Info, "rejected").await, + Err(err) => self.client.log_message(MessageType::Error, err).await, } Ok(None) } async fn did_open(&self, _: DidOpenTextDocumentParams) { - self.client.log_message(MessageType::Info, "file opened!"); + self.client + .log_message(MessageType::Info, "file opened!") + .await; } async fn did_change(&self, _: DidChangeTextDocumentParams) { - self.client.log_message(MessageType::Info, "file changed!"); + self.client + .log_message(MessageType::Info, "file changed!") + .await; } async fn did_save(&self, _: DidSaveTextDocumentParams) { - self.client.log_message(MessageType::Info, "file saved!"); + self.client + .log_message(MessageType::Info, "file saved!") + .await; } async fn did_close(&self, _: DidCloseTextDocumentParams) { - self.client.log_message(MessageType::Info, "file closed!"); + self.client + .log_message(MessageType::Info, "file closed!") + .await; } async fn completion(&self, _: CompletionParams) -> Result> { diff --git a/src/client.rs b/src/client.rs index 9a75260c..ef1b2697 100644 --- a/src/client.rs +++ b/src/client.rs @@ -51,11 +51,12 @@ impl Client { /// This corresponds to the [`window/logMessage`] notification. /// /// [`window/logMessage`]: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_logMessage - pub fn log_message(&self, typ: MessageType, message: M) { + pub async fn log_message(&self, typ: MessageType, message: M) { self.send_notification::(LogMessageParams { typ, message: message.to_string(), - }); + }) + .await; } /// Notifies the client to display a particular message in the user interface. @@ -63,11 +64,12 @@ impl Client { /// This corresponds to the [`window/showMessage`] notification. /// /// [`window/showMessage`]: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessage - pub fn show_message(&self, typ: MessageType, message: M) { + pub async fn show_message(&self, typ: MessageType, message: M) { self.send_notification::(ShowMessageParams { typ, message: message.to_string(), - }); + }) + .await; } /// Asks the client to display a particular message in the user interface. @@ -104,14 +106,14 @@ impl Client { /// This corresponds to the [`telemetry/event`] notification. /// /// [`telemetry/event`]: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#telemetry_event - pub fn telemetry_event(&self, data: S) { + pub async fn telemetry_event(&self, data: S) { match serde_json::to_value(data) { Err(e) => error!("invalid JSON in `telemetry/event` notification: {}", e), Ok(mut value) => { if !value.is_null() && !value.is_array() && !value.is_object() { value = Value::Array(vec![value]); } - self.send_notification::(value); + self.send_notification::(value).await; } } } @@ -231,10 +233,16 @@ impl Client { /// # Initialization /// /// This notification will only be sent if the server is initialized. - pub fn publish_diagnostics(&self, uri: Url, diags: Vec, version: Option) { + pub async fn publish_diagnostics( + &self, + uri: Url, + diags: Vec, + version: Option, + ) { self.send_notification_initialized::(PublishDiagnosticsParams::new( uri, diags, version, - )); + )) + .await; } /// Sends a custom notification to the client. @@ -242,11 +250,34 @@ impl Client { /// # Initialization /// /// This notification will only be sent if the server is initialized. - pub fn send_custom_notification(&self, params: N::Params) + pub async fn send_custom_notification(&self, params: N::Params) + where + N: Notification, + { + self.send_notification_initialized::(params).await; + } + + async fn send_notification(&self, params: N::Params) where N: Notification, { - self.send_notification_initialized::(params); + let mut sender = self.inner.sender.clone(); + let message = Outgoing::Request(ClientRequest::notification::(params)); + if sender.send(message).await.is_err() { + error!("failed to send notification") + } + } + + async fn send_notification_initialized(&self, params: N::Params) + where + N: Notification, + { + if let State::Initialized | State::ShutDown = self.inner.state.get() { + self.send_notification::(params).await; + } else { + let msg = ClientRequest::notification::(params); + trace!("server not initialized, supressing message: {}", msg); + } } async fn send_request(&self, params: R::Params) -> Result @@ -272,19 +303,6 @@ impl Client { }) } - fn send_notification(&self, params: N::Params) - where - N: Notification, - { - let mut sender = self.inner.sender.clone(); - let message = Outgoing::Request(ClientRequest::notification::(params)); - tokio::spawn(async move { - if sender.send(message).await.is_err() { - error!("failed to send notification") - } - }); - } - async fn send_request_initialized(&self, params: R::Params) -> Result where R: Request, @@ -298,36 +316,30 @@ impl Client { Err(jsonrpc::not_initialized_error()) } } - - fn send_notification_initialized(&self, params: N::Params) - where - N: Notification, - { - if let State::Initialized | State::ShutDown = self.inner.state.get() { - self.send_notification::(params); - } else { - let msg = ClientRequest::notification::(params); - trace!("server not initialized, supressing message: {}", msg); - } - } } #[cfg(test)] mod tests { + use std::future::Future; + use futures::channel::mpsc; use futures::StreamExt; use serde_json::json; use super::*; - async fn assert_client_messages(f: F, expected: ClientRequest) { + async fn assert_client_messages(f: F, expected: ClientRequest) + where + F: FnOnce(Client) -> Fut, + Fut: Future, + { let (request_tx, request_rx) = mpsc::channel(1); let pending = Arc::new(ClientRequests::new()); let state = Arc::new(ServerState::new()); state.set(State::Initialized); let client = Client::new(request_tx, pending, state); - f(client); + f(client).await; let messages: Vec<_> = request_rx.collect().await; assert_eq!(messages, vec![Outgoing::Request(expected)]); @@ -335,44 +347,44 @@ mod tests { #[tokio::test] async fn log_message() { - let (typ, message) = (MessageType::Log, "foo bar".to_owned()); + let (typ, msg) = (MessageType::Log, "foo bar".to_owned()); let expected = ClientRequest::notification::(LogMessageParams { typ, - message: message.clone(), + message: msg.clone(), }); - assert_client_messages(|p| p.log_message(typ, message), expected).await; + assert_client_messages(|p| async move { p.log_message(typ, msg).await }, expected).await; } #[tokio::test] async fn show_message() { - let (typ, message) = (MessageType::Log, "foo bar".to_owned()); + let (typ, msg) = (MessageType::Log, "foo bar".to_owned()); let expected = ClientRequest::notification::(ShowMessageParams { typ, - message: message.clone(), + message: msg.clone(), }); - assert_client_messages(|p| p.show_message(typ, message), expected).await; + assert_client_messages(|p| async move { p.show_message(typ, msg).await }, expected).await; } #[tokio::test] async fn telemetry_event() { let null = json!(null); let expected = ClientRequest::notification::(null.clone()); - assert_client_messages(|p| p.telemetry_event(null), expected).await; + assert_client_messages(|p| async move { p.telemetry_event(null).await }, expected).await; let array = json!([1, 2, 3]); let expected = ClientRequest::notification::(array.clone()); - assert_client_messages(|p| p.telemetry_event(array), expected).await; + assert_client_messages(|p| async move { p.telemetry_event(array).await }, expected).await; let object = json!({}); let expected = ClientRequest::notification::(object.clone()); - assert_client_messages(|p| p.telemetry_event(object), expected).await; + assert_client_messages(|p| async move { p.telemetry_event(object).await }, expected).await; - let anything_else = json!("hello"); - let wrapped = Value::Array(vec![anything_else.clone()]); + let other = json!("hello"); + let wrapped = Value::Array(vec![other.clone()]); let expected = ClientRequest::notification::(wrapped); - assert_client_messages(|p| p.telemetry_event(anything_else), expected).await; + assert_client_messages(|p| async move { p.telemetry_event(other).await }, expected).await; } #[tokio::test] @@ -383,6 +395,10 @@ mod tests { let params = PublishDiagnosticsParams::new(uri.clone(), diagnostics.clone(), None); let expected = ClientRequest::notification::(params); - assert_client_messages(|p| p.publish_diagnostics(uri, diagnostics, None), expected).await; + assert_client_messages( + |p| async move { p.publish_diagnostics(uri, diagnostics, None).await }, + expected, + ) + .await; } } diff --git a/src/lib.rs b/src/lib.rs index 615f1a8b..2b47da00 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,9 @@ //! } //! //! async fn initialized(&self, _: InitializedParams) { -//! self.client.log_message(MessageType::Info, "server initialized!"); +//! self.client +//! .log_message(MessageType::Info, "server initialized!") +//! .await; //! } //! //! async fn shutdown(&self) -> Result<()> {