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#[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 format!("{VSCODE_PRE_PROMPT_MARKER}{prompt}{VSCODE_POST_PROMPT_MARKER}").into()
132 } else if self.shell_integration_osc133 {
133 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}