diff --git a/flake.nix b/flake.nix index 58b1100..7acd0f9 100644 --- a/flake.nix +++ b/flake.nix @@ -22,9 +22,12 @@ lockFile = ./Cargo.lock; }; - doCheck = false; passthru.shellPath = "/bin/shed"; + checkPhase = '' + cargo test -- --test-threads=1 + ''; + meta = with pkgs.lib; { description = "A Linux shell written in Rust"; homepage = "https://github.com/km-clay/shed"; diff --git a/src/builtin/dirstack.rs b/src/builtin/dirstack.rs index d4ea28f..9f498e3 100644 --- a/src/builtin/dirstack.rs +++ b/src/builtin/dirstack.rs @@ -429,7 +429,7 @@ pub fn dirs(node: Node) -> ShResult<()> { #[cfg(test)] pub mod tests { use std::{env, path::PathBuf}; - use crate::{parse::execute::exec_input, state::{self, read_meta}, testutil::TestGuard}; + use crate::{state::{self, read_meta}, testutil::{TestGuard, test_input}}; use pretty_assertions::{assert_ne,assert_eq}; use tempfile::TempDir; @@ -438,7 +438,7 @@ use tempfile::TempDir; let g = TestGuard::new(); let current_dir = env::current_dir().unwrap(); - exec_input("pushd /tmp".into(), None, true, None).unwrap(); + test_input("pushd /tmp").unwrap(); let new_dir = env::current_dir().unwrap(); @@ -461,7 +461,7 @@ use tempfile::TempDir; let tempdir = TempDir::new().unwrap(); let tempdir_raw = tempdir.path().to_path_buf().to_string_lossy().to_string(); - exec_input(format!("pushd {tempdir_raw}"), None, true, None).unwrap(); + test_input(format!("pushd {tempdir_raw}")).unwrap(); let dir_stack = read_meta(|m| m.dirs().clone()); assert_eq!(dir_stack.len(), 1); @@ -470,7 +470,7 @@ use tempfile::TempDir; assert_eq!(env::current_dir().unwrap(), tempdir.path()); g.read_output(); // consume output of pushd - exec_input("popd".into(), None, true, None).unwrap(); + test_input("popd").unwrap(); assert_eq!(env::current_dir().unwrap(), current_dir); let out = g.read_output(); @@ -482,7 +482,7 @@ use tempfile::TempDir; fn test_popd_empty_stack() { let _g = TestGuard::new(); - exec_input("popd".into(), None, false, None).unwrap_err(); + test_input("popd").unwrap_err(); assert_ne!(state::get_status(), 0); } @@ -495,8 +495,8 @@ use tempfile::TempDir; let path1 = tmp1.path().to_path_buf(); let path2 = tmp2.path().to_path_buf(); - exec_input(format!("pushd {}", path1.display()), None, false, None).unwrap(); - exec_input(format!("pushd {}", path2.display()), None, false, None).unwrap(); + test_input(format!("pushd {}", path1.display())).unwrap(); + test_input(format!("pushd {}", path2.display())).unwrap(); g.read_output(); assert_eq!(env::current_dir().unwrap(), path2); @@ -505,10 +505,10 @@ use tempfile::TempDir; assert_eq!(stack[0], path1); assert_eq!(stack[1], original); - exec_input("popd".into(), None, false, None).unwrap(); + test_input("popd").unwrap(); assert_eq!(env::current_dir().unwrap(), path1); - exec_input("popd".into(), None, false, None).unwrap(); + test_input("popd").unwrap(); assert_eq!(env::current_dir().unwrap(), original); let stack = read_meta(|m| m.dirs().clone()); @@ -526,13 +526,13 @@ use tempfile::TempDir; // Build stack: cwd=original, then pushd path1, pushd path2 // Stack after: cwd=path2, [path1, original] - exec_input(format!("pushd {}", path1.display()), None, false, None).unwrap(); - exec_input(format!("pushd {}", path2.display()), None, false, None).unwrap(); + test_input(format!("pushd {}", path1.display())).unwrap(); + test_input(format!("pushd {}", path2.display())).unwrap(); g.read_output(); // pushd +1 rotates: [path2, path1, original] -> rotate_left(1) -> [path1, original, path2] // pop front -> cwd=path1, stack=[original, path2] - exec_input("pushd +1".into(), None, false, None).unwrap(); + test_input("pushd +1").unwrap(); assert_eq!(env::current_dir().unwrap(), path1); let stack = read_meta(|m| m.dirs().clone()); @@ -548,7 +548,7 @@ use tempfile::TempDir; let tmp = TempDir::new().unwrap(); let path = tmp.path().to_path_buf(); - exec_input(format!("pushd -n {}", path.display()), None, false, None).unwrap(); + test_input(format!("pushd -n {}", path.display())).unwrap(); // -n means don't cd, but the dir should still be on the stack assert_eq!(env::current_dir().unwrap(), original); @@ -559,10 +559,10 @@ use tempfile::TempDir; let _g = TestGuard::new(); let tmp = TempDir::new().unwrap(); - exec_input(format!("pushd {}", tmp.path().display()), None, false, None).unwrap(); + test_input(format!("pushd {}", tmp.path().display())).unwrap(); assert_eq!(read_meta(|m| m.dirs().len()), 1); - exec_input("dirs -c".into(), None, false, None).unwrap(); + test_input("dirs -c").unwrap(); assert_eq!(read_meta(|m| m.dirs().len()), 0); } @@ -573,10 +573,10 @@ use tempfile::TempDir; let tmp = TempDir::new().unwrap(); let path = tmp.path().to_path_buf(); - exec_input(format!("pushd {}", path.display()), None, false, None).unwrap(); + test_input(format!("pushd {}", path.display())).unwrap(); g.read_output(); - exec_input("dirs -p".into(), None, false, None).unwrap(); + test_input("dirs -p").unwrap(); let out = g.read_output(); let lines: Vec<&str> = out.split('\n').filter(|l| !l.is_empty()).collect(); assert_eq!(lines.len(), 2); @@ -594,11 +594,11 @@ use tempfile::TempDir; let path2 = tmp2.path().to_path_buf(); // Stack: cwd=path2, [path1, original] - exec_input(format!("pushd {}", path1.display()), None, false, None).unwrap(); - exec_input(format!("pushd {}", path2.display()), None, false, None).unwrap(); + test_input(format!("pushd {}", path1.display())).unwrap(); + test_input(format!("pushd {}", path2.display())).unwrap(); // popd +1 removes index (1-1)=0 from stored dirs, i.e. path1 - exec_input("popd +1".into(), None, false, None).unwrap(); + test_input("popd +1").unwrap(); assert_eq!(env::current_dir().unwrap(), path2); // no cd let stack = read_meta(|m| m.dirs().clone()); @@ -610,7 +610,7 @@ use tempfile::TempDir; fn test_pushd_nonexistent_dir() { let _g = TestGuard::new(); - let result = exec_input("pushd /nonexistent_dir_12345".into(), None, false, None); + let result = test_input("pushd /nonexistent_dir_12345"); assert!(result.is_err()); } } diff --git a/src/parse/execute.rs b/src/parse/execute.rs index 5b53221..6c40111 100644 --- a/src/parse/execute.rs +++ b/src/parse/execute.rs @@ -411,7 +411,8 @@ impl Dispatcher { blame.rename(func_name.clone()); - let argv = prepare_argv(argv).try_blame(blame.clone())?; + let mut argv = prepare_argv(argv).try_blame(blame.clone())?; + argv.insert(0, (func_name.clone(), blame.clone())); let result = if let Some(ref mut func_body) = read_logic(|l| l.get_func(&func_name)) { let _guard = scope_guard(Some(argv)); func_body.body_mut().propagate_context(func_ctx); diff --git a/src/readline/complete.rs b/src/readline/complete.rs index d923db2..f6475ea 100644 --- a/src/readline/complete.rs +++ b/src/readline/complete.rs @@ -226,7 +226,7 @@ fn complete_filename(start: &str) -> Vec { let mut full_path = dir.join(&file_name); // Add trailing slash for directories - if entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) { + if entry.metadata().map(|m| m.is_dir()).unwrap_or(false) { full_path.push(""); // adds trailing / } diff --git a/src/readline/mod.rs b/src/readline/mod.rs index 111e94b..ef8e9ca 100644 --- a/src/readline/mod.rs +++ b/src/readline/mod.rs @@ -488,6 +488,16 @@ impl ShedVi { self.needs_redraw = true; self.completer.reset(); + write_vars(|v| { + v.set_var( + "SHED_VI_MODE", + VarKind::Str(self.mode.report_mode().to_string()), + VarFlags::NONE, + ) + }) + .ok(); + self.prompt.refresh(); + with_vars([("_COMP_CANDIDATE".into(), candidate.clone())], || { post_cmds.exec_with(&candidate); }); @@ -642,6 +652,13 @@ impl ShedVi { .update_pending_cmd((self.editor.as_str(), self.editor.cursor.get())); let hint = self.history.get_hint(); self.editor.set_hint(hint); + write_vars(|v| { + v.set_var( + "SHED_VI_MODE", + VarKind::Str(self.mode.report_mode().to_string()), + VarFlags::NONE, + ) + }).ok(); // If we are here, we hit a case where pressing tab returned a single candidate // So we can just go ahead and reset the completer after this diff --git a/src/state.rs b/src/state.rs index a2d0358..4f95adb 100644 --- a/src/state.rs +++ b/src/state.rs @@ -195,7 +195,7 @@ impl ScopeStack { new } pub fn descend(&mut self, argv: Option>) { - let mut new_vars = VarTab::new(); + let mut new_vars = VarTab::bare(); if let Some(argv) = argv { for arg in argv { new_vars.bpush_arg(arg); @@ -494,6 +494,13 @@ impl ScopeStack { { return val.clone(); } + // Positional params are scope-local; only check the current scope + if matches!(param, ShellParam::Pos(_) | ShellParam::AllArgs | ShellParam::AllArgsStr | ShellParam::ArgCount) { + if let Some(scope) = self.scopes.last() { + return scope.get_param(param); + } + return "".into(); + } for scope in self.scopes.iter().rev() { let val = scope.get_param(param); if !val.is_empty() { @@ -996,6 +1003,14 @@ pub struct VarTab { } impl VarTab { + pub fn bare() -> Self { + Self { + vars: HashMap::new(), + params: HashMap::new(), + sh_argv: VecDeque::new(), + maps: HashMap::new(), + } + } pub fn new() -> Self { let vars = HashMap::new(); let params = Self::init_params(); diff --git a/src/testutil.rs b/src/testutil.rs index 871e636..3868342 100644 --- a/src/testutil.rs +++ b/src/testutil.rs @@ -32,7 +32,7 @@ pub fn has_cmd(cmd: &str) -> bool { } pub fn test_input(input: impl Into) -> ShResult<()> { - exec_input(input.into(), None, true, None) + exec_input(input.into(), None, false, None) } pub struct TestGuard {