Skip to main content

qlue_ls/
lib.rs

1//! qlue-ls language server library.
2//!
3//! This crate provides the core language server implementation for SPARQL,
4//! usable from both native and WASM targets.
5//!
6//! # Native Usage
7//!
8//! For native builds (including tests), use the re-exported server types:
9//! - [`Server`] (or [`LspServer`]): The main server struct
10//! - [`handle_message`] (or [`handle_lsp_message`]): Process incoming LSP messages
11//! - [`format_raw`]: Format SPARQL queries directly
12//! - [`format_with_settings`]: Format with custom settings
13//!
14//! # WASM Usage
15//!
16//! For WASM builds, additional functions are available:
17//! - [`init_language_server`]: Creates a new server instance with a Web Streams writer
18//! - [`listen`]: Main event loop using Web Streams API for I/O
19//!
20//! # Related Modules
21//!
22//! - `server`: Core server implementation shared across all targets
23//! - `main.rs` (native only): CLI entry point using stdio
24
25mod server;
26mod sparql;
27
28// Re-export core server types for all targets (used by tests and native builds)
29pub use crate::server::configuration::FormatSettings;
30pub use crate::server::message_handler::formatting::{format_raw, format_with_settings};
31pub use crate::server::{handle_message, Server};
32
33// Aliases for more descriptive names (for external consumers)
34pub use crate::server::{handle_message as handle_lsp_message, Server as LspServer};
35
36// WASM-specific imports and exports
37#[cfg(target_family = "wasm")]
38mod wasm {
39    use super::server::{Server, handle_message};
40    use futures::lock::Mutex;
41    use log::error;
42    use std::panic;
43    use std::rc::Rc;
44    use wasm_bindgen::prelude::*;
45    use wasm_bindgen_futures::JsFuture;
46    use web_sys::js_sys;
47
48    fn send_message(writer: &web_sys::WritableStreamDefaultWriter, message: String) {
49        let _future = JsFuture::from(writer.write_with_chunk(&message.into()));
50    }
51
52    #[wasm_bindgen]
53    pub fn init_language_server(writer: web_sys::WritableStreamDefaultWriter) -> Server {
54        wasm_logger::init(wasm_logger::Config::default());
55        panic::set_hook(Box::new(|info| {
56            let msg = info.to_string();
57            web_sys::console::error_1(&msg.into());
58            let _ = js_sys::Function::new_with_args("msg", "self.postMessage({type:'crash'});")
59                .call0(&JsValue::NULL);
60        }));
61        Server::new(move |message| send_message(&writer, message))
62    }
63
64    async fn read_message(
65        reader: &web_sys::ReadableStreamDefaultReader,
66    ) -> Result<(String, bool), String> {
67        match JsFuture::from(reader.read()).await {
68            Ok(js_object) => {
69                let value = js_sys::Reflect::get(&js_object, &"value".into())
70                    .map_err(|_| "\"value\" property not present in message")?
71                    .as_string()
72                    .ok_or("\"value\" is not a string")?;
73                let done = js_sys::Reflect::get(&js_object, &"done".into())
74                    .map_err(|_| "\"done\" property not present in message")?
75                    .as_bool()
76                    .ok_or("\"done\" is not a boolean")?;
77                Ok((value, done))
78            }
79            Err(_) => Err("Error while reading from input-stream".to_string()),
80        }
81    }
82
83    #[wasm_bindgen]
84    pub async fn listen(server: Server, reader: web_sys::ReadableStreamDefaultReader) {
85        let server_rc = Rc::new(Mutex::new(server));
86        loop {
87            match read_message(&reader).await {
88                Ok((value, done)) => {
89                    handle_message(server_rc.clone(), value).await;
90                    if done {
91                        break;
92                    }
93                }
94                Err(e) => error!("{}", e),
95            }
96        }
97    }
98}
99
100#[cfg(target_family = "wasm")]
101pub use wasm::*;