Skip to main content

shrub_core/shell/
prompts.rs

1//! Prompt handling for shell instances.
2
3use std::borrow::Cow;
4
5use crate::{Shell, error, extensions, prompt};
6
7impl<SE: extensions::ShellExtensions> Shell<SE> {
8    /// Returns the default prompt string for the shell.
9    const fn default_prompt(&self) -> &'static str {
10        if self.options.sh_mode {
11            "$ "
12        } else {
13            "shrub$ "
14        }
15    }
16
17    /// Composes the shell's post-input, pre-command prompt, applying all
18    /// appropriate expansions.
19    pub async fn compose_precmd_prompt(&mut self) -> Result<String, error::Error> {
20        self.expand_prompt_var("PS0", "").await
21    }
22
23    /// Composes the shell's prompt, applying all appropriate expansions.
24    pub async fn compose_prompt(&mut self) -> Result<String, error::Error> {
25        self.expand_prompt_var("PS1", self.default_prompt()).await
26    }
27
28    /// Composes the shell's alternate-side prompt, applying all appropriate
29    /// expansions.
30    pub async fn compose_alt_side_prompt(&mut self) -> Result<String, error::Error> {
31        // This is a shrub extension.
32        self.expand_prompt_var("SHRUB_PS_ALT", "").await
33    }
34
35    /// Composes the shell's continuation prompt.
36    pub async fn compose_continuation_prompt(&mut self) -> Result<String, error::Error> {
37        self.expand_prompt_var("PS2", "> ").await
38    }
39
40    pub(super) async fn expand_prompt_var(
41        &mut self,
42        var_name: &str,
43        default: &str,
44    ) -> Result<String, error::Error> {
45        //
46        // TODO(prompt): bash appears to do this in a subshell; we need to
47        // investigate if that's required.
48        //
49
50        // Retrieve the spec.
51        let prompt_spec = self.parameter_or_default(var_name, default);
52        if prompt_spec.is_empty() {
53            return Ok(String::new());
54        }
55
56        // Save (and later restore) the state the last command left behind, so
57        // that expanding here is invisible to it.
58        let saved_status = self.save_command_status();
59
60        // Expand it. We must own the spec here: it borrows `self` (via the
61        // returned `Cow`), and `expand_prompt` needs `&mut self`.
62        let prompt_spec = prompt_spec.into_owned();
63        let params = self.default_exec_params();
64        let result = prompt::expand_prompt(self, &params, &prompt_spec).await;
65
66        self.restore_command_status(saved_status);
67
68        // Strip out special characters that readline would typically drop:
69        // \001 and \002 (start and end of non-printing sequences).
70        let mut expanded = result?;
71        expanded.retain(|c| c != '\x01' && c != '\x02');
72
73        Ok(expanded)
74    }
75
76    fn parameter_or_default<'a>(&'a self, name: &str, default: &'a str) -> Cow<'a, str> {
77        self.env_str(name).unwrap_or_else(|| default.into())
78    }
79}