use {
crate::core_editor::{RestPolicy, SelectionExtent},
nu_ansi_term::Color,
serde::{Deserialize, Serialize},
std::{
borrow::Cow,
fmt::{Display, Formatter},
},
strum::{EnumIter, EnumString, IntoDiscriminant},
};
pub static DEFAULT_PROMPT_COLOR: Color = Color::LightGreen;
pub static DEFAULT_PROMPT_MULTILINE_COLOR: Color = Color::LightBlue;
pub static DEFAULT_INDICATOR_COLOR: Color = Color::LightCyan;
pub static DEFAULT_PROMPT_RIGHT_COLOR: Color = Color::Purple;
pub enum PromptHistorySearchStatus {
Passing,
Failing,
}
pub struct PromptHistorySearch {
pub status: PromptHistorySearchStatus,
pub term: String,
}
impl PromptHistorySearch {
pub const fn new(status: PromptHistorySearchStatus, search_term: String) -> Self {
PromptHistorySearch {
status,
term: search_term,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq)]
pub enum PromptEditMode {
#[default]
Default,
Emacs,
Vi(PromptViMode),
#[cfg(feature = "helix")]
Helix(PromptHelixMode),
Custom(String),
}
impl PromptEditMode {
pub(crate) fn rest_policy(&self) -> RestPolicy {
match self {
PromptEditMode::Vi(PromptViMode::Normal) => RestPolicy::OnGrapheme,
PromptEditMode::Vi(PromptViMode::Visual) => RestPolicy::Block,
#[cfg(feature = "helix")]
PromptEditMode::Helix(PromptHelixMode::Normal)
| PromptEditMode::Helix(PromptHelixMode::Select) => RestPolicy::BlockOverNewline,
#[cfg(feature = "helix")]
PromptEditMode::Helix(PromptHelixMode::Insert) => RestPolicy::Between,
PromptEditMode::Vi(PromptViMode::Insert)
| PromptEditMode::Default
| PromptEditMode::Emacs => RestPolicy::Between,
PromptEditMode::Custom(_) => RestPolicy::Between,
}
}
pub(crate) fn retains_selection_after_edit(&self) -> bool {
match self {
#[cfg(feature = "helix")]
PromptEditMode::Helix(_) => true,
PromptEditMode::Vi(_)
| PromptEditMode::Default
| PromptEditMode::Emacs
| PromptEditMode::Custom(_) => false,
}
}
pub(crate) fn selection_extent(&self) -> SelectionExtent {
match self {
PromptEditMode::Vi(_) => SelectionExtent::CoverLanding,
#[cfg(feature = "helix")]
PromptEditMode::Helix(_) => SelectionExtent::Span,
PromptEditMode::Default | PromptEditMode::Emacs | PromptEditMode::Custom(_) => {
SelectionExtent::Span
}
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, EnumIter, Default, PartialEq, Eq)]
pub enum PromptViMode {
#[default]
Normal,
Insert,
Visual,
}
#[cfg(feature = "helix")]
#[derive(Serialize, Deserialize, Clone, Debug, EnumIter, Default, PartialEq, Eq)]
pub enum PromptHelixMode {
#[default]
Normal,
Insert,
Select,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, EnumIter, EnumString)]
#[strum(ascii_case_insensitive)]
pub enum PromptEditModeDiscriminants {
#[default]
Default,
Emacs,
#[strum(serialize = "ViNormal", serialize = "vi_normal")]
ViNormal,
#[strum(serialize = "ViInsert", serialize = "vi_insert")]
ViInsert,
#[cfg(feature = "helix")]
#[strum(serialize = "HelixNormal", serialize = "helix_normal")]
HelixNormal,
#[cfg(feature = "helix")]
#[strum(serialize = "HelixInsert", serialize = "helix_insert")]
HelixInsert,
#[cfg(feature = "helix")]
#[strum(serialize = "HelixSelect", serialize = "helix_select")]
HelixSelect,
Custom,
}
impl From<PromptViMode> for PromptEditMode {
fn from(value: PromptViMode) -> Self {
Self::Vi(value)
}
}
impl Display for PromptEditMode {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
#[cfg(feature = "helix")]
use PromptHelixMode as Helix;
use PromptViMode as Vi;
match self {
Self::Default => write!(f, "Default"),
Self::Emacs => write!(f, "Emacs"),
Self::Vi(Vi::Normal) => write!(f, "Vi_Normal"),
Self::Vi(Vi::Insert) => write!(f, "Vi_Insert"),
Self::Vi(Vi::Visual) => write!(f, "Vi_Visual"),
#[cfg(feature = "helix")]
Self::Helix(Helix::Normal) => write!(f, "Helix_Normal"),
#[cfg(feature = "helix")]
Self::Helix(Helix::Insert) => write!(f, "Helix_Insert"),
#[cfg(feature = "helix")]
Self::Helix(Helix::Select) => write!(f, "Helix_Select"),
Self::Custom(s) => write!(f, "Custom_{s}"),
}
}
}
impl IntoDiscriminant for PromptEditMode {
type Discriminant = PromptEditModeDiscriminants;
fn discriminant(&self) -> Self::Discriminant {
#[cfg(feature = "helix")]
use PromptHelixMode as Helix;
use PromptViMode as Vi;
match self {
Self::Default => Self::Discriminant::Default,
Self::Emacs => Self::Discriminant::Emacs,
Self::Vi(Vi::Normal | Vi::Visual) => Self::Discriminant::ViNormal,
Self::Vi(Vi::Insert) => Self::Discriminant::ViInsert,
#[cfg(feature = "helix")]
Self::Helix(Helix::Normal) => Self::Discriminant::HelixNormal,
#[cfg(feature = "helix")]
Self::Helix(Helix::Insert) => Self::Discriminant::HelixInsert,
#[cfg(feature = "helix")]
Self::Helix(Helix::Select) => Self::Discriminant::HelixSelect,
Self::Custom(_) => Self::Discriminant::Custom,
}
}
}
pub trait Prompt: Send {
fn render_prompt_left(&self) -> Cow<'_, str>;
fn render_prompt_right(&self) -> Cow<'_, str>;
fn render_prompt_indicator(&self, prompt_mode: PromptEditMode) -> Cow<'_, str>;
fn render_prompt_multiline_indicator(&self) -> Cow<'_, str>;
fn render_prompt_history_search_indicator(
&self,
history_search: PromptHistorySearch,
) -> Cow<'_, str>;
fn get_prompt_color(&self) -> Color {
DEFAULT_PROMPT_COLOR
}
fn get_prompt_multiline_color(&self) -> Color {
DEFAULT_PROMPT_MULTILINE_COLOR
}
fn get_indicator_color(&self) -> Color {
DEFAULT_INDICATOR_COLOR
}
fn get_prompt_right_color(&self) -> Color {
DEFAULT_PROMPT_RIGHT_COLOR
}
fn right_prompt_on_last_line(&self) -> bool {
false
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn selection_extent_maps_vi_to_cover_landing_and_bar_modes_to_span() {
use PromptViMode::{Insert, Normal, Visual};
for mode in [Normal, Insert, Visual] {
assert_eq!(
PromptEditMode::Vi(mode).selection_extent(),
SelectionExtent::CoverLanding,
);
}
assert_eq!(
PromptEditMode::Emacs.selection_extent(),
SelectionExtent::Span,
);
assert_eq!(
PromptEditMode::Default.selection_extent(),
SelectionExtent::Span,
);
assert_eq!(
PromptEditMode::Custom("anything".into()).selection_extent(),
SelectionExtent::Span,
);
}
#[cfg(feature = "helix")]
#[test]
fn each_helix_mode_gets_its_own_discriminant() {
use PromptEditModeDiscriminants as D;
use PromptHelixMode::{Insert, Normal, Select};
for (mode, expected) in [
(Normal, D::HelixNormal),
(Insert, D::HelixInsert),
(Select, D::HelixSelect),
] {
assert_eq!(PromptEditMode::Helix(mode).discriminant(), expected);
}
}
#[test]
fn vi_visual_still_shares_the_normal_discriminant() {
use PromptEditModeDiscriminants as D;
assert_eq!(
PromptEditMode::Vi(PromptViMode::Visual).discriminant(),
D::ViNormal,
);
assert_eq!(
PromptEditMode::Vi(PromptViMode::Normal).discriminant(),
D::ViNormal,
);
}
#[cfg(feature = "helix")]
#[test]
fn helix_discriminants_round_trip_through_their_names() {
use std::str::FromStr;
use PromptEditModeDiscriminants as D;
for (name, expected) in [
("helix_normal", D::HelixNormal),
("helix_insert", D::HelixInsert),
("helix_select", D::HelixSelect),
("HelixSelect", D::HelixSelect),
] {
assert_eq!(D::from_str(name), Ok(expected), "{name}");
}
}
}