1use {
2 crate::core_editor::{RestPolicy, SelectionExtent},
3 nu_ansi_term::Color,
4 serde::{Deserialize, Serialize},
5 std::{
6 borrow::Cow,
7 fmt::{Display, Formatter},
8 },
9 strum::{EnumIter, EnumString, IntoDiscriminant},
10};
11
12pub static DEFAULT_PROMPT_COLOR: Color = Color::LightGreen;
20pub static DEFAULT_PROMPT_MULTILINE_COLOR: Color = Color::LightBlue;
22pub static DEFAULT_INDICATOR_COLOR: Color = Color::LightCyan;
24pub static DEFAULT_PROMPT_RIGHT_COLOR: Color = Color::Purple;
26
27pub enum PromptHistorySearchStatus {
29 Passing,
31
32 Failing,
34}
35
36pub struct PromptHistorySearch {
38 pub status: PromptHistorySearchStatus,
40
41 pub term: String,
43}
44
45impl PromptHistorySearch {
46 pub const fn new(status: PromptHistorySearchStatus, search_term: String) -> Self {
48 PromptHistorySearch {
49 status,
50 term: search_term,
51 }
52 }
53}
54
55#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq)]
57pub enum PromptEditMode {
58 #[default]
60 Default,
61
62 Emacs,
64
65 Vi(PromptViMode),
67
68 #[cfg(feature = "helix")]
70 Helix(PromptHelixMode),
71
72 Custom(String),
74}
75
76impl PromptEditMode {
77 pub(crate) fn rest_policy(&self) -> RestPolicy {
78 match self {
79 PromptEditMode::Vi(PromptViMode::Normal) => RestPolicy::OnGrapheme,
80 PromptEditMode::Vi(PromptViMode::Visual) => RestPolicy::Block,
83 #[cfg(feature = "helix")]
86 PromptEditMode::Helix(PromptHelixMode::Normal)
87 | PromptEditMode::Helix(PromptHelixMode::Select) => RestPolicy::BlockOverNewline,
88 #[cfg(feature = "helix")]
89 PromptEditMode::Helix(PromptHelixMode::Insert) => RestPolicy::Between,
90 PromptEditMode::Vi(PromptViMode::Insert)
91 | PromptEditMode::Default
92 | PromptEditMode::Emacs => RestPolicy::Between,
93 PromptEditMode::Custom(_) => RestPolicy::Between,
98 }
99 }
100
101 pub(crate) fn retains_selection_after_edit(&self) -> bool {
108 match self {
109 #[cfg(feature = "helix")]
110 PromptEditMode::Helix(_) => true,
111 PromptEditMode::Vi(_)
112 | PromptEditMode::Default
113 | PromptEditMode::Emacs
114 | PromptEditMode::Custom(_) => false,
115 }
116 }
117
118 pub(crate) fn selection_extent(&self) -> SelectionExtent {
119 match self {
120 PromptEditMode::Vi(_) => SelectionExtent::CoverLanding,
123 #[cfg(feature = "helix")]
128 PromptEditMode::Helix(_) => SelectionExtent::Span,
129 PromptEditMode::Default | PromptEditMode::Emacs | PromptEditMode::Custom(_) => {
130 SelectionExtent::Span
131 }
132 }
133 }
134}
135
136#[derive(Serialize, Deserialize, Clone, Debug, EnumIter, Default, PartialEq, Eq)]
138pub enum PromptViMode {
139 #[default]
141 Normal,
142
143 Insert,
145
146 Visual,
149}
150
151#[cfg(feature = "helix")]
153#[derive(Serialize, Deserialize, Clone, Debug, EnumIter, Default, PartialEq, Eq)]
154pub enum PromptHelixMode {
155 #[default]
159 Normal,
160
161 Insert,
163
164 Select,
167}
168
169#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, EnumIter, EnumString)]
171#[strum(ascii_case_insensitive)]
172pub enum PromptEditModeDiscriminants {
173 #[default]
175 Default,
176
177 Emacs,
179
180 #[strum(serialize = "ViNormal", serialize = "vi_normal")]
182 ViNormal,
183
184 #[strum(serialize = "ViInsert", serialize = "vi_insert")]
186 ViInsert,
187
188 #[cfg(feature = "helix")]
190 #[strum(serialize = "HelixNormal", serialize = "helix_normal")]
191 HelixNormal,
192
193 #[cfg(feature = "helix")]
195 #[strum(serialize = "HelixInsert", serialize = "helix_insert")]
196 HelixInsert,
197
198 #[cfg(feature = "helix")]
200 #[strum(serialize = "HelixSelect", serialize = "helix_select")]
201 HelixSelect,
202
203 Custom,
205}
206
207impl From<PromptViMode> for PromptEditMode {
208 fn from(value: PromptViMode) -> Self {
209 Self::Vi(value)
210 }
211}
212
213impl Display for PromptEditMode {
214 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
215 #[cfg(feature = "helix")]
216 use PromptHelixMode as Helix;
217 use PromptViMode as Vi;
218 match self {
219 Self::Default => write!(f, "Default"),
220 Self::Emacs => write!(f, "Emacs"),
221 Self::Vi(Vi::Normal) => write!(f, "Vi_Normal"),
222 Self::Vi(Vi::Insert) => write!(f, "Vi_Insert"),
223 Self::Vi(Vi::Visual) => write!(f, "Vi_Visual"),
224 #[cfg(feature = "helix")]
225 Self::Helix(Helix::Normal) => write!(f, "Helix_Normal"),
226 #[cfg(feature = "helix")]
227 Self::Helix(Helix::Insert) => write!(f, "Helix_Insert"),
228 #[cfg(feature = "helix")]
229 Self::Helix(Helix::Select) => write!(f, "Helix_Select"),
230 Self::Custom(s) => write!(f, "Custom_{s}"),
231 }
232 }
233}
234
235impl IntoDiscriminant for PromptEditMode {
236 type Discriminant = PromptEditModeDiscriminants;
237
238 fn discriminant(&self) -> Self::Discriminant {
239 #[cfg(feature = "helix")]
240 use PromptHelixMode as Helix;
241 use PromptViMode as Vi;
242 match self {
243 Self::Default => Self::Discriminant::Default,
244 Self::Emacs => Self::Discriminant::Emacs,
245 Self::Vi(Vi::Normal | Vi::Visual) => Self::Discriminant::ViNormal,
249 Self::Vi(Vi::Insert) => Self::Discriminant::ViInsert,
250 #[cfg(feature = "helix")]
251 Self::Helix(Helix::Normal) => Self::Discriminant::HelixNormal,
252 #[cfg(feature = "helix")]
253 Self::Helix(Helix::Insert) => Self::Discriminant::HelixInsert,
254 #[cfg(feature = "helix")]
255 Self::Helix(Helix::Select) => Self::Discriminant::HelixSelect,
256 Self::Custom(_) => Self::Discriminant::Custom,
257 }
258 }
259}
260
261pub trait Prompt: Send {
266 fn render_prompt_left(&self) -> Cow<'_, str>;
268 fn render_prompt_right(&self) -> Cow<'_, str>;
270 fn render_prompt_indicator(&self, prompt_mode: PromptEditMode) -> Cow<'_, str>;
272 fn render_prompt_multiline_indicator(&self) -> Cow<'_, str>;
274 fn render_prompt_history_search_indicator(
276 &self,
277 history_search: PromptHistorySearch,
278 ) -> Cow<'_, str>;
279 fn get_prompt_color(&self) -> Color {
281 DEFAULT_PROMPT_COLOR
282 }
283 fn get_prompt_multiline_color(&self) -> Color {
285 DEFAULT_PROMPT_MULTILINE_COLOR
286 }
287 fn get_indicator_color(&self) -> Color {
289 DEFAULT_INDICATOR_COLOR
290 }
291 fn get_prompt_right_color(&self) -> Color {
293 DEFAULT_PROMPT_RIGHT_COLOR
294 }
295
296 fn right_prompt_on_last_line(&self) -> bool {
298 false
299 }
300}
301
302#[cfg(test)]
303mod test {
304 use super::*;
305
306 #[test]
307 fn selection_extent_maps_vi_to_cover_landing_and_bar_modes_to_span() {
308 use PromptViMode::{Insert, Normal, Visual};
314 for mode in [Normal, Insert, Visual] {
315 assert_eq!(
316 PromptEditMode::Vi(mode).selection_extent(),
317 SelectionExtent::CoverLanding,
318 );
319 }
320 assert_eq!(
321 PromptEditMode::Emacs.selection_extent(),
322 SelectionExtent::Span,
323 );
324 assert_eq!(
325 PromptEditMode::Default.selection_extent(),
326 SelectionExtent::Span,
327 );
328 assert_eq!(
329 PromptEditMode::Custom("anything".into()).selection_extent(),
330 SelectionExtent::Span,
331 );
332 }
333
334 #[cfg(feature = "helix")]
335 #[test]
336 fn each_helix_mode_gets_its_own_discriminant() {
337 use PromptEditModeDiscriminants as D;
338 use PromptHelixMode::{Insert, Normal, Select};
339 for (mode, expected) in [
340 (Normal, D::HelixNormal),
341 (Insert, D::HelixInsert),
342 (Select, D::HelixSelect),
343 ] {
344 assert_eq!(PromptEditMode::Helix(mode).discriminant(), expected);
345 }
346 }
347
348 #[test]
349 fn vi_visual_still_shares_the_normal_discriminant() {
350 use PromptEditModeDiscriminants as D;
352 assert_eq!(
353 PromptEditMode::Vi(PromptViMode::Visual).discriminant(),
354 D::ViNormal,
355 );
356 assert_eq!(
357 PromptEditMode::Vi(PromptViMode::Normal).discriminant(),
358 D::ViNormal,
359 );
360 }
361
362 #[cfg(feature = "helix")]
363 #[test]
364 fn helix_discriminants_round_trip_through_their_names() {
365 use std::str::FromStr;
366 use PromptEditModeDiscriminants as D;
367 for (name, expected) in [
368 ("helix_normal", D::HelixNormal),
369 ("helix_insert", D::HelixInsert),
370 ("helix_select", D::HelixSelect),
371 ("HelixSelect", D::HelixSelect),
372 ] {
373 assert_eq!(D::from_str(name), Ok(expected), "{name}");
374 }
375 }
376}