From 2d0d919e661d9988d8bc26a2f5fa7bc6ae5b4f55 Mon Sep 17 00:00:00 2001 From: pagedmov Date: Wed, 25 Feb 2026 16:48:49 -0500 Subject: [PATCH] Prompt now only redraws on completed jobs and new commands Tab completion now finds env var names as well as internally set names --- src/main.rs | 13 +++++++++---- src/prompt/readline/mod.rs | 8 +++----- src/prompt/readline/vimode.rs | 2 -- src/signal.rs | 2 ++ src/state.rs | 9 +++++++-- 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/main.rs b/src/main.rs index f1cd5ab..a1f6a44 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,7 +34,7 @@ use crate::prelude::*; use crate::prompt::get_prompt; use crate::prompt::readline::term::{LineWriter, RawModeGuard, raw_mode}; use crate::prompt::readline::{Prompt, ReadlineEvent, ShedVi}; -use crate::signal::{GOT_SIGWINCH, QUIT_CODE, check_signals, sig_setup, signals_pending}; +use crate::signal::{GOT_SIGWINCH, JOB_DONE, QUIT_CODE, check_signals, sig_setup, signals_pending}; use crate::state::{read_logic, source_rc, write_jobs, write_meta}; use clap::Parser; use state::{read_vars, write_vars}; @@ -186,7 +186,7 @@ fn shed_interactive() -> ShResult<()> { match e.kind() { ShErrKind::ClearReadline => { // Ctrl+C - clear current input and show new prompt - readline.reset(Prompt::new()); + readline.reset(); } ShErrKind::CleanExit(code) => { QUIT_CODE.store(*code, Ordering::SeqCst); @@ -200,9 +200,14 @@ fn shed_interactive() -> ShResult<()> { if GOT_SIGWINCH.swap(false, Ordering::SeqCst) { log::info!("Window size change detected, updating readline dimensions"); readline.writer.update_t_cols(); + readline.prompt_mut().refresh()?; + } + + if JOB_DONE.swap(false, Ordering::SeqCst) { + // update the prompt so any job count escape sequences update dynamically + readline.prompt_mut().refresh()?; } - readline.prompt_mut().refresh(); readline.print_line(false)?; // Poll for stdin input @@ -265,7 +270,7 @@ fn shed_interactive() -> ShResult<()> { readline.writer.flush_write("\n")?; // Reset for next command with fresh prompt - readline.reset(Prompt::new()); + readline.reset(); let real_end = start.elapsed(); log::info!("Total round trip time: {:.2?}", real_end); } diff --git a/src/prompt/readline/mod.rs b/src/prompt/readline/mod.rs index e95f8e1..64e9023 100644 --- a/src/prompt/readline/mod.rs +++ b/src/prompt/readline/mod.rs @@ -256,8 +256,8 @@ impl ShedVi { /// Reset readline state for a new prompt - pub fn reset(&mut self, prompt: Prompt) { - self.prompt = prompt; + pub fn reset(&mut self) { + self.prompt = Prompt::new(); self.editor = Default::default(); self.mode = Box::new(ViInsert::new()); self.old_layout = None; @@ -516,9 +516,7 @@ impl ShedVi { let line = self.line_text(); let new_layout = self.get_layout(&line); let pending_seq = self.mode.pending_seq(); - let mut prompt_string_right = env::var("PSR") - .map(|psr| expand_prompt(&psr).unwrap()) - .ok(); + let mut prompt_string_right = self.prompt.psr_expanded.clone(); if prompt_string_right.as_ref().is_some_and(|psr| psr.lines().count() > 1) { log::warn!("PSR has multiple lines, truncating to one line"); diff --git a/src/prompt/readline/vimode.rs b/src/prompt/readline/vimode.rs index 4e08e91..0d438d5 100644 --- a/src/prompt/readline/vimode.rs +++ b/src/prompt/readline/vimode.rs @@ -346,7 +346,6 @@ impl ViNormal { } } /// End the parse and clear the pending sequence - #[track_caller] pub fn quit_parse(&mut self) -> Option { self.clear_cmd(); None @@ -1137,7 +1136,6 @@ impl ViVisual { } } /// End the parse and clear the pending sequence - #[track_caller] pub fn quit_parse(&mut self) -> Option { self.clear_cmd(); None diff --git a/src/signal.rs b/src/signal.rs index 2023afd..eed3018 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -16,6 +16,7 @@ static SIGNALS: AtomicU64 = AtomicU64::new(0); pub static REAPING_ENABLED: AtomicBool = AtomicBool::new(true); pub static SHOULD_QUIT: AtomicBool = AtomicBool::new(false); pub static GOT_SIGWINCH: AtomicBool = AtomicBool::new(false); +pub static JOB_DONE: AtomicBool = AtomicBool::new(false); pub static QUIT_CODE: AtomicI32 = AtomicI32::new(0); const MISC_SIGNALS: [Signal; 22] = [ @@ -285,6 +286,7 @@ pub fn child_exited(pid: Pid, status: WtStat) -> ShResult<()> { if is_fg { take_term()?; } else { + JOB_DONE.store(true, Ordering::SeqCst); let job_order = read_jobs(|j| j.order().to_vec()); let result = read_jobs(|j| j.query(JobID::Pgid(pgid)).cloned()); if let Some(job) = result { diff --git a/src/state.rs b/src/state.rs index c7a88b4..69691bc 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,5 +1,5 @@ use std::{ - cell::RefCell, collections::{HashMap, HashSet, VecDeque}, fmt::Display, ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Deref}, os::unix::fs::PermissionsExt, str::FromStr, time::Duration + cell::RefCell, collections::{HashMap, HashSet, VecDeque, hash_map::Entry}, fmt::Display, ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Deref}, os::unix::fs::PermissionsExt, str::FromStr, time::Duration }; use nix::unistd::{User, gethostname, getppid}; @@ -183,6 +183,12 @@ impl ScopeStack { flat_vars.insert(var_name.clone(), var.clone()); } } + for var in env::vars() { + if let Entry::Vacant(e) = flat_vars.entry(var.0) { + e.insert(Var::new(VarKind::Str(var.1), VarFlags::EXPORT)); + } + } + flat_vars } pub fn set_var(&mut self, var_name: &str, val: &str, flags: VarFlags) -> ShResult<()> { @@ -953,7 +959,6 @@ pub fn get_status() -> i32 { .parse::() .unwrap() } -#[track_caller] pub fn set_status(code: i32) { write_vars(|v| v.set_param(ShellParam::Status, &code.to_string())) }