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: &bstr = b"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; #[derive(Clone)] pub struct Prompt { watch_var: WatchId, watch_content: Option, parsed: Option, cached_prompt: Option, } impl Prompt { pub fn new(vars: &mut Vars) -> Self { if vars.lookup(PROMPT_VAR).is_none() { vars.set(PROMPT_VAR.to_owned(), DEFAULT_PROMPT_VALUE.to_owned()); } Self { watch_var: vars.watch(vec![PROMPT_VAR.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) .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(); } }