lsp_web/
lib.rs

1#![doc(
2    html_logo_url = "https://ajuvercr.github.io/semantic-web-lsp/assets/icons/favicon.png",
3    html_favicon_url = "https://ajuvercr.github.io/semantic-web-lsp/assets/icons/favicon.ico"
4)]
5mod client;
6mod fetch;
7
8use std::io::Write;
9
10use bevy_ecs::{system::Resource, world::World};
11use client::{WebClient, WebFs};
12use futures::{channel::mpsc::unbounded, stream::TryStreamExt, StreamExt};
13use lsp_core::prelude::*;
14use lsp_types::SemanticTokenType;
15use tower_lsp::{LspService, Server};
16use tracing::level_filters::LevelFilter;
17use wasm_bindgen::{prelude::*, JsCast};
18use wasm_bindgen_futures::stream::JsStream;
19
20#[wasm_bindgen]
21extern "C" {
22    #[wasm_bindgen()]
23    pub fn info(string: &str);
24    pub fn debug(string: &str);
25}
26
27struct LogItWriter;
28impl LogItWriter {
29    fn new() -> Self {
30        debug("building self");
31        LogItWriter
32    }
33}
34impl Write for LogItWriter {
35    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
36        match std::str::from_utf8(buf) {
37            Ok(st) => {
38                debug(st);
39            }
40            Err(e) => debug(&format!("Invalid string logged {:?}", e)),
41        }
42
43        Ok(buf.len())
44    }
45
46    fn flush(&mut self) -> std::io::Result<()> {
47        Ok(())
48    }
49}
50
51fn setup_global_subscriber() {
52    use tracing_subscriber::prelude::*;
53
54    let fmt_layer = tracing_subscriber::fmt::layer()
55        .pretty()
56        .with_ansi(false)
57        .without_time() // std::time is not available in browsers
58        .with_writer(std::sync::Mutex::new(LogItWriter::new()))
59        .with_filter(LevelFilter::DEBUG);
60
61    tracing_subscriber::registry().with(fmt_layer).init();
62}
63
64fn setup_world<C: Client + ClientSync + Resource + Clone>(
65    client: C,
66    tower_client: &tower_lsp::Client,
67) -> (CommandSender, Vec<SemanticTokenType>) {
68    let mut world = World::new();
69
70    setup_schedule_labels::<C>(&mut world);
71
72    let (publisher, mut rx) = DiagnosticPublisher::new();
73    world.insert_resource(publisher);
74
75    let c = client.clone();
76    client.spawn(async move {
77        while let Some(x) = rx.next().await {
78            c.publish_diagnostics(x.uri, x.diagnostics, x.version).await;
79        }
80    });
81
82    lang_turtle::setup_world(&mut world);
83    lang_jsonld::setup_world(&mut world);
84    lang_sparql::setup_world(&mut world);
85
86    let (tx, mut rx) = unbounded();
87    let sender = CommandSender(tx);
88    world.insert_resource(sender.clone());
89    world.insert_resource(client.clone());
90    world.insert_resource(WebFs::new(tower_client));
91
92    let r = world.resource::<SemanticTokensDict>();
93    let mut semantic_tokens: Vec<_> = (0..r.0.len()).map(|_| SemanticTokenType::KEYWORD).collect();
94    r.0.iter()
95        .for_each(|(k, v)| semantic_tokens[*v] = k.clone());
96
97    client.spawn(async move {
98        while let Some(mut x) = rx.next().await {
99            world.commands().append(&mut x);
100            world.flush_commands();
101        }
102    });
103
104    (sender, semantic_tokens)
105}
106
107#[wasm_bindgen]
108pub struct ServerConfig {
109    into_server: js_sys::AsyncIterator,
110    from_server: web_sys::WritableStream,
111}
112
113#[wasm_bindgen]
114impl ServerConfig {
115    #[wasm_bindgen(constructor)]
116    pub fn new(into_server: js_sys::AsyncIterator, from_server: web_sys::WritableStream) -> Self {
117        Self {
118            into_server,
119            from_server,
120        }
121    }
122}
123
124// NOTE: we don't use web_sys::ReadableStream for input here because on the
125// browser side we need to use a ReadableByteStreamController to construct it
126// and so far only Chromium-based browsers support that functionality.
127
128// NOTE: input needs to be an AsyncIterator<Uint8Array, never, void> specifically
129#[wasm_bindgen]
130pub async fn serve(config: ServerConfig) -> Result<(), JsValue> {
131    console_error_panic_hook::set_once();
132
133    web_sys::console::log_1(&"server::serve".into());
134
135    setup_global_subscriber();
136
137    info("string string string");
138
139    let ServerConfig {
140        into_server,
141        from_server,
142    } = config;
143
144    let input = JsStream::from(into_server);
145    let input = input
146        .map_ok(|value| {
147            value
148                .dyn_into::<js_sys::Uint8Array>()
149                .expect("could not cast stream item to Uint8Array")
150                .to_vec()
151        })
152        .map_err(|_err| std::io::Error::from(std::io::ErrorKind::Other))
153        .into_async_read();
154
155    let output = JsCast::unchecked_into::<wasm_streams::writable::sys::WritableStream>(from_server);
156    let output = wasm_streams::WritableStream::from_raw(output);
157    let output = output.try_into_async_write().map_err(|err| err.0)?;
158
159    let (service, socket) = LspService::build(|client| {
160        let (sender, rt) = setup_world(WebClient::new(client.clone()), &client);
161        Backend::new(sender, client, rt)
162    })
163    .finish();
164
165    debug("Testing logit, I'm serve 1");
166    Server::new(input, output, socket).serve(service).await;
167
168    Ok(())
169}
170
171#[wasm_bindgen]
172pub async fn serve2(config: ServerConfig) -> Result<(), JsValue> {
173    console_error_panic_hook::set_once();
174
175    web_sys::console::log_1(&"server::serve".into());
176
177    setup_global_subscriber();
178
179    tracing::info!("Hallo I'm here!");
180
181    let ServerConfig {
182        into_server,
183        from_server,
184    } = config;
185
186    let input = JsStream::from(into_server);
187    let input = input
188        .map_ok(|value| {
189            value
190                .dyn_into::<js_sys::Uint8Array>()
191                .expect("could not cast stream item to Uint8Array")
192                .to_vec()
193        })
194        .map_err(|_err| std::io::Error::from(std::io::ErrorKind::Other))
195        .into_async_read();
196
197    let output = JsCast::unchecked_into::<wasm_streams::writable::sys::WritableStream>(from_server);
198    let output = wasm_streams::WritableStream::from_raw(output);
199    let output = output.try_into_async_write().map_err(|err| err.0)?;
200    let (service, socket) = LspService::build(|client| {
201        let (sender, rt) = setup_world(WebClient::new(client.clone()), &client);
202        Backend::new(sender, client, rt)
203    })
204    .finish();
205    debug("Testing logit, I'm serve 2");
206
207    Server::new(input, output, socket).serve(service).await;
208
209    Ok(())
210}