nushell on your web browser
nushell
wasm
terminal
1use futures::stream::AbortHandle;
2use nu_protocol::Signal;
3use rust_embed::RustEmbed;
4use std::{
5 collections::HashMap,
6 sync::{
7 Arc, Mutex, OnceLock, RwLock,
8 atomic::{AtomicUsize, Ordering},
9 },
10 time::{Duration, SystemTime, UNIX_EPOCH},
11};
12use vfs::{EmbeddedFS, OverlayFS, VfsPath};
13use wasm_bindgen::prelude::*;
14
15use crate::memory_fs::MemoryFS;
16
17static ROOT: OnceLock<Arc<VfsPath>> = OnceLock::new();
18
19fn init_vfs() -> Arc<VfsPath> {
20 let memory_fs = VfsPath::new(MemoryFS::new());
21 let embedded_fs = VfsPath::new(EmbeddedFS::<EmbeddedFiles>::new());
22 let overlaid_fs = VfsPath::new(OverlayFS::new(&[memory_fs, embedded_fs]));
23 Arc::new(overlaid_fs)
24}
25
26pub fn get_vfs() -> Arc<VfsPath> {
27 ROOT.get_or_init(init_vfs).clone()
28}
29
30#[derive(RustEmbed, Debug)]
31#[folder = "embedded/"]
32#[exclude = ".gitkeep"]
33pub struct EmbeddedFiles;
34
35static PWD: OnceLock<RwLock<Arc<VfsPath>>> = OnceLock::new();
36
37pub fn get_pwd() -> Arc<VfsPath> {
38 PWD.get_or_init(|| RwLock::new(get_vfs()))
39 .read()
40 .unwrap()
41 .clone()
42}
43
44pub fn set_pwd(path: Arc<VfsPath>) {
45 *PWD.get_or_init(|| RwLock::new(get_vfs())).write().unwrap() = path;
46}
47
48pub struct TaskInfo {
49 pub id: usize,
50 pub description: String,
51 pub handle: AbortHandle,
52}
53
54pub struct CallbackWrapper(js_sys::Function);
55unsafe impl Send for CallbackWrapper {}
56unsafe impl Sync for CallbackWrapper {}
57
58static NEXT_TASK_ID: AtomicUsize = AtomicUsize::new(1);
59static TASK_REGISTRY: OnceLock<Mutex<HashMap<usize, TaskInfo>>> = OnceLock::new();
60static TASK_CALLBACK: OnceLock<Mutex<Option<CallbackWrapper>>> = OnceLock::new();
61
62pub fn get_registry() -> &'static Mutex<HashMap<usize, TaskInfo>> {
63 TASK_REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
64}
65
66#[wasm_bindgen]
67pub fn register_task_count_callback(f: js_sys::Function) {
68 let _ = TASK_CALLBACK.get_or_init(|| Mutex::new(None));
69 if let Ok(mut guard) = TASK_CALLBACK.get().unwrap().lock() {
70 *guard = Some(CallbackWrapper(f));
71 }
72}
73
74pub fn notify_task_count() {
75 if let Some(mutex) = TASK_CALLBACK.get() {
76 if let Ok(guard) = mutex.lock() {
77 if let Some(cb) = guard.as_ref() {
78 let count = if let Ok(reg) = get_registry().lock() {
79 reg.len()
80 } else {
81 0
82 };
83 let this = JsValue::NULL;
84 let arg = JsValue::from(count as u32);
85 let _ = cb.0.call1(&this, &arg);
86 }
87 }
88 }
89}
90
91pub fn register_task(description: String, handle: AbortHandle) -> usize {
92 let id = NEXT_TASK_ID.fetch_add(1, Ordering::Relaxed);
93 if let Ok(mut reg) = get_registry().lock() {
94 reg.insert(
95 id,
96 TaskInfo {
97 id,
98 description,
99 handle,
100 },
101 );
102 }
103 notify_task_count();
104 id
105}
106
107pub fn remove_task(id: usize) {
108 if let Ok(mut reg) = get_registry().lock() {
109 reg.remove(&id);
110 }
111 notify_task_count();
112}
113
114pub fn get_all_tasks() -> Vec<(usize, String)> {
115 if let Ok(reg) = get_registry().lock() {
116 reg.values()
117 .map(|t| (t.id, t.description.clone()))
118 .collect()
119 } else {
120 vec![]
121 }
122}
123
124pub fn kill_task_by_id(id: usize) -> bool {
125 if let Ok(mut reg) = get_registry().lock() {
126 if let Some(task) = reg.remove(&id) {
127 task.handle.abort();
128 drop(reg);
129 notify_task_count();
130 return true;
131 }
132 }
133 false
134}
135
136// static PENDING_DELTAS: OnceLock<Mutex<Vec<StateDelta>>> = OnceLock::new();
137
138// pub fn queue_delta(delta: StateDelta) {
139// let _ = PENDING_DELTAS.get_or_init(|| Mutex::new(Vec::new()));
140// if let Ok(mut guard) = PENDING_DELTAS.get().unwrap().lock() {
141// guard.push(delta);
142// }
143// }
144
145// pub fn apply_pending_deltas(engine_state: &mut EngineState) -> Result<(), ShellError> {
146// if let Some(mutex) = PENDING_DELTAS.get() {
147// if let Ok(mut guard) = mutex.lock() {
148// for delta in guard.drain(..) {
149// engine_state.merge_delta(delta)?;
150// }
151// }
152// }
153// Ok(())
154// }
155
156pub static CONSOLE_CALLBACK: OnceLock<Mutex<Option<CallbackWrapper>>> = OnceLock::new();
157
158#[wasm_bindgen]
159pub fn register_console_callback(f: js_sys::Function) {
160 let _ = CONSOLE_CALLBACK.get_or_init(|| Mutex::new(None));
161 if let Ok(mut guard) = CONSOLE_CALLBACK.get().unwrap().lock() {
162 *guard = Some(CallbackWrapper(f));
163 }
164}
165
166pub fn print_to_console(msg: &str, is_cmd: bool) {
167 if let Some(mutex) = CONSOLE_CALLBACK.get() {
168 if let Ok(guard) = mutex.lock() {
169 if let Some(cb) = guard.as_ref() {
170 let this = JsValue::NULL;
171 let arg = JsValue::from_str(msg);
172 let arg2 = JsValue::from_bool(is_cmd);
173 let _ = cb.0.call2(&this, &arg, &arg2);
174 }
175 }
176 }
177}
178
179pub fn current_time() -> Option<SystemTime> {
180 UNIX_EPOCH.checked_add(Duration::from_millis(js_sys::Date::now() as u64))
181}
182
183use js_sys::Int32Array;
184use std::cell::RefCell;
185
186// We use thread_local storage because the Wasm worker is single-threaded.
187// This holds the reference to the SharedArrayBuffer view passed from JS.
188thread_local! {
189 pub static INTERRUPT_BUFFER: RefCell<Option<Int32Array>> = RefCell::new(None);
190}
191
192#[wasm_bindgen]
193pub fn set_interrupt_buffer(buffer: Int32Array) {
194 INTERRUPT_BUFFER.with(|b| {
195 *b.borrow_mut() = Some(buffer);
196 });
197}
198
199pub fn check_interrupt() -> bool {
200 INTERRUPT_BUFFER.with(|b| {
201 if let Some(buffer) = b.borrow().as_ref() {
202 match js_sys::Atomics::load(buffer, 0) {
203 Ok(1) => true,
204 _ => false,
205 }
206 } else {
207 false
208 }
209 })
210}
211
212pub fn set_interrupt(value: bool) {
213 INTERRUPT_BUFFER.with(|b| {
214 if let Some(buffer) = b.borrow().as_ref() {
215 let _ = js_sys::Atomics::store(buffer, 0, value as i32);
216 }
217 });
218}
219
220pub struct InterruptBool;
221
222impl Signal for InterruptBool {
223 #[inline]
224 fn get(&self) -> bool {
225 check_interrupt()
226 }
227 #[inline]
228 fn set(&self, value: bool) {
229 set_interrupt(value);
230 }
231}