diff options
Diffstat (limited to 'src/ps1.rs')
| -rw-r--r-- | src/ps1.rs | 95 |
1 files changed, 95 insertions, 0 deletions
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<WatchId>, + parsed: Option<ExpString>, + cached_prompt: Option<BString>, +} + +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<Mutex<Session>>) { + 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<ExpString> { + 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(); + } +} |
