From f3f2f1ceccb5a363a48c74a527b2d907743823d7 Mon Sep 17 00:00:00 2001 From: silvanshade Date: Sun, 29 May 2022 18:25:27 -0600 Subject: [PATCH] Use AsyncIterator for input; works in all browsers --- README.md | 2 -- app/src/index.ts | 70 ++++++++++++++++++++++++++++++++--------------- server/Cargo.toml | 4 ++- server/src/lib.rs | 24 ++++++++++++---- 4 files changed, 70 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 701782c..7a2a209 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,6 @@ ## Building -NOTE: this example uses [ReadableByteStreamController](https://developer.mozilla.org/en-US/docs/Web/API/ReadableByteStreamController#browser_compatibility) which, as of writing this, is only supported yet on chromium based browsers. - ```sh cargo install wasm-bindgen-cli --version 0.2.80 cd server diff --git a/app/src/index.ts b/app/src/index.ts index e79636b..1603b0f 100644 --- a/app/src/index.ts +++ b/app/src/index.ts @@ -1,30 +1,56 @@ class LspStdin { - static create(stdin: HTMLTextAreaElement, sendButton: HTMLButtonElement): ReadableStream { + static async *create( + stdin: HTMLTextAreaElement, + sendButton: HTMLButtonElement + ): AsyncGenerator { const encoder = new TextEncoder(); - return new ReadableStream({ - type: "bytes" as any, - async start(controller) { - while (true) { - await new Promise((resolve) => { - sendButton.addEventListener( - "click", - () => { - const payload = stdin.value; - const message = `Content-Length: ${payload.length}\r\n\r\n${payload}`; - const bytes = encoder.encode(message); - controller.enqueue(bytes); - stdin.value = ""; - resolve(); - }, - { once: true } - ); - }); - } - }, - }); + while (true) { + const bytes = await new Promise((resolve) => { + sendButton.addEventListener( + "click", + () => { + const payload = stdin.value; + const message = `Content-Length: ${payload.length}\r\n\r\n${payload}`; + stdin.value = ""; + resolve(encoder.encode(message)); + }, + { once: true } + ); + }); + yield bytes; + } } } +// NOTE: unused ReadableByteStream based implementation. See comments in server/src/lib.rs. +// +// class LspStdin { +// static create(stdin: HTMLTextAreaElement, sendButton: HTMLButtonElement): ReadableStream { +// const encoder = new TextEncoder(); +// return new ReadableStream({ +// type: "bytes" as any, +// async start(controller) { +// while (true) { +// await new Promise((resolve) => { +// sendButton.addEventListener( +// "click", +// () => { +// const payload = stdin.value; +// const message = `Content-Length: ${payload.length}\r\n\r\n${payload}`; +// const bytes = encoder.encode(message); +// controller.enqueue(bytes); +// stdin.value = ""; +// resolve(); +// }, +// { once: true } +// ); +// }); +// } +// }, +// }); +// } +// } + class LspStdout { static create(stdout: HTMLTextAreaElement): WritableStream { const decoder = new TextDecoder(); diff --git a/server/Cargo.toml b/server/Cargo.toml index c651e46..1996a79 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -11,9 +11,11 @@ crate-type = ["cdylib", "rlib"] [dependencies] console_error_panic_hook = "0.1.7" +futures = "0.3.21" +js-sys = "0.3.57" tower-lsp = { version = "0.17.0", default-features = false } wasm-bindgen = "0.2.80" -wasm-bindgen-futures = "0.4.30" +wasm-bindgen-futures = { version = "0.4.30", features = ["futures-core-03-stream"] } wasm-streams = "0.2.3" web-sys = { version = "0.3.57", features = [ "console", "ReadableStream", "WritableStream" ] } diff --git a/server/src/lib.rs b/server/src/lib.rs index ad5d476..077ea74 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -1,7 +1,10 @@ #![cfg(web_sys_unstable_apis)] +use futures::stream::TryStreamExt; +use js_sys::Uint8Array; use tower_lsp::{jsonrpc, lsp_types::*, LanguageServer, LspService, Server}; use wasm_bindgen::{prelude::*, JsCast}; +use wasm_bindgen_futures::stream::JsStream; struct LspServer { } @@ -21,16 +24,27 @@ impl LanguageServer for LspServer { } } +// NOTE: we don't use web_sys::ReadableStream for input here because on the +// browser side we need to use a ReadableByteStreamController to construct it +// and so far only Chromium-based browsers support that functionality. + +// NOTE: input needs to be an AsyncIterator specifically #[wasm_bindgen] -// NOTE: input needs to be a ReadableByteStream specifically -pub async fn serve(input: web_sys::ReadableStream, output: web_sys::WritableStream) -> Result<(), JsValue> { +pub async fn serve(input: js_sys::AsyncIterator, output: web_sys::WritableStream) -> Result<(), JsValue> { console_error_panic_hook::set_once(); web_sys::console::log_1(&"server::serve".into()); - let input = JsCast::unchecked_into::(input); - let input = wasm_streams::ReadableStream::from_raw(input); - let input = input.try_into_async_read().map_err(|err| err.0)?; + let input = JsStream::from(input); + let input = input + .map_ok(|value| { + value + .dyn_into::() + .expect("could not cast stream item to Uint8Array") + .to_vec() + }) + .map_err(|_err| std::io::Error::from(std::io::ErrorKind::Other)) + .into_async_read(); let output = JsCast::unchecked_into::(output); let output = wasm_streams::WritableStream::from_raw(output);