diff --git a/.gitignore b/.gitignore index 0799c32..2dbfd4b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ Thumbs.db # Env .env .env.* +!.env.nu !.env.example !.env.test @@ -26,4 +27,4 @@ vite.config.ts.timestamp-* target # ai scratchpad -/.scratchpad \ No newline at end of file +/.scratchpad diff --git a/src/cmd/source.rs b/src/cmd/source.rs index 0f2d4e2..330ff7b 100644 --- a/src/cmd/source.rs +++ b/src/cmd/source.rs @@ -1,24 +1,24 @@ use crate::globals::{get_pwd, queue_delta, to_shell_err}; -use nu_engine::{CallExt, command_prelude::IoError, eval_block}; +use nu_engine::{CallExt, eval_block}; use nu_parser::parse; use nu_protocol::{ - Category, PipelineData, ShellError, Signature, SyntaxShape, Type, + Category, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, debugger::WithoutDebug, engine::{Command, EngineState, Stack, StateWorkingSet}, }; -use std::{io::Read, path::PathBuf, str::FromStr}; +use vfs::VfsPath; #[derive(Clone)] pub struct Source; impl Command for Source { fn name(&self) -> &str { - "source" + "source-file" } fn signature(&self) -> Signature { Signature::build(self.name()) - .required("filename", SyntaxShape::Filepath, "the file to source") + .required("path", SyntaxShape::Filepath, "the file to source") .input_output_type(Type::Nothing, Type::Nothing) .category(Category::Core) } @@ -32,59 +32,58 @@ impl Command for Source { engine_state: &EngineState, stack: &mut Stack, call: &nu_protocol::engine::Call, - input: PipelineData, + _input: PipelineData, ) -> Result { - let filename: String = call.req(engine_state, stack, 0)?; - - // 1. Read file from VFS - let path = get_pwd().join(&filename).map_err(to_shell_err(call.head))?; - let mut file = path.open_file().map_err(to_shell_err(call.head))?; - let mut contents = String::new(); - file.read_to_string(&mut contents).map_err(|e| { - ShellError::Io(IoError::new( - e, - call.head, - PathBuf::from_str(path.as_str()).unwrap(), - )) - })?; - - // 2. Parse the content - // We create a new working set based on the CURRENT engine state. - let mut working_set = StateWorkingSet::new(engine_state); + let path: String = call.req(engine_state, stack, 0)?; + + let path = get_pwd() + .join(&path) + .map_err(to_shell_err(call.arguments_span()))?; + eval_file(engine_state, stack, &path, call.arguments_span()) + } +} - // We must add the file to the working set so the parser can track spans correctly - let _file_id = working_set.add_file(filename.clone(), contents.as_bytes()); +pub fn eval_file<'s>( + engine_state: &'s EngineState, + stack: &mut Stack, + path: &VfsPath, + span: Span, +) -> Result { + let contents = path.read_to_string().map_err(to_shell_err(span))?; - // Parse the block - let block = parse( - &mut working_set, - Some(&filename), - contents.as_bytes(), - false, - ); + let mut working_set= StateWorkingSet::new(engine_state); + let _ = working_set.add_file(path.filename(), contents.as_bytes()); - if let Some(err) = working_set.parse_errors.first() { - return Err(ShellError::GenericError { - error: "Parse error".into(), - msg: err.to_string(), - span: Some(call.head), - help: None, - inner: vec![], - }); - } + // we dont need the block here just the delta + let block = parse( + &mut working_set, + Some(&path.filename()), + contents.as_bytes(), + false, + ); - // 3. Prepare execution context - // We clone the engine state to merge the new definitions (delta) locally. - // This ensures the script can call its own defined functions immediately. - let mut local_state = engine_state.clone(); - local_state.merge_delta(working_set.delta.clone())?; + if let Some(err) = working_set.parse_errors.first() { + web_sys::console::error_1(&err.to_string().into()); + return Err(ShellError::GenericError { + error: "parse error".into(), + msg: err.to_string(), + span: Some(span), + help: None, + inner: vec![], + }); + } + if let Some(err) = working_set.compile_errors.first() { + web_sys::console::error_1(&err.to_string().into()); + return Err(ShellError::GenericError { + error: "compile error".into(), + msg: err.to_string(), + span: Some(span), + help: None, + inner: vec![], + }); + } - // 4. Queue the delta for the global engine state - // This allows definitions to be available in the next command execution cycle (REPL behavior). - queue_delta(working_set.delta); + queue_delta(working_set.delta); - // 5. Evaluate the block - // We pass the MUTABLE stack, so environment variable changes (PWD, load-env) WILL persist. - eval_block::(&local_state, stack, &block, input).map(|data| data.body) - } + eval_block::(engine_state, stack, &block, PipelineData::Empty).map(|d| d.body) } diff --git a/src/default_context.rs b/src/default_context.rs index aa5ac2f..b688ec9 100644 --- a/src/default_context.rs +++ b/src/default_context.rs @@ -264,7 +264,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { bind_command! { ExportEnv, LoadEnv, - SourceEnv, + // SourceEnv, WithEnv, ConfigNu, ConfigEnv, diff --git a/src/lib.rs b/src/lib.rs index 43756ed..d9eff88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,11 +29,12 @@ pub mod memory_fs; use crate::{ cmd::{ Cd, Fetch, Job, JobKill, JobList, Ls, Mkdir, Mv, Open, Pwd, Random, Rm, Save, Source, Sys, + source::eval_file, }, default_context::add_shell_command_context, error::format_error, globals::{ - InterruptBool, apply_pending_deltas, get_pwd, print_to_console, set_interrupt, + InterruptBool, apply_pending_deltas, get_pwd, get_vfs, print_to_console, set_interrupt, }, }; use error::CommandError; @@ -67,24 +68,35 @@ fn panic_hook(info: &std::panic::PanicHookInfo) { static ENGINE_STATE: OnceLock> = OnceLock::new(); #[inline] async fn read_engine_state() -> RwLockReadGuard<'static, EngineState> { - ENGINE_STATE.get().unwrap().read().await + unsafe { ENGINE_STATE.get().unwrap_unchecked() } + .read() + .await } #[inline] async fn write_engine_state() -> RwLockWriteGuard<'static, EngineState> { - ENGINE_STATE.get().unwrap().write().await + unsafe { ENGINE_STATE.get().unwrap_unchecked() } + .write() + .await } static STACK: OnceLock> = OnceLock::new(); #[inline] async fn read_stack() -> RwLockReadGuard<'static, Stack> { - STACK.get().unwrap().read().await + unsafe { STACK.get().unwrap_unchecked() }.read().await } #[inline] async fn write_stack() -> RwLockWriteGuard<'static, Stack> { - STACK.get().unwrap().write().await + unsafe { STACK.get().unwrap_unchecked() }.write().await +} + +#[wasm_bindgen] +pub fn init_engine() -> String { + std::panic::set_hook(Box::new(panic_hook)); + init_engine_internal().map_or_else(|err| format!("error: {err}"), |_| String::new()) } fn init_engine_internal() -> Result<(), Report> { + let mut stack = Stack::new(); let mut engine_state = create_default_context(); engine_state = add_shell_command_context(engine_state); engine_state = add_extra_command_context(engine_state); @@ -121,11 +133,19 @@ fn init_engine_internal() -> Result<(), Report> { engine_state.set_signals(Signals::new(Arc::new(InterruptBool))); + // source our "nu rc" + let rc_path = get_vfs().join("/.env.nu").ok(); + let rc = rc_path.and_then(|env| env.exists().ok().and_then(|ok| ok.then_some(env))); + if let Some(env) = rc { + web_sys::console::log_1(&format!("Loading rc file: {}", env.as_str()).into()); + eval_file(&engine_state, &mut stack, &env, Span::unknown())?; + } + ENGINE_STATE .set(RwLock::new(engine_state)) .map_err(|_| miette::miette!("ENGINE_STATE was already set!?"))?; STACK - .set(RwLock::new(Stack::new())) + .set(RwLock::new(stack)) .map_err(|_| miette::miette!("STACK was already set!?"))?; // web_sys::console::log_1(&"Hello, World!".into()); @@ -133,14 +153,10 @@ fn init_engine_internal() -> Result<(), Report> { Ok(()) } -#[wasm_bindgen] -pub fn init_engine() -> String { - std::panic::set_hook(Box::new(panic_hook)); - init_engine_internal().map_or_else(|err| format!("error: {err}"), |_| String::new()) -} - async fn run_command_internal(input: &str) -> Result<(), CommandError> { - let mut engine_state = ENGINE_STATE.get().unwrap().upgradable_read().await; + let mut engine_state = unsafe { ENGINE_STATE.get().unwrap_unchecked() } + .upgradable_read() + .await; let (mut working_set, signals, config) = { let mut write_engine_state = RwLockUpgradableReadGuard::upgrade(engine_state).await; apply_pending_deltas(&mut write_engine_state).map_err(|e| CommandError { @@ -191,6 +207,8 @@ async fn run_command_internal(input: &str) -> Result<(), CommandError> { &block, PipelineData::Empty, ); + // Apply any deltas queued during command execution (e.g., from source-file) + apply_pending_deltas(&mut write_engine_state).map_err(cmd_err)?; engine_state = RwLockWriteGuard::downgrade_to_upgradable(write_engine_state); res }; diff --git a/www/src/worker.ts b/www/src/worker.ts index 6086f67..522bee0 100644 --- a/www/src/worker.ts +++ b/www/src/worker.ts @@ -11,7 +11,10 @@ import init, { // Initialize WASM await init(); -init_engine(); +const error = init_engine(); +if (error) { + console.error(error); +} // Setup Callbacks to proxy messages back to Main Thread register_console_callback((msg: string, isCmd: boolean) => {