aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs37
-rw-r--r--src/parse/mod.rs20
-rw-r--r--src/ps1.rs95
-rw-r--r--src/run/mod.rs1
4 files changed, 120 insertions, 33 deletions
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<Mutex<Self>>) -> 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<Mutex<Self>>) -> 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<BString> {
+ 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<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();
+ }
+}
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 {