From 3588d6b2da6e63730cc560c9986ba7fda5de816e Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 1 Sep 2018 16:18:02 +0300 Subject: add gen_lsp_server --- crates/gen_lsp_server/Cargo.toml | 14 +++ crates/gen_lsp_server/src/lib.rs | 77 +++++++++++++++++ crates/gen_lsp_server/src/msg.rs | 172 +++++++++++++++++++++++++++++++++++++ crates/gen_lsp_server/src/stdio.rs | 49 +++++++++++ 4 files changed, 312 insertions(+) create mode 100644 crates/gen_lsp_server/Cargo.toml create mode 100644 crates/gen_lsp_server/src/lib.rs create mode 100644 crates/gen_lsp_server/src/msg.rs create mode 100644 crates/gen_lsp_server/src/stdio.rs (limited to 'crates/gen_lsp_server') diff --git a/crates/gen_lsp_server/Cargo.toml b/crates/gen_lsp_server/Cargo.toml new file mode 100644 index 000000000..b31884802 --- /dev/null +++ b/crates/gen_lsp_server/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "gen_lsp_server" +version = "0.1.0" +authors = ["Aleksey Kladov "] + +[dependencies] +languageserver-types = "0.49.0" +log = "0.4.3" + +failure = "0.1.2" +serde_json = "1.0.24" +serde = "1.0.71" +serde_derive = "1.0.71" +crossbeam-channel = "0.2.4" diff --git a/crates/gen_lsp_server/src/lib.rs b/crates/gen_lsp_server/src/lib.rs new file mode 100644 index 000000000..a31e90f35 --- /dev/null +++ b/crates/gen_lsp_server/src/lib.rs @@ -0,0 +1,77 @@ +#[macro_use] +extern crate failure; +#[macro_use] +extern crate log; +extern crate serde; +extern crate serde_json; +#[macro_use] +extern crate serde_derive; +extern crate crossbeam_channel; +extern crate languageserver_types; + +mod msg; +mod stdio; + +use crossbeam_channel::{Sender, Receiver}; +use languageserver_types::{ + ServerCapabilities, InitializeResult, + request::{Initialize}, + notification::{Initialized, Exit}, +}; + +pub type Result = ::std::result::Result; +pub use { + msg::{RawMessage, RawRequest, RawResponse, RawResponseError, RawNotification}, + stdio::{stdio_transport, Threads}, +}; + +pub type LspServer = fn(&mut Receiver, &mut Sender) -> Result<()>; + +pub fn run_server( + caps: ServerCapabilities, + server: LspServer, + mut receiver: Receiver, + mut sender: Sender, +) -> Result<()> { + info!("lsp server initializes"); + initialize(&mut receiver, &mut sender, caps)?; + info!("lsp server initialized, serving requests"); + server(&mut receiver, &mut sender)?; + info!("lsp server waiting for exit notification"); + match receiver.recv() { + Some(RawMessage::Notification(n)) => { + n.cast::().map_err(|n| format_err!( + "unexpected notification during shutdown: {:?}", n + ))? + } + m => bail!("unexpected message during shutdown: {:?}", m) + } + info!("lsp server shutdown complete"); + Ok(()) +} + +fn initialize( + receiver: &mut Receiver, + sender: &mut Sender, + caps: ServerCapabilities, +) -> Result<()> { + let id = match receiver.recv() { + Some(RawMessage::Request(req)) => match req.cast::() { + Err(req) => bail!("expected initialize request, got {:?}", req), + Ok(req) => req.0, + } + msg => + bail!("expected initialize request, got {:?}", msg), + }; + let resp = RawResponse::ok(id, InitializeResult { capabilities: caps }); + sender.send(RawMessage::Response(resp)); + match receiver.recv() { + Some(RawMessage::Notification(n)) => { + n.cast::().map_err(|_| format_err!( + "expected initialized notification" + ))?; + } + _ => bail!("expected initialized notification"), + } + Ok(()) +} diff --git a/crates/gen_lsp_server/src/msg.rs b/crates/gen_lsp_server/src/msg.rs new file mode 100644 index 000000000..9426e98ec --- /dev/null +++ b/crates/gen_lsp_server/src/msg.rs @@ -0,0 +1,172 @@ +use std::io::{BufRead, Write}; + +use serde_json::{Value, from_str, to_string, from_value, to_value}; +use serde::{Serialize, de::DeserializeOwned}; +use languageserver_types::{ + request::Request, + notification::Notification, +}; + +use Result; + +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum RawMessage { + Request(RawRequest), + Notification(RawNotification), + Response(RawResponse), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct RawRequest { + pub id: u64, + pub method: String, + pub params: Value, +} + +#[derive(Debug, Serialize, Deserialize)] +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)] +pub struct RawResponseError { + pub code: i32, + pub message: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub data: Option, +} + +#[allow(unused)] +pub enum ErrorCode { + ParseError = -32700, + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, + ServerErrorStart = -32099, + ServerErrorEnd = -32000, + ServerNotInitialized = -32002, + UnknownErrorCode = -32001, + RequestCancelled = -32800, +} + +#[derive(Debug, Serialize, Deserialize)] +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 cast(self) -> ::std::result::Result<(u64, R::Params), RawRequest> + where + R: Request, + R::Params: 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: impl Serialize) -> RawResponse { + 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 cast(self) -> ::std::result::Result + where + N: Notification, + N::Params: DeserializeOwned, + { + if self.method != N::METHOD { + 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") { + bail!("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_err!("malformed header: {:?}", buf))?; + if header_name == "Content-Length" { + size = Some(header_value.parse::()?); + } + } + let size = size.ok_or_else(|| format_err!("no Content-Length"))?; + let mut buf = buf.into_bytes(); + buf.resize(size, 0); + inp.read_exact(&mut buf)?; + let buf = String::from_utf8(buf)?; + debug!("< {}", buf); + Ok(Some(buf)) +} + +fn write_msg_text(out: &mut impl Write, msg: &str) -> Result<()> { + 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 new file mode 100644 index 000000000..81397bb2a --- /dev/null +++ b/crates/gen_lsp_server/src/stdio.rs @@ -0,0 +1,49 @@ +use std::{ + thread, + io::{ + stdout, stdin, + }, +}; + +use crossbeam_channel::{Receiver, Sender, bounded}; + +use {RawMessage, Result}; + +pub fn stdio_transport() -> (Receiver, Sender, Threads) { + let (writer_sender, mut writer_receiver) = bounded::(16); + let writer = thread::spawn(move || { + let stdout = stdout(); + let mut stdout = stdout.lock(); + writer_receiver.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)? { + reader_sender.send(msg); + } + 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(_) => bail!("reader panicked"), + } + match self.writer.join() { + Ok(r) => r, + Err(_) => bail!("writer panicked"), + } + } +} -- cgit v1.2.3