diff --git a/src/langserver/completion.rs b/src/langserver/completion.rs index 2432dfd8..71da20c1 100644 --- a/src/langserver/completion.rs +++ b/src/langserver/completion.rs @@ -8,7 +8,7 @@ use dm::ast::PathOp; use dm::annotation::Annotation; use dm::objtree::{TypeRef, TypeVar, TypeProc, ProcValue}; -use {Engine, Span, io, is_constructor_name, ignore_root}; +use {Engine, Span, is_constructor_name, ignore_root}; use symbol_search::contains; pub fn item_var(ty: TypeRef, name: &str, var: &TypeVar) -> CompletionItem { @@ -126,7 +126,7 @@ pub fn combine_tree_path<'a, I>(iter: &I, mut absolute: bool, mut parts: &'a [St prefix_parts.iter().chain(parts).map(|x| &**x) } -impl<'a, W: io::ResponseWrite> Engine<'a, W> { +impl<'a> Engine<'a> { pub fn follow_type_path<'b, I>(&'b self, iter: &I, mut parts: &'b [(PathOp, String)]) -> Option> where I: Iterator + Clone, diff --git a/src/langserver/io/mod.rs b/src/langserver/io/mod.rs index bd4dc590..ad3221e7 100644 --- a/src/langserver/io/mod.rs +++ b/src/langserver/io/mod.rs @@ -1,15 +1,59 @@ -//! Pluggable backends for input/output handling. +//! I/O backend for standard targets. +//! +//! JSON-RPC over stdin/stdout with Content-Length headers. -pub trait RequestRead { - fn read(&self) -> Option; +use std::io::{self, Read, Write}; + +pub fn run_forever(mut f: F) -> ! { + loop { + let message = read().expect("request bad read"); + f(&message); + } } -pub trait ResponseWrite { - fn write(&self, output: String); +fn read() -> Option { + macro_rules! check { + ($exp:expr) => { + match $exp { + Ok(x) => x, + Err(e) => { + eprintln!("{:?}", e); + return None; + } + } + }; + } + + // read the content-length + let mut buffer = String::new(); + check!(io::stdin().read_line(&mut buffer)); + if buffer.is_empty() { + return None; + } + let size = { + let parts: Vec<&str> = buffer.split(' ').collect(); + if parts.len() != 2 { + return None; + } + if !parts[0].eq_ignore_ascii_case("content-length:") { + return None; + } + check!(usize::from_str_radix(parts[1].trim(), 10)) + }; + + // skip blank line + buffer.clear(); + check!(io::stdin().read_line(&mut buffer)); + + // read content + let mut content = vec![0; size]; + check!(io::stdin().read_exact(&mut content)); + Some(check!(String::from_utf8(content))) } -#[cfg_attr(target_arch="wasm32", path="wasm.rs")] -#[cfg_attr(not(target_arch="wasm32"), path="stdio.rs")] -mod system; - -pub use self::system::*; +pub fn write(output: String) { + let stdout = io::stdout(); + let mut stdout_lock = stdout.lock(); + write!(stdout_lock, "Content-Length: {}\r\n\r\n{}", output.len(), output).unwrap(); + stdout_lock.flush().unwrap(); +} diff --git a/src/langserver/io/stdio.rs b/src/langserver/io/stdio.rs deleted file mode 100644 index affb5ced..00000000 --- a/src/langserver/io/stdio.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! I/O backend for standard targets. -//! -//! JSON-RPC over stdin/stdout with Content-Length headers. - -use Engine; -use io::{RequestRead, ResponseWrite}; -use std::io::{self, Read, Write}; - -pub fn io_main() { - let stdio = StdIo; - let context = Default::default(); - let mut engine = Engine::new(&stdio, &context); - loop { - let message = stdio.read().expect("request bad read"); - engine.handle_input(&message); - } -} - -struct StdIo; - -impl RequestRead for StdIo { - fn read(&self) -> Option { - macro_rules! check { - ($exp:expr) => { - match $exp { - Ok(x) => x, - Err(e) => { - eprintln!("{:?}", e); - return None; - } - } - }; - } - - // read the content-length - let mut buffer = String::new(); - check!(io::stdin().read_line(&mut buffer)); - if buffer.is_empty() { - return None; - } - let size = { - let parts: Vec<&str> = buffer.split(' ').collect(); - if parts.len() != 2 { - return None; - } - if !parts[0].eq_ignore_ascii_case("content-length:") { - return None; - } - check!(usize::from_str_radix(parts[1].trim(), 10)) - }; - - // skip blank line - buffer.clear(); - check!(io::stdin().read_line(&mut buffer)); - - // read content - let mut content = vec![0; size]; - check!(io::stdin().read_exact(&mut content)); - Some(check!(String::from_utf8(content))) - } -} - -impl ResponseWrite for StdIo { - fn write(&self, output: String) { - let stdout = io::stdout(); - let mut stdout_lock = stdout.lock(); - write!(stdout_lock, "Content-Length: {}\r\n\r\n{}", output.len(), output).unwrap(); - stdout_lock.flush().unwrap(); - } -} diff --git a/src/langserver/io/wasm.rs b/src/langserver/io/wasm.rs deleted file mode 100644 index b2968135..00000000 --- a/src/langserver/io/wasm.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! I/O backend for WebAssembly target. -//! -//! `main()` creates an engine that will respond to calls to `handle_input()`, -//! which will then call `handle_output()` with output messages. -#![allow(unsafe_code)] - -use Engine; -use super::ResponseWrite; - -pub fn io_main() { - let wasmio = Box::leak(Box::new(WasmIo)); - let context = Box::leak(Box::new(Default::default())); - let engine = Box::new(Engine::new(wasmio, context)); - unsafe { - ENGINE_PTR = Box::into_raw(engine); - return_into_js(); - } -} - -static mut ENGINE_PTR: *mut Engine = 0 as *mut Engine; - -#[no_mangle] -pub unsafe extern fn handle_input(ptr: *const u8, len: usize) { - assert!(!ENGINE_PTR.is_null()); - let engine = &mut *ENGINE_PTR; - let slice = std::slice::from_raw_parts(ptr, len); - let text = std::str::from_utf8(slice).expect("input is not utf-8"); - engine.handle_input(text); -} - -struct WasmIo; - -impl ResponseWrite for WasmIo { - fn write(&self, output: String) { - unsafe { - handle_output(output.as_ptr(), output.len()); - } - } -} - -extern { - fn handle_output(ptr: *const u8, len: usize); - fn return_into_js() -> !; -} diff --git a/src/langserver/macros.rs b/src/langserver/macros.rs index 56362ab8..2502e023 100644 --- a/src/langserver/macros.rs +++ b/src/langserver/macros.rs @@ -7,7 +7,7 @@ pub mod all_notifications { macro_rules! handle_method_call { ($(on $what:ident(&mut $self:ident, $p:pat) $b:block)*) => { - impl<'a, W: io::ResponseWrite> Engine<'a, W> { + impl<'a> Engine<'a> { fn handle_method_call(&mut self, call: jsonrpc::MethodCall) -> Result { use langserver::request::*; @@ -57,7 +57,7 @@ macro_rules! handle_method_call { macro_rules! handle_notification { ($(on $what:ident(&mut $self:ident, $p:pat) $b:block)*) => { - impl<'a, W: io::ResponseWrite> Engine<'a, W> { + impl<'a> Engine<'a> { fn handle_notification(&mut self, notification: jsonrpc::Notification) -> Result<(), jsonrpc::Error> { use macros::all_notifications::*; diff --git a/src/langserver/main.rs b/src/langserver/main.rs index 1ecf0a03..bc8a75ca 100644 --- a/src/langserver/main.rs +++ b/src/langserver/main.rs @@ -62,7 +62,9 @@ fn main() { Err(e) => eprintln!("dir check failure: {}", e), } - io::io_main(); + let context = dm::Context::default(); + let mut engine = Engine::new(&context); + io::run_forever(|message| engine.handle_input(message)); } const VERSION: Option = Some(jsonrpc::Version::V2); @@ -116,8 +118,7 @@ impl ClientCaps { } } -struct Engine<'a, W: 'a> { - write: &'a W, +struct Engine<'a> { docs: document::DocumentStore, status: InitStatus, @@ -135,10 +136,9 @@ struct Engine<'a, W: 'a> { client_caps: ClientCaps, } -impl<'a, W: io::ResponseWrite> Engine<'a, W> { - fn new(write: &'a W, context: &'a dm::Context) -> Self { +impl<'a> Engine<'a> { + fn new(context: &'a dm::Context) -> Self { Engine { - write, docs: Default::default(), status: InitStatus::Starting, @@ -165,7 +165,7 @@ impl<'a, W: io::ResponseWrite> Engine<'a, W> { T: langserver::notification::Notification, T::Params: serde::Serialize, { - issue_notification::<_, T>(self.write, params) + issue_notification::(params) } fn show_message(&mut self, typ: MessageType, message: S) where @@ -512,8 +512,7 @@ impl<'a, W: io::ResponseWrite> Engine<'a, W> { } } - issue_notification::<_, langserver::notification::PublishDiagnostics>( - self.write, + issue_notification::( langserver::PublishDiagnosticsParams { uri: url.to_owned(), diagnostics, @@ -677,7 +676,7 @@ impl<'a, W: io::ResponseWrite> Engine<'a, W> { _ => Response::Batch(outputs), }; - self.write.write(serde_json::to_string(&response).expect("response bad to_string")); + io::write(serde_json::to_string(&response).expect("response bad to_string")); } fn handle_call(&mut self, call: Call) -> Option { @@ -1707,7 +1706,7 @@ fn span_to_range(range: ::std::ops::Range) -> langserver::Range { langserver::Range::new(location_to_position(range.start), location_to_position(range.end)) } -fn issue_notification(write: &W, params: T::Params) +fn issue_notification(params: T::Params) where T: langserver::notification::Notification, T::Params: serde::Serialize, @@ -1718,7 +1717,7 @@ where method: T::METHOD.to_owned(), params: value_to_params(params), })); - write.write(serde_json::to_string(&request).expect("notification bad to_string")) + io::write(serde_json::to_string(&request).expect("notification bad to_string")) } fn component_to_source(component: dm::Component) -> Option {