Skip to main content

nu_cli/
prompt.rs

1#[cfg(windows)]
2use nu_utils::enable_vt_processing;
3use reedline::{
4    DefaultPrompt, Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus,
5    PromptViMode,
6};
7use std::borrow::Cow;
8
9/// Nushell prompt definition
10#[derive(Default, Clone)]
11pub struct NushellPrompt {
12    left_prompt: Option<String>,
13    right_prompt: Option<String>,
14    prompt_indicator: Option<String>,
15    vi_insert_prompt_indicator: Option<String>,
16    vi_normal_prompt_indicator: Option<String>,
17    multiline_indicator: Option<String>,
18    render_right_prompt_on_last_line: bool,
19}
20
21impl NushellPrompt {
22    pub fn new() -> NushellPrompt {
23        NushellPrompt {
24            left_prompt: None,
25            right_prompt: None,
26            prompt_indicator: None,
27            vi_insert_prompt_indicator: None,
28            vi_normal_prompt_indicator: None,
29            multiline_indicator: None,
30            render_right_prompt_on_last_line: false,
31        }
32    }
33
34    pub fn update_prompt_left(&mut self, left_prompt_string: Option<String>) {
35        self.left_prompt = left_prompt_string;
36    }
37
38    pub fn update_prompt_right(
39        &mut self,
40        right_prompt_string: Option<String>,
41        render_right_prompt_on_last_line: bool,
42    ) {
43        self.right_prompt = right_prompt_string;
44        self.render_right_prompt_on_last_line = render_right_prompt_on_last_line;
45    }
46
47    pub fn update_prompt_indicator(&mut self, prompt_indicator_string: Option<String>) {
48        self.prompt_indicator = prompt_indicator_string;
49    }
50
51    pub fn update_prompt_vi_insert(&mut self, prompt_vi_insert_string: Option<String>) {
52        self.vi_insert_prompt_indicator = prompt_vi_insert_string;
53    }
54
55    pub fn update_prompt_vi_normal(&mut self, prompt_vi_normal_string: Option<String>) {
56        self.vi_normal_prompt_indicator = prompt_vi_normal_string;
57    }
58
59    pub fn update_prompt_multiline(&mut self, prompt_multiline_indicator_string: Option<String>) {
60        self.multiline_indicator = prompt_multiline_indicator_string;
61    }
62
63    pub fn update_all_prompt_strings(
64        &mut self,
65        left_prompt_string: Option<String>,
66        right_prompt_string: Option<String>,
67        prompt_indicator_string: Option<String>,
68        prompt_multiline_indicator_string: Option<String>,
69        prompt_vi: (Option<String>, Option<String>),
70        render_right_prompt_on_last_line: bool,
71    ) {
72        let (prompt_vi_insert_string, prompt_vi_normal_string) = prompt_vi;
73
74        self.left_prompt = left_prompt_string;
75        self.right_prompt = right_prompt_string;
76        self.prompt_indicator = prompt_indicator_string;
77        self.multiline_indicator = prompt_multiline_indicator_string;
78        self.vi_insert_prompt_indicator = prompt_vi_insert_string;
79        self.vi_normal_prompt_indicator = prompt_vi_normal_string;
80        self.render_right_prompt_on_last_line = render_right_prompt_on_last_line;
81    }
82
83    fn default_wrapped_custom_string(&self, str: String) -> String {
84        format!("({str})")
85    }
86}
87
88impl Prompt for NushellPrompt {
89    fn render_prompt_left(&self) -> Cow<'_, str> {
90        #[cfg(windows)]
91        {
92            let _ = enable_vt_processing();
93        }
94
95        if let Some(prompt_string) = &self.left_prompt {
96            prompt_string.replace('\n', "\r\n").into()
97        } else {
98            let default = DefaultPrompt::default();
99            default
100                .render_prompt_left()
101                .to_string()
102                .replace('\n', "\r\n")
103                .into()
104        }
105    }
106
107    fn render_prompt_right(&self) -> Cow<'_, str> {
108        if let Some(prompt_string) = &self.right_prompt {
109            prompt_string.replace('\n', "\r\n").into()
110        } else {
111            let default = DefaultPrompt::default();
112            default
113                .render_prompt_right()
114                .to_string()
115                .replace('\n', "\r\n")
116                .into()
117        }
118    }
119
120    fn render_prompt_indicator(&self, edit_mode: PromptEditMode) -> Cow<'_, str> {
121        let indicator: &str = match edit_mode {
122            PromptEditMode::Default => self.prompt_indicator.as_deref().unwrap_or("> "),
123            PromptEditMode::Emacs => self.prompt_indicator.as_deref().unwrap_or("> "),
124            PromptEditMode::Vi(vi_mode) => match vi_mode {
125                PromptViMode::Normal => self.vi_normal_prompt_indicator.as_deref().unwrap_or("> "),
126                PromptViMode::Insert => self.vi_insert_prompt_indicator.as_deref().unwrap_or(": "),
127            },
128            PromptEditMode::Custom(str) => &self.default_wrapped_custom_string(str),
129        };
130
131        indicator.to_string().into()
132    }
133
134    fn render_prompt_multiline_indicator(&self) -> Cow<'_, str> {
135        let indicator = match &self.multiline_indicator {
136            Some(indicator) => indicator.as_str(),
137            None => "::: ",
138        };
139
140        indicator.to_string().into()
141    }
142
143    fn render_prompt_history_search_indicator(
144        &self,
145        history_search: PromptHistorySearch,
146    ) -> Cow<'_, str> {
147        let prefix = match history_search.status {
148            PromptHistorySearchStatus::Passing => "",
149            PromptHistorySearchStatus::Failing => "failing ",
150        };
151
152        Cow::Owned(format!(
153            "({}reverse-search: {})",
154            prefix, history_search.term
155        ))
156    }
157
158    fn right_prompt_on_last_line(&self) -> bool {
159        self.render_right_prompt_on_last_line
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    #[test]
168    fn default_prompt_does_not_embed_osc_markers() {
169        let prompt = NushellPrompt::new();
170        let rendered = prompt.render_prompt_left().to_string();
171
172        assert!(!rendered.contains("\x1b]133;"));
173        assert!(!rendered.contains("\x1b]633;"));
174    }
175}