From 72a3722470e5297c72dcaccaf2f113e7b758606d Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 30 Aug 2019 17:24:11 +0300 Subject: move lsp-server to a separate repository --- Cargo.lock | 46 ++--- crates/gen_lsp_server/Cargo.toml | 18 -- .../gen_lsp_server/examples/01_gen_lsp_server.rs | 47 ----- .../examples/02_gen_lsp_server_with_logging.rs | 120 ------------ crates/gen_lsp_server/src/lib.rs | 136 -------------- crates/gen_lsp_server/src/msg.rs | 205 --------------------- crates/gen_lsp_server/src/stdio.rs | 57 ------ crates/ra_lsp_server/Cargo.toml | 2 +- crates/ra_lsp_server/src/main.rs | 14 +- crates/ra_lsp_server/src/main_loop.rs | 141 +++++++------- crates/ra_lsp_server/src/main_loop/handlers.rs | 2 +- .../src/main_loop/pending_requests.rs | 19 +- crates/ra_lsp_server/src/world.rs | 2 +- crates/ra_lsp_server/tests/heavy_tests/support.rs | 57 +++--- 14 files changed, 141 insertions(+), 725 deletions(-) delete mode 100644 crates/gen_lsp_server/Cargo.toml delete mode 100644 crates/gen_lsp_server/examples/01_gen_lsp_server.rs delete mode 100644 crates/gen_lsp_server/examples/02_gen_lsp_server_with_logging.rs delete mode 100644 crates/gen_lsp_server/src/lib.rs delete mode 100644 crates/gen_lsp_server/src/msg.rs delete mode 100644 crates/gen_lsp_server/src/stdio.rs diff --git a/Cargo.lock b/Cargo.lock index 93814bedc..17e60aa82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -214,23 +214,6 @@ dependencies = [ "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "console" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", - "clicolors-control 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "encode_unicode 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "termios 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "console" version = "0.8.0" @@ -472,18 +455,6 @@ name = "fuchsia-zircon-sys" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "gen_lsp_server" -version = "0.2.0" -dependencies = [ - "crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "flexi_logger 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "lsp-types 0.60.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "getrandom" version = "0.1.10" @@ -539,7 +510,7 @@ name = "indicatif" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "console 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)", + "console 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -689,6 +660,17 @@ dependencies = [ "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "lsp-server" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "lsp-types" version = "0.60.0" @@ -1092,8 +1074,8 @@ version = "0.1.0" dependencies = [ "crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "flexi_logger 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gen_lsp_server 0.2.0", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "lsp-server 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "lsp-types 0.60.0 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "ra_ide_api 0.1.0", @@ -1899,7 +1881,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum clicolors-control 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "73abfd4c73d003a674ce5d2933fca6ce6c42480ea84a5ffe0a2dc39ed56300f9" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum console 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8ca57c2c14b8a2bf3105bc9d15574aad80babf6a9c44b1058034cdf8bd169628" "checksum console 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b147390a412132d75d10dd3b7b175a69cf5fd95032f7503c7091b8831ba10242" "checksum crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2d818a4990769aac0c7ff1360e233ef3a41adcb009ebb2036bf6915eb0f6b23c" "checksum crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa" @@ -1956,6 +1937,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" "checksum lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +"checksum lsp-server 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "47632ec528046c1a39f14448f1ee7d66d4b7b83e1771590b62e6c08665dea053" "checksum lsp-types 0.60.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fe3edefcd66dde1f7f1df706f46520a3c93adc5ca4bc5747da6621195e894efd" "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" diff --git a/crates/gen_lsp_server/Cargo.toml b/crates/gen_lsp_server/Cargo.toml deleted file mode 100644 index 7011aa1bf..000000000 --- a/crates/gen_lsp_server/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -edition = "2018" -name = "gen_lsp_server" -version = "0.2.0" -authors = ["rust-analyzer developers"] -repository = "https://github.com/rust-analyzer/rust-analyzer" -license = "MIT OR Apache-2.0" -description = "Generic LSP server scaffold." - -[dependencies] -lsp-types = "0.60.0" -log = "0.4.3" -serde_json = "1.0.34" -serde = { version = "1.0.83", features = ["derive"] } -crossbeam-channel = "0.3.5" - -[dev-dependencies] -flexi_logger = "0.14.0" diff --git a/crates/gen_lsp_server/examples/01_gen_lsp_server.rs b/crates/gen_lsp_server/examples/01_gen_lsp_server.rs deleted file mode 100644 index f49965064..000000000 --- a/crates/gen_lsp_server/examples/01_gen_lsp_server.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::error::Error; - -use crossbeam_channel::{Receiver, Sender}; -use gen_lsp_server::{handle_shutdown, run_server, stdio_transport, RawMessage, RawResponse}; -use lsp_types::{ - request::{GotoDefinition, GotoDefinitionResponse}, - InitializeParams, ServerCapabilities, -}; - -fn main() -> Result<(), Box> { - let (receiver, sender, io_threads) = stdio_transport(); - run_server(ServerCapabilities::default(), receiver, sender, main_loop)?; - io_threads.join()?; - Ok(()) -} - -fn main_loop( - _params: InitializeParams, - receiver: &Receiver, - sender: &Sender, -) -> Result<(), Box> { - for msg in receiver { - match msg { - RawMessage::Request(req) => { - let req = match handle_shutdown(req, sender) { - None => return Ok(()), - Some(req) => req, - }; - match req.cast::() { - Ok((id, _params)) => { - let resp = RawResponse::ok::( - id, - &Some(GotoDefinitionResponse::Array(Vec::new())), - ); - sender.send(RawMessage::Response(resp))?; - continue; - } - Err(req) => req, - }; - // ... - } - RawMessage::Response(_resp) => (), - RawMessage::Notification(_not) => (), - } - } - Ok(()) -} diff --git a/crates/gen_lsp_server/examples/02_gen_lsp_server_with_logging.rs b/crates/gen_lsp_server/examples/02_gen_lsp_server_with_logging.rs deleted file mode 100644 index 3c48106c5..000000000 --- a/crates/gen_lsp_server/examples/02_gen_lsp_server_with_logging.rs +++ /dev/null @@ -1,120 +0,0 @@ -//! A minimal example LSP server that can only respond to the `gotoDefinition` request. To use -//! this example, execute it and then send an `initialize` request. -//! -//! ```no_run -//! Content-Length: 85 -//! -//! {"jsonrpc": "2.0", "method": "initialize", "id": 1, "params": {"capabilities": {}}} -//! ``` -//! -//! This will respond with a server respose. Then send it a `initialized` notification which will -//! have no response. -//! -//! ```no_run -//! Content-Length: 59 -//! -//! {"jsonrpc": "2.0", "method": "initialized", "params": {}} -//! ``` -//! -//! Once these two are sent, then we enter the main loop of the server. The only request this -//! example can handle is `gotoDefinition`: -//! -//! ```no_run -//! Content-Length: 159 -//! -//! {"jsonrpc": "2.0", "method": "textDocument/definition", "id": 2, "params": {"textDocument": {"uri": "file://temp"}, "position": {"line": 1, "character": 1}}} -//! ``` -//! -//! To finish up without errors, send a shutdown request: -//! -//! ```no_run -//! Content-Length: 67 -//! -//! {"jsonrpc": "2.0", "method": "shutdown", "id": 3, "params": null} -//! ``` -//! -//! The server will exit the main loop and finally we send a `shutdown` notification to stop -//! the server. -//! -//! ``` -//! Content-Length: 54 -//! -//! {"jsonrpc": "2.0", "method": "exit", "params": null} -//! ``` - -use std::error::Error; - -use crossbeam_channel::{Receiver, Sender}; -use gen_lsp_server::{ - handle_shutdown, run_server, stdio_transport, RawMessage, RawRequest, RawResponse, -}; -use log::info; -use lsp_types::{ - request::{GotoDefinition, GotoDefinitionResponse}, - InitializeParams, ServerCapabilities, -}; - -fn main() -> Result<(), Box> { - // Set up logging. Because `stdio_transport` gets a lock on stdout and stdin, we must have - // our logging only write out to stderr. - flexi_logger::Logger::with_str("info").start().unwrap(); - info!("starting generic LSP server"); - - // Create the transport. Includes the stdio (stdin and stdout) versions but this could - // also be implemented to use sockets or HTTP. - let (receiver, sender, io_threads) = stdio_transport(); - - // Run the server and wait for the two threads to end (typically by trigger LSP Exit event). - run_server(ServerCapabilities::default(), receiver, sender, main_loop)?; - io_threads.join()?; - - // Shut down gracefully. - info!("shutting down server"); - Ok(()) -} - -fn main_loop( - _params: InitializeParams, - receiver: &Receiver, - sender: &Sender, -) -> Result<(), Box> { - info!("starting example main loop"); - for msg in receiver { - info!("got msg: {:?}", msg); - match msg { - RawMessage::Request(req) => { - let req = match log_handle_shutdown(req, sender) { - None => return Ok(()), - Some(req) => req, - }; - info!("got request: {:?}", req); - match req.cast::() { - Ok((id, params)) => { - info!("got gotoDefinition request #{}: {:?}", id, params); - let resp = RawResponse::ok::( - id, - &Some(GotoDefinitionResponse::Array(Vec::new())), - ); - info!("sending gotoDefinition response: {:?}", resp); - sender.send(RawMessage::Response(resp))?; - continue; - } - Err(req) => req, - }; - // ... - } - RawMessage::Response(resp) => { - info!("got response: {:?}", resp); - } - RawMessage::Notification(not) => { - info!("got notification: {:?}", not); - } - } - } - Ok(()) -} - -pub fn log_handle_shutdown(req: RawRequest, sender: &Sender) -> Option { - info!("handle_shutdown: {:?}", req); - handle_shutdown(req, sender) -} diff --git a/crates/gen_lsp_server/src/lib.rs b/crates/gen_lsp_server/src/lib.rs deleted file mode 100644 index 0984e3e25..000000000 --- a/crates/gen_lsp_server/src/lib.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! A language server scaffold, exposing a synchronous crossbeam-channel based API. -//! This crate handles protocol handshaking and parsing messages, while you -//! control the message dispatch loop yourself. -//! -//! Run with `RUST_LOG=gen_lsp_server=debug` to see all the messages. -//! -//! ```no_run -//! use std::error::Error; -//! use crossbeam_channel::{Sender, Receiver}; -//! use lsp_types::{ServerCapabilities, InitializeParams, request::{GotoDefinition, GotoDefinitionResponse}}; -//! use gen_lsp_server::{run_server, stdio_transport, handle_shutdown, RawMessage, RawResponse}; -//! -//! fn main() -> Result<(), Box> { -//! let (receiver, sender, io_threads) = stdio_transport(); -//! run_server( -//! ServerCapabilities::default(), -//! receiver, -//! sender, -//! main_loop, -//! )?; -//! io_threads.join()?; -//! Ok(()) -//! } -//! -//! fn main_loop( -//! _params: InitializeParams, -//! receiver: &Receiver, -//! sender: &Sender, -//! ) -> Result<(), Box> { -//! for msg in receiver { -//! match msg { -//! RawMessage::Request(req) => { -//! let req = match handle_shutdown(req, sender) { -//! None => return Ok(()), -//! Some(req) => req, -//! }; -//! match req.cast::() { -//! Ok((id, _params)) => { -//! let resp = RawResponse::ok::( -//! id, -//! &Some(GotoDefinitionResponse::Array(Vec::new())), -//! ); -//! sender.send(RawMessage::Response(resp))?; -//! continue; -//! }, -//! Err(req) => req, -//! }; -//! // ... -//! } -//! RawMessage::Response(_resp) => (), -//! RawMessage::Notification(_not) => (), -//! } -//! } -//! Ok(()) -//! } -//! ``` - -use std::error::Error; - -mod msg; -mod stdio; - -use crossbeam_channel::{Receiver, Sender}; -use lsp_types::{ - notification::{Exit, Initialized}, - request::{Initialize, Shutdown}, - InitializeParams, InitializeResult, ServerCapabilities, -}; - -pub type Result = std::result::Result>; -pub use crate::{ - msg::{ErrorCode, RawMessage, RawNotification, RawRequest, RawResponse, RawResponseError}, - stdio::{stdio_transport, Threads}, -}; - -/// Main entry point: runs the server from initialization to shutdown. -/// To attach server to standard input/output streams, use the `stdio_transport` -/// function to create corresponding `sender` and `receiver` pair. -/// -/// `server` should use the `handle_shutdown` function to handle the `Shutdown` -/// request. -pub fn run_server( - caps: ServerCapabilities, - receiver: Receiver, - sender: Sender, - server: impl FnOnce(InitializeParams, &Receiver, &Sender) -> Result<()>, -) -> Result<()> { - log::info!("lsp server initializes"); - let params = initialize(&receiver, &sender, caps)?; - log::info!("lsp server initialized, serving requests"); - server(params, &receiver, &sender)?; - log::info!("lsp server waiting for exit notification"); - match receiver.recv() { - Ok(RawMessage::Notification(n)) => n - .cast::() - .map_err(|n| format!("unexpected notification during shutdown: {:?}", n))?, - m => Err(format!("unexpected message during shutdown: {:?}", m))?, - } - log::info!("lsp server shutdown complete"); - Ok(()) -} - -/// If `req` is `Shutdown`, respond to it and return `None`, otherwise return `Some(req)` -pub fn handle_shutdown(req: RawRequest, sender: &Sender) -> Option { - match req.cast::() { - Ok((id, ())) => { - let resp = RawResponse::ok::(id, &()); - let _ = sender.send(RawMessage::Response(resp)); - None - } - Err(req) => Some(req), - } -} - -fn initialize( - receiver: &Receiver, - sender: &Sender, - caps: ServerCapabilities, -) -> Result { - let (id, params) = match receiver.recv() { - Ok(RawMessage::Request(req)) => match req.cast::() { - Err(req) => Err(format!("expected initialize request, got {:?}", req))?, - Ok(req) => req, - }, - msg => Err(format!("expected initialize request, got {:?}", msg))?, - }; - let resp = RawResponse::ok::(id, &InitializeResult { capabilities: caps }); - sender.send(RawMessage::Response(resp)).unwrap(); - match receiver.recv() { - Ok(RawMessage::Notification(n)) => { - n.cast::().map_err(|_| "expected initialized notification")?; - } - _ => Err("expected initialized notification".to_string())?, - } - Ok(params) -} diff --git a/crates/gen_lsp_server/src/msg.rs b/crates/gen_lsp_server/src/msg.rs deleted file mode 100644 index 2928e4f8b..000000000 --- a/crates/gen_lsp_server/src/msg.rs +++ /dev/null @@ -1,205 +0,0 @@ -use std::io::{BufRead, Write}; - -use lsp_types::{notification::Notification, request::Request}; -use serde::{Deserialize, Serialize}; -use serde_json::{from_str, from_value, to_string, to_value, Value}; - -use crate::Result; - -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(untagged)] -pub enum RawMessage { - Request(RawRequest), - Notification(RawNotification), - Response(RawResponse), -} - -impl From for RawMessage { - fn from(raw: RawRequest) -> RawMessage { - RawMessage::Request(raw) - } -} - -impl From for RawMessage { - fn from(raw: RawNotification) -> RawMessage { - RawMessage::Notification(raw) - } -} - -impl From for RawMessage { - fn from(raw: RawResponse) -> RawMessage { - RawMessage::Response(raw) - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct RawRequest { - pub id: u64, - pub method: String, - pub params: Value, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct RawResponse { - // JSON RPC allows this to be null if it was impossible - // to decode the request's id. Ignore this special case - // and just die horribly. - pub id: u64, - #[serde(skip_serializing_if = "Option::is_none")] - pub result: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct RawResponseError { - pub code: i32, - pub message: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub data: Option, -} - -#[derive(Clone, Copy, Debug)] -#[allow(unused)] -pub enum ErrorCode { - ParseError = -32700, - InvalidRequest = -32600, - MethodNotFound = -32601, - InvalidParams = -32602, - InternalError = -32603, - ServerErrorStart = -32099, - ServerErrorEnd = -32000, - ServerNotInitialized = -32002, - UnknownErrorCode = -32001, - RequestCanceled = -32800, - ContentModified = -32801, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct RawNotification { - pub method: String, - pub params: Value, -} - -impl RawMessage { - pub fn read(r: &mut impl BufRead) -> Result> { - let text = match read_msg_text(r)? { - None => return Ok(None), - Some(text) => text, - }; - let msg = from_str(&text)?; - Ok(Some(msg)) - } - pub fn write(self, w: &mut impl Write) -> Result<()> { - #[derive(Serialize)] - struct JsonRpc { - jsonrpc: &'static str, - #[serde(flatten)] - msg: RawMessage, - } - let text = to_string(&JsonRpc { jsonrpc: "2.0", msg: self })?; - write_msg_text(w, &text)?; - Ok(()) - } -} - -impl RawRequest { - pub fn new(id: u64, params: &R::Params) -> RawRequest - where - R: Request, - R::Params: serde::Serialize, - { - RawRequest { id, method: R::METHOD.to_string(), params: to_value(params).unwrap() } - } - pub fn cast(self) -> ::std::result::Result<(u64, R::Params), RawRequest> - where - R: Request, - R::Params: serde::de::DeserializeOwned, - { - if self.method != R::METHOD { - return Err(self); - } - let id = self.id; - let params: R::Params = from_value(self.params).unwrap(); - Ok((id, params)) - } -} - -impl RawResponse { - pub fn ok(id: u64, result: &R::Result) -> RawResponse - where - R: Request, - R::Result: serde::Serialize, - { - RawResponse { id, result: Some(to_value(&result).unwrap()), error: None } - } - pub fn err(id: u64, code: i32, message: String) -> RawResponse { - let error = RawResponseError { code, message, data: None }; - RawResponse { id, result: None, error: Some(error) } - } -} - -impl RawNotification { - pub fn new(params: &N::Params) -> RawNotification - where - N: Notification, - N::Params: serde::Serialize, - { - RawNotification { method: N::METHOD.to_string(), params: to_value(params).unwrap() } - } - pub fn is(&self) -> bool - where - N: Notification, - { - self.method == N::METHOD - } - pub fn cast(self) -> ::std::result::Result - where - N: Notification, - N::Params: serde::de::DeserializeOwned, - { - if !self.is::() { - return Err(self); - } - Ok(from_value(self.params).unwrap()) - } -} - -fn read_msg_text(inp: &mut impl BufRead) -> Result> { - let mut size = None; - let mut buf = String::new(); - loop { - buf.clear(); - if inp.read_line(&mut buf)? == 0 { - return Ok(None); - } - if !buf.ends_with("\r\n") { - Err(format!("malformed header: {:?}", buf))?; - } - let buf = &buf[..buf.len() - 2]; - if buf.is_empty() { - break; - } - let mut parts = buf.splitn(2, ": "); - let header_name = parts.next().unwrap(); - let header_value = parts.next().ok_or_else(|| format!("malformed header: {:?}", buf))?; - if header_name == "Content-Length" { - size = Some(header_value.parse::()?); - } - } - let size = size.ok_or("no Content-Length")?; - let mut buf = buf.into_bytes(); - buf.resize(size, 0); - inp.read_exact(&mut buf)?; - let buf = String::from_utf8(buf)?; - log::debug!("< {}", buf); - Ok(Some(buf)) -} - -fn write_msg_text(out: &mut impl Write, msg: &str) -> Result<()> { - log::debug!("> {}", msg); - write!(out, "Content-Length: {}\r\n\r\n", msg.len())?; - out.write_all(msg.as_bytes())?; - out.flush()?; - Ok(()) -} diff --git a/crates/gen_lsp_server/src/stdio.rs b/crates/gen_lsp_server/src/stdio.rs deleted file mode 100644 index f8931f2dc..000000000 --- a/crates/gen_lsp_server/src/stdio.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::{ - io::{stdin, stdout}, - thread, -}; - -use crossbeam_channel::{bounded, Receiver, Sender}; -use lsp_types::notification::Exit; - -use crate::{RawMessage, Result}; - -pub fn stdio_transport() -> (Receiver, Sender, Threads) { - let (writer_sender, writer_receiver) = bounded::(16); - let writer = thread::spawn(move || { - let stdout = stdout(); - let mut stdout = stdout.lock(); - writer_receiver.into_iter().try_for_each(|it| it.write(&mut stdout))?; - Ok(()) - }); - let (reader_sender, reader_receiver) = bounded::(16); - let reader = thread::spawn(move || { - let stdin = stdin(); - let mut stdin = stdin.lock(); - while let Some(msg) = RawMessage::read(&mut stdin)? { - let is_exit = match &msg { - RawMessage::Notification(n) => n.is::(), - _ => false, - }; - - reader_sender.send(msg).unwrap(); - - if is_exit { - break; - } - } - Ok(()) - }); - let threads = Threads { reader, writer }; - (reader_receiver, writer_sender, threads) -} - -pub struct Threads { - reader: thread::JoinHandle>, - writer: thread::JoinHandle>, -} - -impl Threads { - pub fn join(self) -> Result<()> { - match self.reader.join() { - Ok(r) => r?, - Err(_) => Err("reader panicked")?, - } - match self.writer.join() { - Ok(r) => r, - Err(_) => Err("writer panicked")?, - } - } -} diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml index e271c257b..084a20cd9 100644 --- a/crates/ra_lsp_server/Cargo.toml +++ b/crates/ra_lsp_server/Cargo.toml @@ -21,7 +21,7 @@ thread_worker = { path = "../thread_worker" } ra_syntax = { path = "../ra_syntax" } ra_text_edit = { path = "../ra_text_edit" } ra_ide_api = { path = "../ra_ide_api" } -gen_lsp_server = { path = "../gen_lsp_server" } +lsp-server = "0.1.0" ra_project_model = { path = "../ra_project_model" } ra_prof = { path = "../ra_prof" } ra_vfs_glob = { path = "../ra_vfs_glob" } diff --git a/crates/ra_lsp_server/src/main.rs b/crates/ra_lsp_server/src/main.rs index ae1392cb5..1debe7660 100644 --- a/crates/ra_lsp_server/src/main.rs +++ b/crates/ra_lsp_server/src/main.rs @@ -1,5 +1,5 @@ use flexi_logger::{Duplicate, Logger}; -use gen_lsp_server::{run_server, stdio_transport}; +use lsp_server::{run_server, stdio_transport, LspServerError}; use ra_lsp_server::{show_message, Result, ServerConfig}; use ra_prof; @@ -29,9 +29,11 @@ fn main() -> Result<()> { } fn main_inner() -> Result<()> { - let (receiver, sender, threads) = stdio_transport(); + let (sender, receiver, io_threads) = stdio_transport(); let cwd = std::env::current_dir()?; - run_server(ra_lsp_server::server_capabilities(), receiver, sender, |params, r, s| { + let caps = serde_json::to_value(ra_lsp_server::server_capabilities()).unwrap(); + run_server(caps, sender, receiver, |params, s, r| { + let params: lsp_types::InitializeParams = serde_json::from_value(params)?; let root = params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd); let workspace_roots = params @@ -62,9 +64,13 @@ fn main_inner() -> Result<()> { .unwrap_or_default(); ra_lsp_server::main_loop(workspace_roots, params.capabilities, server_config, r, s) + }) + .map_err(|err| match err { + LspServerError::ProtocolError(err) => err.into(), + LspServerError::ServerError(err) => err, })?; log::info!("shutting down IO..."); - threads.join()?; + io_threads.join()?; log::info!("... IO is down"); Ok(()) } diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index 45bd52769..fb357b36b 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs @@ -5,9 +5,7 @@ pub(crate) mod pending_requests; use std::{error::Error, fmt, path::PathBuf, sync::Arc, time::Instant}; use crossbeam_channel::{select, unbounded, Receiver, RecvError, Sender}; -use gen_lsp_server::{ - handle_shutdown, ErrorCode, RawMessage, RawNotification, RawRequest, RawResponse, -}; +use lsp_server::{handle_shutdown, ErrorCode, Message, Notification, Request, RequestId, Response}; use lsp_types::{ClientCapabilities, NumberOrString}; use ra_ide_api::{Canceled, FeatureFlags, FileId, LibraryData}; use ra_prof::profile; @@ -53,8 +51,8 @@ pub fn main_loop( ws_roots: Vec, client_caps: ClientCapabilities, config: ServerConfig, - msg_receiver: &Receiver, - msg_sender: &Sender, + msg_receiver: &Receiver, + msg_sender: &Sender, ) -> Result<()> { log::info!("server_config: {:#?}", config); // FIXME: support dynamic workspace loading. @@ -146,12 +144,12 @@ pub fn main_loop( #[derive(Debug)] enum Task { - Respond(RawResponse), - Notify(RawNotification), + Respond(Response), + Notify(Notification), } enum Event { - Msg(RawMessage), + Msg(Message), Task(Task), Vfs(VfsTask), Lib(LibraryData), @@ -159,24 +157,28 @@ enum Event { impl fmt::Debug for Event { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let debug_verbose_not = |not: &RawNotification, f: &mut fmt::Formatter| { - f.debug_struct("RawNotification").field("method", ¬.method).finish() + let debug_verbose_not = |not: &Notification, f: &mut fmt::Formatter| { + f.debug_struct("Notification").field("method", ¬.method).finish() }; match self { - Event::Msg(RawMessage::Notification(not)) => { - if not.is::() || not.is::() { + Event::Msg(Message::Notification(not)) => { + if notification_is::(not) + || notification_is::(not) + { return debug_verbose_not(not, f); } } Event::Task(Task::Notify(not)) => { - if not.is::() || not.is::() { + if notification_is::(not) + || notification_is::(not) + { return debug_verbose_not(not, f); } } Event::Task(Task::Respond(resp)) => { return f - .debug_struct("RawResponse") + .debug_struct("Response") .field("id", &resp.id) .field("error", &resp.error) .finish(); @@ -194,8 +196,8 @@ impl fmt::Debug for Event { fn main_loop_inner( pool: &ThreadPool, - msg_sender: &Sender, - msg_receiver: &Receiver, + msg_sender: &Sender, + msg_receiver: &Receiver, task_sender: Sender, task_receiver: Receiver, state: &mut WorldState, @@ -249,10 +251,9 @@ fn main_loop_inner( in_flight_libraries -= 1; } Event::Msg(msg) => match msg { - RawMessage::Request(req) => { - let req = match handle_shutdown(req, msg_sender) { - Some(req) => req, - None => return Ok(()), + Message::Request(req) => { + if handle_shutdown(&req, msg_sender) { + return Ok(()); }; on_request( state, @@ -264,11 +265,11 @@ fn main_loop_inner( req, )? } - RawMessage::Notification(not) => { + Message::Notification(not) => { on_notification(msg_sender, state, pending_requests, &mut subs, not)?; state_changed = true; } - RawMessage::Response(resp) => log::error!("unexpected response: {:?}", resp), + Message::Response(resp) => log::error!("unexpected response: {:?}", resp), }, }; @@ -313,13 +314,13 @@ fn main_loop_inner( fn on_task( task: Task, - msg_sender: &Sender, + msg_sender: &Sender, pending_requests: &mut PendingRequests, state: &mut WorldState, ) { match task { Task::Respond(response) => { - if let Some(completed) = pending_requests.finish(response.id) { + if let Some(completed) = pending_requests.finish(&response.id) { log::info!("handled req#{} in {:?}", completed.id, completed.duration); state.complete_request(completed); msg_sender.send(response.into()).unwrap(); @@ -336,9 +337,9 @@ fn on_request( pending_requests: &mut PendingRequests, pool: &ThreadPool, sender: &Sender, - msg_sender: &Sender, + msg_sender: &Sender, request_received: Instant, - req: RawRequest, + req: Request, ) -> Result<()> { let mut pool_dispatcher = PoolDispatcher { req: Some(req), @@ -388,22 +389,20 @@ fn on_request( } fn on_notification( - msg_sender: &Sender, + msg_sender: &Sender, state: &mut WorldState, pending_requests: &mut PendingRequests, subs: &mut Subscriptions, - not: RawNotification, + not: Notification, ) -> Result<()> { - let not = match not.cast::() { + let not = match notification_cast::(not) { Ok(params) => { - let id = match params.id { - NumberOrString::Number(id) => id, - NumberOrString::String(id) => { - panic!("string id's not supported: {:?}", id); - } + let id: RequestId = match params.id { + NumberOrString::Number(id) => id.into(), + NumberOrString::String(id) => id.into(), }; - if pending_requests.cancel(id) { - let response = RawResponse::err( + if pending_requests.cancel(&id) { + let response = Response::new_err( id, ErrorCode::RequestCanceled as i32, "canceled by client".to_string(), @@ -414,7 +413,7 @@ fn on_notification( } Err(not) => not, }; - let not = match not.cast::() { + let not = match notification_cast::(not) { Ok(params) => { let uri = params.text_document.uri; let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; @@ -427,7 +426,7 @@ fn on_notification( } Err(not) => not, }; - let not = match not.cast::() { + let not = match notification_cast::(not) { Ok(mut params) => { let uri = params.text_document.uri; let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; @@ -438,7 +437,7 @@ fn on_notification( } Err(not) => not, }; - let not = match not.cast::() { + let not = match notification_cast::(not) { Ok(params) => { let uri = params.text_document.uri; let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; @@ -446,13 +445,13 @@ fn on_notification( subs.remove_sub(FileId(file_id.0)); } let params = req::PublishDiagnosticsParams { uri, diagnostics: Vec::new() }; - let not = RawNotification::new::(¶ms); + let not = notification_new::(params); msg_sender.send(not.into()).unwrap(); return Ok(()); } Err(not) => not, }; - let not = match not.cast::() { + let not = match notification_cast::(not) { Ok(_params) => { return Ok(()); } @@ -463,11 +462,11 @@ fn on_notification( } struct PoolDispatcher<'a> { - req: Option, + req: Option, pool: &'a ThreadPool, world: &'a mut WorldState, pending_requests: &'a mut PendingRequests, - msg_sender: &'a Sender, + msg_sender: &'a Sender, sender: &'a Sender, request_received: Instant, } @@ -522,13 +521,13 @@ impl<'a> PoolDispatcher<'a> { Ok(self) } - fn parse(&mut self) -> Option<(u64, R::Params)> + fn parse(&mut self) -> Option<(RequestId, R::Params)> where R: req::Request + 'static, R::Params: DeserializeOwned + Send + 'static, { let req = self.req.take()?; - let (id, params) = match req.cast::() { + let (id, params) = match req.extract::(R::METHOD) { Ok(it) => it, Err(req) => { self.req = Some(req); @@ -536,7 +535,7 @@ impl<'a> PoolDispatcher<'a> { } }; self.pending_requests.start(PendingRequest { - id, + id: id.clone(), method: R::METHOD.to_string(), received: self.request_received, }); @@ -548,7 +547,7 @@ impl<'a> PoolDispatcher<'a> { None => (), Some(req) => { log::error!("unknown request: {:?}", req); - let resp = RawResponse::err( + let resp = Response::new_err( req.id, ErrorCode::MethodNotFound as i32, "unknown request".to_string(), @@ -559,34 +558,30 @@ impl<'a> PoolDispatcher<'a> { } } -fn result_to_task(id: u64, result: Result) -> Task +fn result_to_task(id: RequestId, result: Result) -> Task where R: req::Request + 'static, R::Params: DeserializeOwned + Send + 'static, R::Result: Serialize + 'static, { let response = match result { - Ok(resp) => RawResponse::ok::(id, &resp), + Ok(resp) => Response::new_ok(id, &resp), Err(e) => match e.downcast::() { - Ok(lsp_error) => RawResponse::err(id, lsp_error.code, lsp_error.message), + Ok(lsp_error) => Response::new_err(id, lsp_error.code, lsp_error.message), Err(e) => { if is_canceled(&e) { // FIXME: When https://github.com/Microsoft/vscode-languageserver-node/issues/457 // gets fixed, we can return the proper response. // This works around the issue where "content modified" error would continuously // show an message pop-up in VsCode - // RawResponse::err( + // Response::err( // id, // ErrorCode::ContentModified as i32, // "content modified".to_string(), // ) - RawResponse { - id, - result: Some(serde_json::to_value(&()).unwrap()), - error: None, - } + Response::new_ok(id, ()) } else { - RawResponse::err(id, ErrorCode::InternalError as i32, e.to_string()) + Response::new_err(id, ErrorCode::InternalError as i32, e.to_string()) } } }, @@ -613,7 +608,7 @@ fn update_file_notifications_on_threadpool( } } Ok(params) => { - let not = RawNotification::new::(¶ms); + let not = notification_new::(params); sender.send(Task::Notify(not)).unwrap(); } } @@ -626,7 +621,7 @@ fn update_file_notifications_on_threadpool( } } Ok(params) => { - let not = RawNotification::new::(¶ms); + let not = notification_new::(params); sender.send(Task::Notify(not)).unwrap(); } } @@ -635,17 +630,33 @@ fn update_file_notifications_on_threadpool( }); } -pub fn show_message( - typ: req::MessageType, - message: impl Into, - sender: &Sender, -) { +pub fn show_message(typ: req::MessageType, message: impl Into, sender: &Sender) { let message = message.into(); let params = req::ShowMessageParams { typ, message }; - let not = RawNotification::new::(¶ms); + let not = notification_new::(params); sender.send(not.into()).unwrap(); } fn is_canceled(e: &Box) -> bool { e.downcast_ref::().is_some() } + +fn notification_is(notification: &Notification) -> bool { + notification.method == N::METHOD +} + +fn notification_cast(notification: Notification) -> std::result::Result +where + N: lsp_types::notification::Notification, + N::Params: DeserializeOwned, +{ + notification.extract(N::METHOD) +} + +fn notification_new(params: N::Params) -> Notification +where + N: lsp_types::notification::Notification, + N::Params: Serialize, +{ + Notification::new(N::METHOD.to_string(), params) +} diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index 3a559e845..eb805a6d3 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs @@ -1,6 +1,6 @@ use std::{fmt::Write as _, io::Write as _}; -use gen_lsp_server::ErrorCode; +use lsp_server::ErrorCode; use lsp_types::{ CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic, DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeKind, diff --git a/crates/ra_lsp_server/src/main_loop/pending_requests.rs b/crates/ra_lsp_server/src/main_loop/pending_requests.rs index 741770e45..7a99fc679 100644 --- a/crates/ra_lsp_server/src/main_loop/pending_requests.rs +++ b/crates/ra_lsp_server/src/main_loop/pending_requests.rs @@ -1,17 +1,18 @@ use std::time::{Duration, Instant}; +use lsp_server::RequestId; use rustc_hash::FxHashMap; #[derive(Debug)] pub struct CompletedRequest { - pub id: u64, + pub id: RequestId, pub method: String, pub duration: Duration, } #[derive(Debug)] pub(crate) struct PendingRequest { - pub(crate) id: u64, + pub(crate) id: RequestId, pub(crate) method: String, pub(crate) received: Instant, } @@ -28,20 +29,20 @@ impl From for CompletedRequest { #[derive(Debug, Default)] pub(crate) struct PendingRequests { - map: FxHashMap, + map: FxHashMap, } impl PendingRequests { pub(crate) fn start(&mut self, request: PendingRequest) { - let id = request.id; - let prev = self.map.insert(id, request); + let id = request.id.clone(); + let prev = self.map.insert(id.clone(), request); assert!(prev.is_none(), "duplicate request with id {}", id); } - pub(crate) fn cancel(&mut self, id: u64) -> bool { - self.map.remove(&id).is_some() + pub(crate) fn cancel(&mut self, id: &RequestId) -> bool { + self.map.remove(id).is_some() } - pub(crate) fn finish(&mut self, id: u64) -> Option { - self.map.remove(&id).map(CompletedRequest::from) + pub(crate) fn finish(&mut self, id: &RequestId) -> Option { + self.map.remove(id).map(CompletedRequest::from) } } diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs index cc7964469..73d7f8fb9 100644 --- a/crates/ra_lsp_server/src/world.rs +++ b/crates/ra_lsp_server/src/world.rs @@ -4,7 +4,7 @@ use std::{ }; use crossbeam_channel::{unbounded, Receiver}; -use gen_lsp_server::ErrorCode; +use lsp_server::ErrorCode; use lsp_types::Url; use parking_lot::RwLock; use ra_ide_api::{ diff --git a/crates/ra_lsp_server/tests/heavy_tests/support.rs b/crates/ra_lsp_server/tests/heavy_tests/support.rs index 055c8fff2..45b4cacf6 100644 --- a/crates/ra_lsp_server/tests/heavy_tests/support.rs +++ b/crates/ra_lsp_server/tests/heavy_tests/support.rs @@ -8,13 +8,10 @@ use std::{ use crossbeam_channel::{after, select, Receiver}; use flexi_logger::Logger; -use gen_lsp_server::{RawMessage, RawNotification, RawRequest}; +use lsp_server::{Message, Notification, Request}; use lsp_types::{ - notification::DidOpenTextDocument, - notification::{Notification, ShowMessage}, - request::{Request, Shutdown}, - ClientCapabilities, DidOpenTextDocumentParams, GotoCapability, TextDocumentClientCapabilities, - TextDocumentIdentifier, TextDocumentItem, Url, + request::Shutdown, ClientCapabilities, DidOpenTextDocumentParams, GotoCapability, + TextDocumentClientCapabilities, TextDocumentIdentifier, TextDocumentItem, Url, }; use serde::Serialize; use serde_json::{to_string_pretty, Value}; @@ -84,9 +81,9 @@ pub fn project(fixture: &str) -> Server { pub struct Server { req_id: Cell, - messages: RefCell>, + messages: RefCell>, dir: TempDir, - worker: Worker, + worker: Worker, } impl Server { @@ -100,7 +97,7 @@ impl Server { let roots = if roots.is_empty() { vec![path] } else { roots }; - let worker = Worker::::spawn( + let worker = Worker::::spawn( "test server", 128, move |msg_receiver, msg_sender| { @@ -128,7 +125,8 @@ impl Server { let res = Server { req_id: Cell::new(1), dir, messages: Default::default(), worker }; for (path, text) in files { - res.send_notification(RawNotification::new::( + res.send_notification(Notification::new( + "textDocument/didOpen".to_string(), &DidOpenTextDocumentParams { text_document: TextDocumentItem { uri: Url::from_file_path(path).unwrap(), @@ -149,16 +147,16 @@ impl Server { pub fn notification(&self, params: N::Params) where - N: Notification, + N: lsp_types::notification::Notification, N::Params: Serialize, { - let r = RawNotification::new::(¶ms); + let r = Notification::new(N::METHOD.to_string(), params); self.send_notification(r) } pub fn request(&self, params: R::Params, expected_resp: Value) where - R: Request, + R: lsp_types::request::Request, R::Params: Serialize, { let actual = self.send_request::(params); @@ -175,23 +173,23 @@ impl Server { pub fn send_request(&self, params: R::Params) -> Value where - R: Request, + R: lsp_types::request::Request, R::Params: Serialize, { let id = self.req_id.get(); self.req_id.set(id + 1); - let r = RawRequest::new::(id, ¶ms); + let r = Request::new(id.into(), R::METHOD.to_string(), params); self.send_request_(r) } - fn send_request_(&self, r: RawRequest) -> Value { - let id = r.id; - self.worker.sender().send(RawMessage::Request(r)).unwrap(); + fn send_request_(&self, r: Request) -> Value { + let id = r.id.clone(); + self.worker.sender().send(r.into()).unwrap(); while let Some(msg) = self.recv() { match msg { - RawMessage::Request(req) => panic!("unexpected request: {:?}", req), - RawMessage::Notification(_) => (), - RawMessage::Response(res) => { + Message::Request(req) => panic!("unexpected request: {:?}", req), + Message::Notification(_) => (), + Message::Response(res) => { assert_eq!(res.id, id); if let Some(err) = res.error { panic!("error response: {:#?}", err); @@ -203,15 +201,16 @@ impl Server { panic!("no response"); } pub fn wait_until_workspace_is_loaded(&self) { - self.wait_for_message_cond(1, &|msg: &RawMessage| match msg { - RawMessage::Notification(n) if n.method == ShowMessage::METHOD => { - let msg = n.clone().cast::().unwrap(); + self.wait_for_message_cond(1, &|msg: &Message| match msg { + Message::Notification(n) if n.method == "window/showMessage" => { + let msg = + n.clone().extract::("window/showMessage").unwrap(); msg.message.starts_with("workspace loaded") } _ => false, }) } - fn wait_for_message_cond(&self, n: usize, cond: &dyn Fn(&RawMessage) -> bool) { + fn wait_for_message_cond(&self, n: usize, cond: &dyn Fn(&Message) -> bool) { let mut total = 0; for msg in self.messages.borrow().iter() { if cond(msg) { @@ -225,14 +224,14 @@ impl Server { } } } - fn recv(&self) -> Option { + fn recv(&self) -> Option { recv_timeout(&self.worker.receiver()).map(|msg| { self.messages.borrow_mut().push(msg.clone()); msg }) } - fn send_notification(&self, not: RawNotification) { - self.worker.sender().send(RawMessage::Notification(not)).unwrap(); + fn send_notification(&self, not: Notification) { + self.worker.sender().send(Message::Notification(not)).unwrap(); } pub fn path(&self) -> &Path { @@ -246,7 +245,7 @@ impl Drop for Server { } } -fn recv_timeout(receiver: &Receiver) -> Option { +fn recv_timeout(receiver: &Receiver) -> Option { let timeout = Duration::from_secs(120); select! { recv(receiver) -> msg => msg.ok(), -- cgit v1.2.3