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#[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}