nushell on your web browser
nushell wasm terminal
at main 10 kB view raw
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}