aboutsummaryrefslogtreecommitdiffstats
path: root/src/ps1.rs
diff options
context:
space:
mode:
authorJonas Maier <>2026-05-23 23:34:31 +0200
committerJonas Maier <>2026-05-23 23:34:31 +0200
commit7cd15fe67ca0118520dcc4e9e189d513b0375e10 (patch)
treeeed22bff9dc80eb298323c92acd3b915a803180d /src/ps1.rs
parenta9ea5669915f4ac6b0d9e7d826d1ee341a2e3c80 (diff)
downloadpish-7cd15fe67ca0118520dcc4e9e189d513b0375e10.tar.gz
prompt evaluation in own module
Diffstat (limited to 'src/ps1.rs')
-rw-r--r--src/ps1.rs95
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();
+ }
+}