aboutsummaryrefslogtreecommitdiffstats
path: root/src/ps1.rs
blob: 05b58865cb30747dd53d81632169ce390cf3ed7c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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();
    }
}