use std::sync::Arc;
use ls_types::Uri;
use wasm_bindgen::prelude::*;
use crate::core::LanguageServerCore;
use crate::semantic::analyzer::analyze_document;
use crate::semantic::types::{LiveMetadataSnapshot, SymbolOrigin, WorkspaceIndex};
use crate::wasm::dispatch;
use crate::wasm::host_data::{
HostPushedMetadata, HostPushedMetadataStore, HostPushedWorkspace, HostPushedWorkspaceStore,
};
use crate::wasm::notifier::{JsCallbackNotifier, JsCallbacks};
pub(crate) type WasmCore =
LanguageServerCore<JsCallbackNotifier, HostPushedWorkspace, HostPushedMetadata>;
#[wasm_bindgen]
pub struct WasmLanguageServer {
core: Arc<WasmCore>,
workspace: Arc<HostPushedWorkspaceStore>,
metadata: Arc<HostPushedMetadataStore>,
}
#[wasm_bindgen]
impl WasmLanguageServer {
#[wasm_bindgen(constructor)]
pub fn new(callbacks: JsValue) -> Result<WasmLanguageServer, JsValue> {
console_error_panic_hook::set_once();
let workspace = Arc::new(HostPushedWorkspaceStore::default());
let metadata = Arc::new(HostPushedMetadataStore::default());
let core = LanguageServerCore::new(
JsCallbackNotifier::new(JsCallbacks::from_object(&callbacks)?),
HostPushedWorkspace::new(Arc::clone(&workspace)),
HostPushedMetadata::new(Arc::clone(&metadata)),
);
Ok(WasmLanguageServer {
core: Arc::new(core),
workspace,
metadata,
})
}
#[wasm_bindgen(js_name = handleMessage)]
pub async fn handle_message(&self, json: String) -> Result<JsValue, JsValue> {
dispatch::handle_message(self.core.as_ref(), &json).await
}
#[wasm_bindgen(js_name = pushWorkspaceDocument)]
pub async fn push_workspace_document(&self, uri: String, text: String) -> Result<(), JsValue> {
let parsed = parse_uri(&uri)?;
let mut workspace = self.workspace.snapshot();
if let Some(analysis) = analyze_document(parsed.clone(), &text, SymbolOrigin::Local) {
workspace.documents.insert(parsed, Arc::new(analysis));
}
self.workspace.replace(workspace);
self.core.reload_workspace().await;
Ok(())
}
#[wasm_bindgen(js_name = dropWorkspaceDocument)]
pub async fn drop_workspace_document(&self, uri: String) -> Result<(), JsValue> {
let parsed = parse_uri(&uri)?;
let mut workspace = self.workspace.snapshot();
workspace.documents.remove(&parsed);
self.workspace.replace(workspace);
self.core.reload_workspace().await;
Ok(())
}
#[wasm_bindgen(js_name = replaceWorkspace)]
pub async fn replace_workspace(&self, documents: JsValue) -> Result<(), JsValue> {
let entries: Vec<DocumentEntry> = serde_wasm_bindgen::from_value(documents)
.map_err(|error| JsValue::from_str(&format!("invalid workspace payload: {error}")))?;
let mut workspace = WorkspaceIndex::default();
for DocumentEntry { uri, text } in entries {
let Ok(parsed) = uri.parse::<Uri>() else {
continue;
};
if let Some(analysis) = analyze_document(parsed.clone(), &text, SymbolOrigin::Local) {
workspace.documents.insert(parsed, Arc::new(analysis));
}
}
self.workspace.replace(workspace);
self.core.reload_workspace().await;
Ok(())
}
#[wasm_bindgen(js_name = setLiveMetadata)]
pub async fn set_live_metadata(&self, define_strings: JsValue) -> Result<(), JsValue> {
let strings: Vec<String> = serde_wasm_bindgen::from_value(define_strings)
.map_err(|error| JsValue::from_str(&format!("invalid metadata payload: {error}")))?;
let mut snapshot = LiveMetadataSnapshot::default();
for (index, define) in strings.into_iter().enumerate() {
let uri_string = format!("surrealdb:///metadata/{}.surql", index);
let Ok(uri) = uri_string.parse::<Uri>() else {
continue;
};
if let Some(analysis) = analyze_document(uri.clone(), &define, SymbolOrigin::Remote) {
snapshot.documents.insert(uri, Arc::new(analysis));
}
}
self.metadata.replace(snapshot.clone());
self.core.replace_live_metadata(snapshot).await;
Ok(())
}
}
#[derive(serde::Deserialize)]
struct DocumentEntry {
uri: String,
text: String,
}
fn parse_uri(uri: &str) -> Result<Uri, JsValue> {
uri.parse::<Uri>()
.map_err(|error| JsValue::from_str(&format!("invalid uri `{uri}`: {error}")))
}