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 ///
20 /// # Errors
21 ///
22 /// Returns [`crate::error::ErrorKind::WordParseError`] if the value of
23 /// `PS0` is not a valid prompt string, or the error from any expansion in
24 /// it, such as a failing command substitution.
25 pub async fn compose_precmd_prompt(&mut self) -> Result<String, error::Error> {
26 self.expand_prompt_var("PS0", "").await
27 }
28
29 /// Composes the shell's prompt, applying all appropriate expansions.
30 ///
31 /// # Errors
32 ///
33 /// Returns [`crate::error::ErrorKind::WordParseError`] if the value of
34 /// `PS1` is not a valid prompt string, or the error from any expansion in
35 /// it, such as a failing command substitution.
36 pub async fn compose_prompt(&mut self) -> Result<String, error::Error> {
37 self.expand_prompt_var("PS1", self.default_prompt()).await
38 }
39
40 /// Composes the shell's alternate-side prompt, applying all appropriate
41 /// expansions.
42 ///
43 /// # Errors
44 ///
45 /// Returns [`crate::error::ErrorKind::WordParseError`] if the value of
46 /// `SHRUB_PS_ALT` is not a valid prompt string, or the error from any
47 /// expansion in it, such as a failing command substitution.
48 pub async fn compose_alt_side_prompt(&mut self) -> Result<String, error::Error> {
49 // This is a shrub extension.
50 self.expand_prompt_var("SHRUB_PS_ALT", "").await
51 }
52
53 /// Composes the shell's continuation prompt.
54 ///
55 /// # Errors
56 ///
57 /// Returns [`crate::error::ErrorKind::WordParseError`] if the value of
58 /// `PS2` is not a valid prompt string, or the error from any expansion in
59 /// it, such as a failing command substitution.
60 pub async fn compose_continuation_prompt(&mut self) -> Result<String, error::Error> {
61 self.expand_prompt_var("PS2", "> ").await
62 }
63
64 pub(super) async fn expand_prompt_var(
65 &mut self,
66 var_name: &str,
67 default: &str,
68 ) -> Result<String, error::Error> {
69 //
70 // TODO(prompt): bash appears to do this in a subshell; we need to
71 // investigate if that's required.
72 //
73
74 // Retrieve the spec.
75 let prompt_spec = self.parameter_or_default(var_name, default);
76 if prompt_spec.is_empty() {
77 return Ok(String::new());
78 }
79
80 // Save (and later restore) the state the last command left behind, so
81 // that expanding here is invisible to it.
82 let saved_status = self.save_command_status();
83
84 // Expand it. We must own the spec here: it borrows `self` (via the
85 // returned `Cow`), and `expand_prompt` needs `&mut self`.
86 let prompt_spec = prompt_spec.into_owned();
87 let params = self.default_exec_params();
88 let result = prompt::expand_prompt(self, ¶ms, &prompt_spec).await;
89
90 self.restore_command_status(saved_status);
91
92 // Strip out special characters that readline would typically drop:
93 // \001 and \002 (start and end of non-printing sequences).
94 let mut expanded = result?;
95 expanded.retain(|c| c != '\x01' && c != '\x02');
96
97 Ok(expanded)
98 }
99
100 fn parameter_or_default<'a>(&'a self, name: &str, default: &'a str) -> Cow<'a, str> {
101 self.env_str(name).unwrap_or_else(|| default.into())
102 }
103}