Use AsyncIterator for input; works in all browsers

This commit is contained in:
silvanshade 2022-05-29 18:25:27 -06:00
parent 95fd05ee6b
commit f3f2f1cecc
No known key found for this signature in database
4 changed files with 70 additions and 30 deletions

View file

@ -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

View file

@ -1,30 +1,56 @@
class LspStdin {
static create(stdin: HTMLTextAreaElement, sendButton: HTMLButtonElement): ReadableStream {
static async *create(
stdin: HTMLTextAreaElement,
sendButton: HTMLButtonElement
): AsyncGenerator<Uint8Array, never, void> {
const encoder = new TextEncoder();
return new ReadableStream({
type: "bytes" as any,
async start(controller) {
while (true) {
await new Promise<void>((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<Uint8Array>((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<void>((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();

View file

@ -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" ] }

View file

@ -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<Uint8Array> 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::<wasm_streams::readable::sys::ReadableStream>(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::<Uint8Array>()
.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::<wasm_streams::writable::sys::WritableStream>(output);
let output = wasm_streams::WritableStream::from_raw(output);