From 7cd15fe67ca0118520dcc4e9e189d513b0375e10 Mon Sep 17 00:00:00 2001 From: Jonas Maier <> Date: Sat, 23 May 2026 23:34:31 +0200 Subject: prompt evaluation in own module --- src/lib.rs | 37 +++------------------- src/parse/mod.rs | 20 ++++++++++++ src/ps1.rs | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/run/mod.rs | 1 + 4 files changed, 120 insertions(+), 33 deletions(-) create mode 100644 src/ps1.rs (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index 4bdbebe..6ecbe83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,14 +28,15 @@ pub mod history; pub mod line; pub mod panic; pub mod parse; +pub mod ps1; pub mod raw; pub mod reload; pub mod run; pub mod rw; pub mod serialization; pub mod syntax_highlighting; -pub mod wait; pub mod variants; +pub mod wait; use raw::*; @@ -182,38 +183,8 @@ fn pretty_cwd() -> BString { } impl Session { - fn load_unevaled_prompt(&self) -> BString { - match self.vars.lookup(&b"PROMPT"[..]) { - Some(prompt) => prompt.into_owned(), - None => { - let mut prompt = BString::new(); - if cfg!(debug_assertions) { - prompt.push_all(b"dev "); - } - prompt.push_all(b"[$CWD_PRETTY]# "); - prompt - } - } - } - - fn prompt(this: Arc>) -> BString { - let mut prompt = this.lock().unwrap().load_unevaled_prompt(); - let mut x = Vec::with_capacity(prompt.len() + 2); - x.push(b'"'); - x.append(&mut prompt); - x.push(b'"'); - let parsed = match crate::parse::ExpString::parse_from_bytes(&x) { - Ok(x) => x, - Err(e) => { - println!("{e:?}"); - return b"PARSE_ERROR$ ".to_vec(); - } - }; - let mut expander = run::Executor::new(this); - let Ok(expanded) = parsed.expand(&mut expander) else { - return b"EXEC_ERROR$ ".to_vec(); - }; - expanded + fn prompt(_this: Arc>) -> BString { + todo!("replace with ps1 module"); } fn prompt_clear(&mut self) { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 5059311..6ce3133 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -551,6 +551,11 @@ impl StringPart { pub fn is_boring(&self) -> bool { matches!(self, StringPart::Boring(..)) } + + pub fn is_command(&self) -> bool { + matches!(self, StringPart::Cmd(..)) + } + pub fn unwrap_boring(self) -> BString { match self { StringPart::Boring(items) => items, @@ -588,6 +593,21 @@ impl ExpString { } Ok(out) } + + pub fn has_commands(&self) -> bool { + self.parts.iter().any(|part| part.is_command()) + } + + /// vars that are directly mentioned in this string interpolation, i.e. does not look into commands + pub fn vars(&self) -> Vec { + self.parts + .iter() + .filter_map(|part| match part { + StringPart::Var(var) => Some(var.name.name.clone()), + _ => None, + }) + .collect() + } } fn is_symbol(x: u8) -> bool { diff --git a/src/ps1.rs b/src/ps1.rs new file mode 100644 index 0000000..05b5886 --- /dev/null +++ b/src/ps1.rs @@ -0,0 +1,95 @@ +use std::borrow::Cow; +use std::sync::{Arc, Mutex}; + +use crate::parse::{ExpString, Parse}; +use crate::run::{Vars, WatchId}; +use crate::{BString, Session, bstr}; + +pub const PROMPT_VAR: &str = "PROMPT"; + +#[allow(unused)] +const DEFAULT_PROMPT_VALUE_DEBUG: &bstr = b"dev [$CWD_PRETTY]# "; +#[allow(unused)] +const DEFAULT_PROMPT_VALUE_RELEASE: &bstr = b"[$CWD_PRETTY]# "; +#[cfg(test)] +const POSSIBLE_DEFAULT_PROMPT_VALUES: &[&bstr] = + &[DEFAULT_PROMPT_VALUE_DEBUG, DEFAULT_PROMPT_VALUE_RELEASE]; +#[cfg(debug_assertions)] +pub const DEFAULT_PROMPT_VALUE: &bstr = DEFAULT_PROMPT_VALUE_DEBUG; +#[cfg(not(debug_assertions))] +pub const DEFAULT_PROMPT_VALUE: &bstr = DEFAULT_PROMPT_VALUE_RELEASE; + +pub struct Prompt { + watch_var: WatchId, + watch_content: Option, + parsed: Option, + cached_prompt: Option, +} + +impl Prompt { + pub fn new(vars: &mut Vars) -> Self { + Self { + watch_var: vars.watch(vec![PROMPT_VAR.as_bytes().to_vec()]), + watch_content: None, + parsed: None, + cached_prompt: None, + } + } + + pub fn requires_update(&self, vars: &Vars) -> bool { + self.parsed.iter().any(|p| p.has_commands()) + || vars.peek_dirty(&self.watch_var) + || self.watch_content.iter().any(|wc| vars.peek_dirty(wc)) + } + + pub fn load_prompt(&mut self, vars: &mut Vars) { + if vars.pop_dirty(&self.watch_var) { + let prompt = vars + .lookup(PROMPT_VAR.as_bytes()) + .map(Cow::into_owned) + .unwrap_or_else(|| DEFAULT_PROMPT_VALUE.to_vec()); + let parsed = parse_prompt(prompt); + self.watch_content = Some(vars.watch(parsed.vars())); + self.parsed = Some(parsed); + } + + if let Some(wc) = &self.watch_content { + vars.pop_dirty(wc); + } + } + + pub fn eval_prompt(&mut self, se: Arc>) { + let mut expander = crate::run::Executor::new(se); + let expanded = self + .parsed + .clone() + .unwrap_or_else(|| parse_prompt(DEFAULT_PROMPT_VALUE.to_vec())) + .expand(&mut expander) + .unwrap_or_else(|_| b"EXEC_ERROR$ ".to_vec()); + self.cached_prompt = Some(expanded); + } + + pub fn prompt(&self) -> &bstr { + self.cached_prompt.as_ref().map(|p| &p[..]).unwrap_or(b"> ") + } +} + +fn parse_prompt_fallible(mut prompt: BString) -> Option { + let mut x = Vec::with_capacity(prompt.len() + 2); + x.push(b'"'); + x.append(&mut prompt); + x.push(b'"'); + crate::parse::ExpString::parse_from_bytes(&x).ok() +} + +fn parse_prompt(prompt: BString) -> ExpString { + parse_prompt_fallible(prompt) + .unwrap_or_else(|| parse_prompt_fallible(DEFAULT_PROMPT_VALUE.to_vec()).unwrap()) +} + +#[test] +fn default_prompt_should_parse() { + for prompt in POSSIBLE_DEFAULT_PROMPT_VALUES { + parse_prompt_fallible(prompt.to_vec()).unwrap(); + } +} diff --git a/src/run/mod.rs b/src/run/mod.rs index 842a918..dc715b1 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -12,6 +12,7 @@ mod builtin; mod var; pub use var::Vars; +pub use var::WatchId; #[derive(Debug)] pub enum ExecError { -- cgit v1.2.3