nu_cli/
prompt.rs

1use crate::prompt_update::{
2    POST_PROMPT_MARKER, PRE_PROMPT_MARKER, VSCODE_POST_PROMPT_MARKER, VSCODE_PRE_PROMPT_MARKER,
3};
4use nu_protocol::engine::{EngineState, Stack};
5#[cfg(windows)]
6use nu_utils::enable_vt_processing;
7use reedline::{
8    DefaultPrompt, Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus,
9    PromptViMode,
10};
11use std::borrow::Cow;
12
13/// Nushell prompt definition
14#[derive(Clone)]
15pub struct NushellPrompt {
16    shell_integration_osc133: bool,
17    shell_integration_osc633: bool,
18    left_prompt_string: Option<String>,
19    right_prompt_string: Option<String>,
20    default_prompt_indicator: Option<String>,
21    default_vi_insert_prompt_indicator: Option<String>,
22    default_vi_normal_prompt_indicator: Option<String>,
23    default_multiline_indicator: Option<String>,
24    render_right_prompt_on_last_line: bool,
25    engine_state: EngineState,
26    stack: Stack,
27}
28
29impl NushellPrompt {
30    pub fn new(
31        shell_integration_osc133: bool,
32        shell_integration_osc633: bool,
33        engine_state: EngineState,
34        stack: Stack,
35    ) -> NushellPrompt {
36        NushellPrompt {
37            shell_integration_osc133,
38            shell_integration_osc633,
39            left_prompt_string: None,
40            right_prompt_string: None,
41            default_prompt_indicator: None,
42            default_vi_insert_prompt_indicator: None,
43            default_vi_normal_prompt_indicator: None,
44            default_multiline_indicator: None,
45            render_right_prompt_on_last_line: false,
46            engine_state,
47            stack,
48        }
49    }
50
51    pub fn update_prompt_left(&mut self, prompt_string: Option<String>) {
52        self.left_prompt_string = prompt_string;
53    }
54
55    pub fn update_prompt_right(
56        &mut self,
57        prompt_string: Option<String>,
58        render_right_prompt_on_last_line: bool,
59    ) {
60        self.right_prompt_string = prompt_string;
61        self.render_right_prompt_on_last_line = render_right_prompt_on_last_line;
62    }
63
64    pub fn update_prompt_indicator(&mut self, prompt_indicator_string: Option<String>) {
65        self.default_prompt_indicator = prompt_indicator_string;
66    }
67
68    pub fn update_prompt_vi_insert(&mut self, prompt_vi_insert_string: Option<String>) {
69        self.default_vi_insert_prompt_indicator = prompt_vi_insert_string;
70    }
71
72    pub fn update_prompt_vi_normal(&mut self, prompt_vi_normal_string: Option<String>) {
73        self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
74    }
75
76    pub fn update_prompt_multiline(&mut self, prompt_multiline_indicator_string: Option<String>) {
77        self.default_multiline_indicator = prompt_multiline_indicator_string;
78    }
79
80    pub fn update_all_prompt_strings(
81        &mut self,
82        left_prompt_string: Option<String>,
83        right_prompt_string: Option<String>,
84        prompt_indicator_string: Option<String>,
85        prompt_multiline_indicator_string: Option<String>,
86        prompt_vi: (Option<String>, Option<String>),
87        render_right_prompt_on_last_line: bool,
88    ) {
89        let (prompt_vi_insert_string, prompt_vi_normal_string) = prompt_vi;
90
91        self.left_prompt_string = left_prompt_string;
92        self.right_prompt_string = right_prompt_string;
93        self.default_prompt_indicator = prompt_indicator_string;
94        self.default_multiline_indicator = prompt_multiline_indicator_string;
95
96        self.default_vi_insert_prompt_indicator = prompt_vi_insert_string;
97        self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
98
99        self.render_right_prompt_on_last_line = render_right_prompt_on_last_line;
100    }
101
102    fn default_wrapped_custom_string(&self, str: String) -> String {
103        format!("({str})")
104    }
105}
106
107impl Prompt for NushellPrompt {
108    fn render_prompt_left(&self) -> Cow<'_, str> {
109        #[cfg(windows)]
110        {
111            let _ = enable_vt_processing();
112        }
113
114        if let Some(prompt_string) = &self.left_prompt_string {
115            prompt_string.replace('\n', "\r\n").into()
116        } else {
117            let default = DefaultPrompt::default();
118            let prompt = default
119                .render_prompt_left()
120                .to_string()
121                .replace('\n', "\r\n");
122
123            if self.shell_integration_osc633 {
124                if self
125                    .stack
126                    .get_env_var(&self.engine_state, "TERM_PROGRAM")
127                    .and_then(|v| v.as_str().ok())
128                    == Some("vscode")
129                {
130                    // We're in vscode and we have osc633 enabled
131                    format!("{VSCODE_PRE_PROMPT_MARKER}{prompt}{VSCODE_POST_PROMPT_MARKER}").into()
132                } else if self.shell_integration_osc133 {
133                    // If we're in VSCode but we don't find the env var, but we have osc133 set, then use it
134                    format!("{PRE_PROMPT_MARKER}{prompt}{POST_PROMPT_MARKER}").into()
135                } else {
136                    prompt.into()
137                }
138            } else if self.shell_integration_osc133 {
139                format!("{PRE_PROMPT_MARKER}{prompt}{POST_PROMPT_MARKER}").into()
140            } else {
141                prompt.into()
142            }
143        }
144    }
145
146    fn render_prompt_right(&self) -> Cow<'_, str> {
147        if let Some(prompt_string) = &self.right_prompt_string {
148            prompt_string.replace('\n', "\r\n").into()
149        } else {
150            let default = DefaultPrompt::default();
151            default
152                .render_prompt_right()
153                .to_string()
154                .replace('\n', "\r\n")
155                .into()
156        }
157    }
158
159    fn render_prompt_indicator(&self, edit_mode: PromptEditMode) -> Cow<'_, str> {
160        match edit_mode {
161            PromptEditMode::Default => match &self.default_prompt_indicator {
162                Some(indicator) => indicator,
163                None => "> ",
164            }
165            .into(),
166            PromptEditMode::Emacs => match &self.default_prompt_indicator {
167                Some(indicator) => indicator,
168                None => "> ",
169            }
170            .into(),
171            PromptEditMode::Vi(vi_mode) => match vi_mode {
172                PromptViMode::Normal => match &self.default_vi_normal_prompt_indicator {
173                    Some(indicator) => indicator,
174                    None => "> ",
175                },
176                PromptViMode::Insert => match &self.default_vi_insert_prompt_indicator {
177                    Some(indicator) => indicator,
178                    None => ": ",
179                },
180            }
181            .into(),
182            PromptEditMode::Custom(str) => self.default_wrapped_custom_string(str).into(),
183        }
184    }
185
186    fn render_prompt_multiline_indicator(&self) -> Cow<'_, str> {
187        match &self.default_multiline_indicator {
188            Some(indicator) => indicator,
189            None => "::: ",
190        }
191        .into()
192    }
193
194    fn render_prompt_history_search_indicator(
195        &self,
196        history_search: PromptHistorySearch,
197    ) -> Cow<'_, str> {
198        let prefix = match history_search.status {
199            PromptHistorySearchStatus::Passing => "",
200            PromptHistorySearchStatus::Failing => "failing ",
201        };
202
203        Cow::Owned(format!(
204            "({}reverse-search: {})",
205            prefix, history_search.term
206        ))
207    }
208
209    fn right_prompt_on_last_line(&self) -> bool {
210        self.render_right_prompt_on_last_line
211    }
212}