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/.gitignore | 1 + codeless/server/Cargo.toml | 16 +++ 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 +++ codeless/server/target/.rustc_info.json | 1 + codeless/server/target/debug/.cargo-lock | 0 codeless/server/target/debug/libm.d | 1 + codeless/server/target/debug/libm.rmeta | 0 11 files changed, 467 insertions(+) create mode 100644 codeless/server/.gitignore create mode 100644 codeless/server/Cargo.toml 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 create mode 100644 codeless/server/target/.rustc_info.json create mode 100644 codeless/server/target/debug/.cargo-lock create mode 100644 codeless/server/target/debug/libm.d create mode 100644 codeless/server/target/debug/libm.rmeta (limited to 'codeless/server') diff --git a/codeless/server/.gitignore b/codeless/server/.gitignore new file mode 100644 index 000000000..5a50b7f98 --- /dev/null +++ b/codeless/server/.gitignore @@ -0,0 +1 @@ +/target/* diff --git a/codeless/server/Cargo.toml b/codeless/server/Cargo.toml new file mode 100644 index 000000000..4c3dd345c --- /dev/null +++ b/codeless/server/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "m" +version = "0.1.0" +authors = ["Aleksey Kladov "] +[workspace] + +[dependencies] +failure = "0.1.2" +languageserver-types = "0.48.0" +serde_json = "1.0.24" +serde = "1.0.71" +serde_derive = "1.0.71" +drop_bomb = "0.1.0" +crossbeam-channel = "0.2.4" +libeditor = { path = "../../libeditor" } +libanalysis = { path = "../../libanalysis" } 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 +} diff --git a/codeless/server/target/.rustc_info.json b/codeless/server/target/.rustc_info.json new file mode 100644 index 000000000..a37ac2011 --- /dev/null +++ b/codeless/server/target/.rustc_info.json @@ -0,0 +1 @@ +{"rustc_fingerprint":11898242945176772229,"outputs":{"15337506775154344876":["___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/matklad/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\ndebug_assertions\nproc_macro\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\nunix\n",""],"1617349019360157463":["___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/matklad/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\ndebug_assertions\nproc_macro\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\nunix\n",""],"1164083562126845933":["rustc 1.28.0 (9634041f0 2018-07-30)\nbinary: rustc\ncommit-hash: 9634041f0e8c0f3191d2867311276f19d0a42564\ncommit-date: 2018-07-30\nhost: x86_64-unknown-linux-gnu\nrelease: 1.28.0\nLLVM version: 6.0\n",""]}} \ No newline at end of file diff --git a/codeless/server/target/debug/.cargo-lock b/codeless/server/target/debug/.cargo-lock new file mode 100644 index 000000000..e69de29bb diff --git a/codeless/server/target/debug/libm.d b/codeless/server/target/debug/libm.d new file mode 100644 index 000000000..04d8bb9ed --- /dev/null +++ b/codeless/server/target/debug/libm.d @@ -0,0 +1 @@ +/home/matklad/projects/libsyntax2/codeless/server/target/debug/libm.rmeta: /home/matklad/projects/libsyntax2/codeless/server/src/caps.rs /home/matklad/projects/libsyntax2/codeless/server/src/dispatch.rs /home/matklad/projects/libsyntax2/codeless/server/src/io.rs /home/matklad/projects/libsyntax2/codeless/server/src/main.rs /home/matklad/projects/libsyntax2/codeless/server/src/req.rs /home/matklad/projects/libsyntax2/libanalysis/src/lib.rs /home/matklad/projects/libsyntax2/libeditor/src/extend_selection.rs /home/matklad/projects/libsyntax2/libeditor/src/lib.rs /home/matklad/projects/libsyntax2/src/algo/mod.rs /home/matklad/projects/libsyntax2/src/algo/walk.rs /home/matklad/projects/libsyntax2/src/ast/generated.rs /home/matklad/projects/libsyntax2/src/ast/mod.rs /home/matklad/projects/libsyntax2/src/grammar/attributes.rs /home/matklad/projects/libsyntax2/src/grammar/expressions/atom.rs /home/matklad/projects/libsyntax2/src/grammar/expressions/mod.rs /home/matklad/projects/libsyntax2/src/grammar/items/consts.rs /home/matklad/projects/libsyntax2/src/grammar/items/mod.rs /home/matklad/projects/libsyntax2/src/grammar/items/structs.rs /home/matklad/projects/libsyntax2/src/grammar/items/traits.rs /home/matklad/projects/libsyntax2/src/grammar/items/use_item.rs /home/matklad/projects/libsyntax2/src/grammar/mod.rs /home/matklad/projects/libsyntax2/src/grammar/params.rs /home/matklad/projects/libsyntax2/src/grammar/paths.rs /home/matklad/projects/libsyntax2/src/grammar/patterns.rs /home/matklad/projects/libsyntax2/src/grammar/type_args.rs /home/matklad/projects/libsyntax2/src/grammar/type_params.rs /home/matklad/projects/libsyntax2/src/grammar/types.rs /home/matklad/projects/libsyntax2/src/lexer/classes.rs /home/matklad/projects/libsyntax2/src/lexer/comments.rs /home/matklad/projects/libsyntax2/src/lexer/mod.rs /home/matklad/projects/libsyntax2/src/lexer/numbers.rs /home/matklad/projects/libsyntax2/src/lexer/ptr.rs /home/matklad/projects/libsyntax2/src/lexer/strings.rs /home/matklad/projects/libsyntax2/src/lib.rs /home/matklad/projects/libsyntax2/src/parser_api.rs /home/matklad/projects/libsyntax2/src/parser_impl/event.rs /home/matklad/projects/libsyntax2/src/parser_impl/input.rs /home/matklad/projects/libsyntax2/src/parser_impl/mod.rs /home/matklad/projects/libsyntax2/src/smol_str.rs /home/matklad/projects/libsyntax2/src/syntax_kinds/generated.rs /home/matklad/projects/libsyntax2/src/syntax_kinds/mod.rs /home/matklad/projects/libsyntax2/src/utils.rs /home/matklad/projects/libsyntax2/src/yellow/builder.rs /home/matklad/projects/libsyntax2/src/yellow/green.rs /home/matklad/projects/libsyntax2/src/yellow/mod.rs /home/matklad/projects/libsyntax2/src/yellow/red.rs /home/matklad/projects/libsyntax2/src/yellow/syntax.rs diff --git a/codeless/server/target/debug/libm.rmeta b/codeless/server/target/debug/libm.rmeta new file mode 100644 index 000000000..e69de29bb -- cgit v1.2.3