1use async_lock::{RwLock, RwLockReadGuard, RwLockUpgradableReadGuard, RwLockWriteGuard};
2use futures::TryFutureExt;
3use js_sys::Promise;
4use nu_cmd_base::hook::eval_hook;
5use nu_cmd_extra::add_extra_command_context;
6use nu_cmd_lang::create_default_context;
7use nu_engine::{command_prelude::*, eval_block};
8use nu_parser::{FlatShape, flatten_block, parse};
9use nu_protocol::{
10 Config, ListStream, PipelineData, Signals, Span,
11 engine::{EngineState, Stack, StateWorkingSet},
12};
13use std::{
14 fmt::Write,
15 io::Cursor,
16 sync::{Arc, OnceLock},
17};
18use wasm_bindgen::prelude::*;
19use wasm_bindgen_futures::future_to_promise;
20
21pub mod cmd;
22pub mod completion;
23pub mod default_context;
24pub mod error;
25pub mod globals;
26pub mod highlight;
27pub mod memory_fs;
28
29use crate::{
30 cmd::{
31 Cd, Eval, Fetch, Glob, Job, JobKill, JobList, Ls, Mkdir, Mv, Open, Print, Pwd, Random, Rm,
32 Save, SourceFile, Sys,
33 },
34 default_context::add_shell_command_context,
35 globals::{InterruptBool, get_pwd, get_vfs, print_to_console, set_interrupt},
36};
37use error::CommandError;
38
39#[wasm_bindgen]
40extern "C" {
41 #[wasm_bindgen(js_namespace = console)]
42 fn error(msg: String);
43
44 type Error;
45
46 #[wasm_bindgen(constructor)]
47 fn new() -> Error;
48
49 #[wasm_bindgen(structural, method, getter)]
50 fn stack(error: &Error) -> String;
51}
52
53fn panic_hook(info: &std::panic::PanicHookInfo) {
54 let mut msg = info.to_string();
55
56 msg.push_str("\n\nStack:\n\n");
57 let e = Error::new();
58 let stack = e.stack();
59 msg.push_str(&stack);
60 msg.push_str("\n\n");
61
62 let _ = print_to_console(&msg, false);
63}
64
65static ENGINE_STATE: OnceLock<RwLock<EngineState>> = OnceLock::new();
66#[inline]
67async fn read_engine_state() -> RwLockReadGuard<'static, EngineState> {
68 unsafe { ENGINE_STATE.get().unwrap_unchecked() }
69 .read()
70 .await
71}
72#[inline]
73async fn write_engine_state() -> RwLockWriteGuard<'static, EngineState> {
74 unsafe { ENGINE_STATE.get().unwrap_unchecked() }
75 .write()
76 .await
77}
78
79static STACK: OnceLock<RwLock<Stack>> = OnceLock::new();
80#[inline]
81async fn read_stack() -> RwLockReadGuard<'static, Stack> {
82 unsafe { STACK.get().unwrap_unchecked() }.read().await
83}
84#[inline]
85async fn write_stack() -> RwLockWriteGuard<'static, Stack> {
86 unsafe { STACK.get().unwrap_unchecked() }.write().await
87}
88
89#[wasm_bindgen]
90pub fn init_engine() -> Promise {
91 std::panic::set_hook(Box::new(panic_hook));
92 future_to_promise(
93 init_engine_internal()
94 .map_ok(|_| JsValue::null())
95 .map_err(|s| JsValue::from_str(&s)),
96 )
97}
98
99async fn init_engine_internal() -> Result<(), String> {
100 let mut engine_state = create_default_context();
101 engine_state = add_shell_command_context(engine_state);
102 engine_state = add_extra_command_context(engine_state);
103
104 let mut working_set = StateWorkingSet::new(&engine_state);
105 let decls: [Box<dyn Command>; 18] = [
106 Box::new(Ls),
107 Box::new(Open),
108 Box::new(Save),
109 Box::new(Mkdir),
110 Box::new(Mv),
111 Box::new(Pwd),
112 Box::new(Cd),
113 Box::new(Rm),
114 Box::new(Fetch),
115 Box::new(SourceFile),
116 Box::new(Eval),
117 Box::new(Job),
118 Box::new(JobList),
119 Box::new(JobKill),
120 Box::new(Sys),
121 Box::new(Random),
122 Box::new(Print),
123 Box::new(Glob),
124 ];
125 for decl in decls {
126 working_set.add_decl(decl);
127 }
128 engine_state
129 .merge_delta(working_set.delta)
130 .map_err(CommandError::from)?;
131
132 let mut config = Config::default();
133 config.use_ansi_coloring = true.into();
134 config.show_banner = nu_protocol::BannerKind::Full;
135 config.hooks.display_output = Some("table".into_value(Span::unknown()));
136 config.table.show_empty = false;
137 engine_state.config = Arc::new(config);
138
139 engine_state.set_signals(Signals::new(Arc::new(InterruptBool)));
140
141 ENGINE_STATE
142 .set(RwLock::new(engine_state))
143 .map_err(|_| "ENGINE_STATE was already set!?".to_string())?;
144 STACK
145 .set(RwLock::new(Stack::new()))
146 .map_err(|_| "STACK was already set!?".to_string())?;
147
148 let mut startup_script = String::new();
149
150 // source our "nu rc"
151 let rc_path = get_vfs().join("/.env.nu").ok();
152 let rc = rc_path.and_then(|env| env.exists().ok().and_then(|ok| ok.then_some(env)));
153 if let Some(env) = rc {
154 writeln!(&mut startup_script, "eval file {path}", path = env.as_str()).unwrap();
155 }
156
157 // add some aliases for some commands
158 let aliases = ["alias l = ls", "alias la = ls -a", "alias . = eval file"];
159 for alias in aliases {
160 writeln!(&mut startup_script, "{alias}").unwrap();
161 }
162
163 run_command_internal(&startup_script).await?;
164
165 Ok(())
166}
167
168async fn run_command_internal(input: &str) -> Result<(), String> {
169 let mut engine_state = unsafe { ENGINE_STATE.get().unwrap_unchecked() }
170 .upgradable_read()
171 .await;
172 let (mut working_set, signals, config) = {
173 let mut write_engine_state = RwLockUpgradableReadGuard::upgrade(engine_state).await;
174 // apply_pending_deltas(&mut write_engine_state).map_err(|e| CommandError {
175 // error: Report::new(e),
176 // start_offset: 0,
177 // })?;
178 write_engine_state.add_env_var(
179 "PWD".to_string(),
180 get_pwd_string().into_value(Span::unknown()),
181 );
182 engine_state = RwLockWriteGuard::downgrade_to_upgradable(write_engine_state);
183
184 (
185 StateWorkingSet::new(&engine_state),
186 engine_state.signals().clone(),
187 engine_state.config.clone(),
188 )
189 };
190 let start_offset = working_set.next_span_start();
191 let block = parse(&mut working_set, Some("entry"), input.as_bytes(), false);
192
193 let cmd_err = |err: ShellError| CommandError::new(err, input).with_start_offset(start_offset);
194
195 if let Some(err) = working_set.parse_errors.into_iter().next() {
196 return Err(CommandError::new(err, input)
197 .with_start_offset(start_offset)
198 .into());
199 }
200 if let Some(err) = working_set.compile_errors.into_iter().next() {
201 return Err(CommandError::new(err, input)
202 .with_start_offset(start_offset)
203 .into());
204 }
205 let delta = working_set.delta;
206
207 let result = {
208 let mut write_engine_state = RwLockUpgradableReadGuard::upgrade(engine_state).await;
209 let mut stack = write_stack().await;
210 write_engine_state.merge_delta(delta).map_err(cmd_err)?;
211 engine_state = RwLockWriteGuard::downgrade_to_upgradable(write_engine_state);
212 let res = eval_block::<nu_protocol::debugger::WithoutDebug>(
213 &engine_state,
214 &mut stack,
215 &block,
216 PipelineData::Empty,
217 );
218 // apply_pending_deltas(&mut write_engine_state).map_err(cmd_err)?;
219 res
220 };
221
222 let pipeline_data = result.map_err(cmd_err)?.body;
223
224 // this is annoying but we have to collect here so we can uncover errors
225 // before passing the data off to Table, because otherwise Table
226 // can't properly handle the errors and panics (something about ShellErrorBridge
227 // having a non IO error in it somehow idk i dont care really)
228 // TODO: see if there is a way to do this without collecting the pipeline
229 let pipeline_data = match pipeline_data {
230 PipelineData::Empty => return Ok(()),
231 PipelineData::Value(Value::Error { error, .. }, _) => {
232 return Err(cmd_err(*error).into());
233 }
234 PipelineData::ByteStream(s, m) => match (s.span(), s.type_(), s.reader()) {
235 (span, ty, Some(r)) => {
236 use std::io::Read;
237 let v = r
238 .bytes()
239 .collect::<Result<Vec<u8>, _>>()
240 .map_err(|e| cmd_err(ShellError::Io(IoError::new(e, span, None))))?;
241 (v.len() > 0)
242 .then(|| {
243 PipelineData::byte_stream(
244 ByteStream::read(Cursor::new(v), span, signals, ty),
245 m,
246 )
247 })
248 .unwrap_or(PipelineData::Empty)
249 }
250 (_, _, None) => PipelineData::Empty,
251 },
252 PipelineData::ListStream(s, m) => {
253 let span = s.span();
254 let v = s
255 .into_iter()
256 .map(|val| val.unwrap_error().map_err(cmd_err))
257 .collect::<Result<Vec<Value>, _>>()?;
258 PipelineData::list_stream(ListStream::new(v.into_iter(), span, signals), m)
259 }
260 x => x,
261 };
262
263 let res = {
264 let mut write_engine_state = RwLockUpgradableReadGuard::upgrade(engine_state).await;
265 let mut stack = write_stack().await;
266 eval_hook(
267 &mut write_engine_state,
268 &mut stack,
269 Some(pipeline_data),
270 vec![],
271 config.hooks.display_output.as_ref().unwrap(),
272 "display_output",
273 )
274 .map_err(cmd_err)?
275 };
276
277 match res {
278 PipelineData::Empty => {}
279 PipelineData::Value(v, _) => {
280 print_to_console(&v.to_expanded_string("\n", &config), true);
281 }
282 PipelineData::ByteStream(s, _) => {
283 for line in s.lines().into_iter().flatten() {
284 let out = line.map_err(cmd_err)?; // TODO: do we turn this into a Value ??? or is returning err fine
285 print_to_console(&out, true);
286 }
287 }
288 PipelineData::ListStream(s, _) => {
289 for item in s.into_iter() {
290 let out = item
291 .unwrap_error()
292 .map_err(cmd_err)?
293 .to_expanded_string("\n", &config);
294 print_to_console(&out, true);
295 }
296 }
297 }
298
299 Ok(())
300}
301
302#[wasm_bindgen]
303pub fn run_command(input: String) -> Promise {
304 set_interrupt(false);
305
306 future_to_promise(async move {
307 run_command_internal(&input)
308 .map_ok(|_| JsValue::null())
309 .map_err(|s| JsValue::from_str(&s))
310 .await
311 })
312}
313
314#[wasm_bindgen]
315pub fn get_pwd_string() -> String {
316 // web_sys::console::log_1(&"before pwd".into());
317 let pwd = get_pwd();
318 // web_sys::console::log_1(&"after pwd".into());
319 if pwd.is_root() {
320 return "/".to_string();
321 }
322 pwd.as_str().to_string()
323}