use eure::query::TextFile;
use js_sys::Array;
use serde::Serialize;
use serde_json::Value;
use wasm_bindgen::prelude::*;
use crate::uri_utils::{text_file_to_uri, uri_to_text_file};
use crate::{CoreRequestId, LspCore, LspOutput};
#[wasm_bindgen(getter_with_clone)]
pub struct CacheKeyInfo {
pub url: String,
pub hash: String,
pub host: String,
pub filename: String,
pub cache_path: String,
}
impl From<eure_env::cache::CacheKeyInfo> for CacheKeyInfo {
fn from(info: eure_env::cache::CacheKeyInfo) -> Self {
Self {
url: info.url,
hash: info.hash,
host: info.host,
filename: info.filename,
cache_path: info.cache_path,
}
}
}
#[derive(Clone)]
#[wasm_bindgen(getter_with_clone)]
pub struct ConditionalHeaders {
pub if_none_match: Option<String>,
pub if_modified_since: Option<String>,
}
impl From<eure_env::cache::ConditionalHeaders> for ConditionalHeaders {
fn from(headers: eure_env::cache::ConditionalHeaders) -> Self {
Self {
if_none_match: headers.if_none_match,
if_modified_since: headers.if_modified_since,
}
}
}
#[wasm_bindgen]
pub struct CacheAction {
action: CacheActionKind,
headers: Option<ConditionalHeaders>,
}
#[derive(Clone, Copy)]
#[wasm_bindgen]
pub enum CacheActionKind {
Fetch,
UseCached,
Revalidate,
}
#[wasm_bindgen]
impl CacheAction {
#[wasm_bindgen(getter)]
pub fn action(&self) -> CacheActionKind {
self.action
}
#[wasm_bindgen(getter)]
pub fn headers(&self) -> Option<ConditionalHeaders> {
self.headers.clone()
}
}
impl From<eure_env::cache::CacheAction> for CacheAction {
fn from(action: eure_env::cache::CacheAction) -> Self {
match action {
eure_env::cache::CacheAction::Fetch => Self {
action: CacheActionKind::Fetch,
headers: None,
},
eure_env::cache::CacheAction::UseCached => Self {
action: CacheActionKind::UseCached,
headers: None,
},
eure_env::cache::CacheAction::Revalidate { headers } => Self {
action: CacheActionKind::Revalidate,
headers: Some(headers.into()),
},
}
}
}
#[wasm_bindgen]
pub struct WasmCore {
core: LspCore,
outbox: Vec<Value>,
}
#[wasm_bindgen]
impl WasmCore {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
console_error_panic_hook::set_once();
Self {
core: LspCore::new(),
outbox: Vec::new(),
}
}
#[wasm_bindgen]
pub fn handle_message(&mut self, msg: JsValue) {
let msg: Value = match serde_wasm_bindgen::from_value(msg) {
Ok(v) => v,
Err(_) => return,
};
if let Some(id) = msg.get("id") {
if msg.get("method").is_some() {
let method = msg.get("method").and_then(|m| m.as_str()).unwrap_or("");
let params = msg.get("params").cloned().unwrap_or(Value::Null);
let core_id = CoreRequestId::from(id);
let (outputs, _effects) = self.core.handle_request(core_id, method, params);
self.process_outputs(outputs);
}
} else if msg.get("method").is_some() {
let method = msg.get("method").and_then(|m| m.as_str()).unwrap_or("");
let params = msg.get("params").cloned().unwrap_or(Value::Null);
let (outputs, _effects) = self.core.handle_notification(method, params);
self.process_outputs(outputs);
}
}
#[wasm_bindgen]
pub fn drain_outbox(&mut self) -> Array {
let serializer = serde_wasm_bindgen::Serializer::json_compatible();
let messages: Vec<JsValue> = self
.outbox
.drain(..)
.filter_map(|v| v.serialize(&serializer).ok())
.collect();
messages.into_iter().collect()
}
#[wasm_bindgen]
pub fn get_pending_text_files(&self) -> Array {
self.core
.pending_files()
.map(|file| JsValue::from_str(&text_file_to_uri(file)))
.collect()
}
#[wasm_bindgen]
pub fn get_pending_globs(&self) -> Array {
self.core
.pending_globs()
.map(|(id, glob)| {
let obj = js_sys::Object::new();
js_sys::Reflect::set(&obj, &"id".into(), &JsValue::from_str(id)).unwrap();
js_sys::Reflect::set(
&obj,
&"base_dir".into(),
&JsValue::from_str(&glob.base_dir.to_string_lossy()),
)
.unwrap();
js_sys::Reflect::set(&obj, &"pattern".into(), &JsValue::from_str(&glob.pattern))
.unwrap();
JsValue::from(obj)
})
.collect()
}
#[wasm_bindgen]
pub fn resolve_glob(&mut self, id: &str, files: Array) {
let text_files: Vec<TextFile> = files
.iter()
.filter_map(|v| v.as_string())
.filter_map(|uri| uri_to_text_file(&uri).ok())
.collect();
let (outputs, _effects) = self.core.resolve_glob(id, text_files);
self.process_outputs(outputs);
}
#[wasm_bindgen]
pub fn resolve_text_file(&mut self, uri: &str, content: Option<String>, error: Option<String>) {
let Ok(file) = uri_to_text_file(uri) else {
return;
};
let result = match (content, error) {
(Some(c), _) => Ok(c),
(_, Some(e)) => Err(e),
(None, None) => Err("File not found".to_string()),
};
let (outputs, _effects) = self.core.resolve_file(file, result);
self.process_outputs(outputs);
}
#[wasm_bindgen]
pub fn tick(&mut self) {
}
#[wasm_bindgen]
pub fn compute_cache_key(&self, url_str: &str) -> Option<CacheKeyInfo> {
use eure_env::cache::compute_cache_key;
let url = url::Url::parse(url_str).ok()?;
Some(compute_cache_key(&url).into())
}
#[wasm_bindgen]
pub fn check_cache_status(&self, meta_json: Option<String>, max_age_secs: u32) -> CacheAction {
use eure_env::cache::CacheMeta;
let Some(meta_json) = meta_json else {
return eure_env::cache::CacheAction::Fetch.into();
};
let Ok(meta) = serde_json::from_str::<CacheMeta>(&meta_json) else {
return eure_env::cache::CacheAction::Fetch.into();
};
meta.check_freshness(max_age_secs).into()
}
#[wasm_bindgen]
pub fn build_cache_meta(
&self,
url: &str,
etag: Option<String>,
last_modified: Option<String>,
content_hash: &str,
size_bytes: u32,
) -> String {
use eure_env::cache::CacheMeta;
let meta = CacheMeta::new(
url.to_string(),
etag,
last_modified,
content_hash.to_string(),
size_bytes as u64,
);
serde_json::to_string_pretty(&meta).unwrap_or_default()
}
#[wasm_bindgen]
pub fn compute_content_hash(&self, content: &str) -> String {
eure_env::cache::compute_content_hash(content)
}
}
impl WasmCore {
fn process_outputs(&mut self, outputs: Vec<LspOutput>) {
for output in outputs {
let json = match output {
LspOutput::Response { id, result } => match result {
Ok(value) => serde_json::json!({
"jsonrpc": "2.0",
"id": id,
"result": value
}),
Err(err) => serde_json::json!({
"jsonrpc": "2.0",
"id": id,
"error": {
"code": err.code,
"message": err.message
}
}),
},
LspOutput::Notification { method, params } => serde_json::json!({
"jsonrpc": "2.0",
"method": method,
"params": params
}),
};
self.outbox.push(json);
}
}
}
impl Default for WasmCore {
fn default() -> Self {
Self::new()
}
}