From d7c5a6f3081c2e7266620779d3c32067f947b959 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 10 Aug 2018 15:07:43 +0300 Subject: Start lang server --- codeless/server/src/caps.rs | 23 +++++ codeless/server/src/dispatch.rs | 124 +++++++++++++++++++++++++ codeless/server/src/io.rs | 201 ++++++++++++++++++++++++++++++++++++++++ codeless/server/src/main.rs | 84 +++++++++++++++++ codeless/server/src/req.rs | 16 ++++ 5 files changed, 448 insertions(+) create mode 100644 codeless/server/src/caps.rs create mode 100644 codeless/server/src/dispatch.rs create mode 100644 codeless/server/src/io.rs create mode 100644 codeless/server/src/main.rs create mode 100644 codeless/server/src/req.rs (limited to 'codeless/server/src') diff --git a/codeless/server/src/caps.rs b/codeless/server/src/caps.rs new file mode 100644 index 000000000..b2fad6732 --- /dev/null +++ b/codeless/server/src/caps.rs @@ -0,0 +1,23 @@ +use languageserver_types::ServerCapabilities; + +pub const SERVER_CAPABILITIES: ServerCapabilities = ServerCapabilities { + text_document_sync: None, + hover_provider: None, + completion_provider: None, + signature_help_provider: None, + definition_provider: None, + type_definition_provider: None, + implementation_provider: None, + references_provider: None, + document_highlight_provider: None, + document_symbol_provider: None, + workspace_symbol_provider: None, + code_action_provider: None, + code_lens_provider: None, + document_formatting_provider: None, + document_range_formatting_provider: None, + document_on_type_formatting_provider: None, + rename_provider: None, + color_provider: None, + execute_command_provider: None, +}; diff --git a/codeless/server/src/dispatch.rs b/codeless/server/src/dispatch.rs new file mode 100644 index 000000000..a9476acde --- /dev/null +++ b/codeless/server/src/dispatch.rs @@ -0,0 +1,124 @@ +use std::marker::PhantomData; + +use serde::{ + ser::Serialize, + de::DeserializeOwned, +}; +use serde_json; +use drop_bomb::DropBomb; + +use ::{ + Result, + req::Request, + io::{Io, RawMsg, RawResponse, RawRequest}, +}; + +pub struct Responder { + id: u64, + bomb: DropBomb, + ph: PhantomData, +} + +impl Responder + where + R::Params: DeserializeOwned, + R::Result: Serialize, +{ + pub fn respond_with(self, io: &mut Io, f: impl FnOnce() -> Result) -> Result<()> { + match f() { + Ok(res) => self.result(io, res)?, + Err(e) => { + self.error(io)?; + return Err(e); + } + } + Ok(()) + } + + pub fn result(mut self, io: &mut Io, result: R::Result) -> Result<()> { + self.bomb.defuse(); + io.send(RawMsg::Response(RawResponse { + id: Some(self.id), + result: serde_json::to_value(result)?, + error: serde_json::Value::Null, + })); + Ok(()) + } + + pub fn error(mut self, io: &mut Io) -> Result<()> { + self.bomb.defuse(); + error(io, self.id, ErrorCode::InternalError, "internal error") + } +} + + +pub fn parse_as(raw: RawRequest) -> Result<::std::result::Result<(R::Params, Responder), RawRequest>> + where + R: Request, + R::Params: DeserializeOwned, + R::Result: Serialize, +{ + if raw.method != R::METHOD { + return Ok(Err(raw)); + } + + let params: R::Params = serde_json::from_value(raw.params)?; + let responder = Responder { + id: raw.id, + bomb: DropBomb::new("dropped request"), + ph: PhantomData, + }; + Ok(Ok((params, responder))) +} + +pub fn expect(io: &mut Io, raw: RawRequest) -> Result)>> + where + R: Request, + R::Params: DeserializeOwned, + R::Result: Serialize, +{ + let ret = match parse_as::(raw)? { + Ok(x) => Some(x), + Err(raw) => { + unknown_method(io, raw)?; + None + } + }; + Ok(ret) +} + +pub fn unknown_method(io: &mut Io, raw: RawRequest) -> Result<()> { + error(io, raw.id, ErrorCode::MethodNotFound, "unknown method") +} + +fn error(io: &mut Io, id: u64, code: ErrorCode, message: &'static str) -> Result<()> { + #[derive(Serialize)] + struct Error { + code: i32, + message: &'static str, + } + io.send(RawMsg::Response(RawResponse { + id: Some(id), + result: serde_json::Value::Null, + error: serde_json::to_value(Error { + code: code as i32, + message, + })?, + })); + Ok(()) +} + + +#[allow(unused)] +enum ErrorCode { + ParseError = -32700, + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, + ServerErrorStart = -32099, + ServerErrorEnd = -32000, + ServerNotInitialized = -32002, + UnknownErrorCode = -32001, + RequestCancelled = -32800, +} diff --git a/codeless/server/src/io.rs b/codeless/server/src/io.rs new file mode 100644 index 000000000..b84103d65 --- /dev/null +++ b/codeless/server/src/io.rs @@ -0,0 +1,201 @@ +use std::{ + thread, + io::{ + stdout, stdin, + BufRead, Write, + }, +}; +use serde_json::{Value, from_str, to_string}; +use crossbeam_channel::{Receiver, Sender, bounded}; + +use Result; + + +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum RawMsg { + 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 RawNotification { + pub method: String, + pub params: Value, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct RawResponse { + pub id: Option, + pub result: Value, + pub error: Value, +} + +struct MsgReceiver { + chan: Receiver, + thread: Option>>, +} + +impl MsgReceiver { + fn recv(&mut self) -> Result { + match self.chan.recv() { + Some(msg) => Ok(msg), + None => { + self.thread + .take() + .ok_or_else(|| format_err!("MsgReceiver thread panicked"))? + .join() + .map_err(|_| format_err!("MsgReceiver thread panicked"))??; + bail!("client disconnected") + } + } + } + + fn stop(self) -> Result<()> { + // Can't really self.thread.join() here, b/c it might be + // blocking on read + Ok(()) + } +} + +struct MsgSender { + chan: Sender, + thread: Option>>, +} + +impl MsgSender { + fn send(&mut self, msg: RawMsg) { + self.chan.send(msg) + } + + fn stop(mut self) -> Result<()> { + if let Some(thread) = self.thread.take() { + thread.join() + .map_err(|_| format_err!("MsgSender thread panicked"))?? + } + Ok(()) + } +} + +impl Drop for MsgSender { + fn drop(&mut self) { + if let Some(thread) = self.thread.take() { + let res = thread.join(); + if thread::panicking() { + drop(res) + } else { + res.unwrap().unwrap() + } + } + } +} + +pub struct Io { + receiver: MsgReceiver, + sender: MsgSender, +} + +impl Io { + pub fn from_stdio() -> Io { + let sender = { + let (tx, rx) = bounded(16); + MsgSender { + chan: tx, + thread: Some(thread::spawn(move || { + let stdout = stdout(); + let mut stdout = stdout.lock(); + for msg in rx { + #[derive(Serialize)] + struct JsonRpc { + jsonrpc: &'static str, + #[serde(flatten)] + msg: RawMsg, + } + let text = to_string(&JsonRpc { + jsonrpc: "2.0", + msg, + })?; + write_msg_text(&mut stdout, &text)?; + } + Ok(()) + })), + } + }; + let receiver = { + let (tx, rx) = bounded(16); + MsgReceiver { + chan: rx, + thread: Some(thread::spawn(move || { + let stdin = stdin(); + let mut stdin = stdin.lock(); + while let Some(text) = read_msg_text(&mut stdin)? { + let msg: RawMsg = from_str(&text)?; + tx.send(msg); + } + Ok(()) + })), + } + }; + Io { receiver, sender } + } + + pub fn send(&mut self, msg: RawMsg) { + self.sender.send(msg) + } + + pub fn recv(&mut self) -> Result { + self.receiver.recv() + } + + pub fn stop(self) -> Result<()> { + self.receiver.stop()?; + self.sender.stop()?; + Ok(()) + } +} + + +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)?; + Ok(Some(buf)) +} + +fn write_msg_text(out: &mut impl Write, msg: &str) -> Result<()> { + write!(out, "Content-Length: {}\r\n\r\n", msg.len())?; + out.write_all(msg.as_bytes())?; + out.flush()?; + Ok(()) +} diff --git a/codeless/server/src/main.rs b/codeless/server/src/main.rs new file mode 100644 index 000000000..11b6b7067 --- /dev/null +++ b/codeless/server/src/main.rs @@ -0,0 +1,84 @@ +#[macro_use] +extern crate failure; +#[macro_use] +extern crate serde_derive; +extern crate serde; +extern crate serde_json; +extern crate languageserver_types; +extern crate drop_bomb; +extern crate crossbeam_channel; +extern crate libeditor; +extern crate libanalysis; + +mod io; +mod caps; +mod req; +mod dispatch; + +use languageserver_types::InitializeResult; +use libanalysis::WorldState; +use self::io::{Io, RawMsg}; + +pub type Result = ::std::result::Result; + +fn main() -> Result<()> { + let mut io = Io::from_stdio(); + initialize(&mut io)?; + io.stop()?; + Ok(()) +} + +fn initialize(io: &mut Io) -> Result<()> { + loop { + match io.recv()? { + RawMsg::Request(req) => { + if let Some((_params, resp)) = dispatch::expect::(io, req)? { + resp.result(io, InitializeResult { + capabilities: caps::SERVER_CAPABILITIES + })?; + match io.recv()? { + RawMsg::Notification(n) => { + if n.method != "initialized" { + bail!("expected initialized notification"); + } + } + _ => { + bail!("expected initialized notification"); + } + } + return initialized(io); + } + } + RawMsg::Notification(n) => { + bail!("expected initialize request, got {:?}", n) + } + RawMsg::Response(res) => { + bail!("expected initialize request, got {:?}", res) + } + } + } +} + +fn initialized(io: &mut Io) -> Result<()> { + eprintln!("initialized"); + let world = WorldState::new(); + loop { + match io.recv()? { + RawMsg::Request(req) => { + let world = world.snapshot(); + if let Some((params, resp)) = dispatch::expect::(io, req)? { + resp.respond_with(io, || { + let path = params.text_document.uri.to_file_path() + .map_err(|()| format_err!("invalid path"))?; + let file = world.file_syntax(&path)?; + Ok(libeditor::syntax_tree(&file)) + })? + } + } + msg => { + eprintln!("msg = {:?}", msg); + } + } + } +} + diff --git a/codeless/server/src/req.rs b/codeless/server/src/req.rs new file mode 100644 index 000000000..bc54c1d33 --- /dev/null +++ b/codeless/server/src/req.rs @@ -0,0 +1,16 @@ +use languageserver_types::TextDocumentIdentifier; +pub use languageserver_types::request::*; + +pub enum SyntaxTree {} + +impl Request for SyntaxTree { + type Params = SyntaxTreeParams; + type Result = String; + const METHOD: &'static str = "m/syntaxTree"; +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all="camelCase")] +pub struct SyntaxTreeParams { + pub text_document: TextDocumentIdentifier +} -- cgit v1.2.3