diff --git a/src/lib.rs b/src/lib.rs index b6da798b..6eef8918 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -291,8 +291,9 @@ pub use validator::{DefaultValidator, ValidationResult, Validator}; mod menu; pub use menu::{ - menu_functions, ColumnarMenu, DescriptionMenu, DescriptionMode, IdeMenu, ListMenu, Menu, - MenuBuilder, MenuEvent, MenuSettings, MenuTextStyle, ReedlineMenu, TraversalDirection, + menu_functions, ColumnarMenu, DescriptionMenu, DescriptionMode, DescriptionPosition, IdeMenu, + ListMenu, Menu, MenuBuilder, MenuEvent, MenuSettings, MenuTextStyle, ReedlineMenu, + TraversalDirection, }; mod terminal_extensions; diff --git a/src/menu/list_menu.rs b/src/menu/list_menu.rs index 071d5ff5..b0d5d045 100644 --- a/src/menu/list_menu.rs +++ b/src/menu/list_menu.rs @@ -13,6 +13,19 @@ use { const SELECTION_CHAR: char = '!'; +/// Controls where the description is rendered relative to the completion value +/// in a [`ListMenu`] row. +#[derive(Clone, Default, Debug, PartialEq, Eq)] +pub enum DescriptionPosition { + /// Description is shown **before** the value, wrapped in parentheses: + /// `(description) value` — the original behaviour. + #[default] + Before, + /// Description is shown **after** the value with a leading space: + /// `value description` + After, +} + struct Page { size: usize, full: bool, @@ -67,6 +80,8 @@ pub struct ListMenu { event: Option, /// String collected after the menu is activated input: Option, + /// Controls where the description is rendered relative to the completion value + description_position: DescriptionPosition, } impl Default for ListMenu { @@ -87,6 +102,7 @@ impl Default for ListMenu { pages: Vec::new(), event: None, input: None, + description_position: DescriptionPosition::default(), } } } @@ -113,6 +129,15 @@ impl ListMenu { self.max_lines = max_lines; self } + + /// Menu builder to set where descriptions are rendered relative to the + /// completion value. Defaults to [`DescriptionPosition::Before`] for + /// backwards compatibility. + #[must_use] + pub fn with_description_position(mut self, position: DescriptionPosition) -> Self { + self.description_position = position; + self + } } // Menu functionality @@ -265,40 +290,80 @@ impl ListMenu { row_number: &str, use_ansi_coloring: bool, ) -> String { - let description = description.map_or("".to_string(), |desc| { - if use_ansi_coloring { - format!( - "{}({}) {}", - self.settings.color.description_style.prefix(), - desc, - RESET - ) - } else { - format!("({desc}) ") - } - }); + match self.description_position { + DescriptionPosition::Before => { + let description = description.map_or("".to_string(), |desc| { + if use_ansi_coloring { + format!( + "{}({}) ", + self.settings.color.description_style.prefix(), + desc, + ) + } else { + format!("({desc}) ") + } + }); + + if use_ansi_coloring { + format!( + "{}{}{}{}{}{}", + row_number, + description, + self.text_style(index), + &line, + RESET, + Self::end_of_line(), + ) + } else { + // If no ansi coloring is found, then the selection word is + // the line in uppercase + let line_str = if index == self.index() { + format!("{}{}>{}", row_number, description, line.to_uppercase()) + } else { + format!("{row_number}{description}{line}") + }; - if use_ansi_coloring { - format!( - "{}{}{}{}{}{}", - row_number, - description, - self.text_style(index), - &line, - RESET, - Self::end_of_line(), - ) - } else { - // If no ansi coloring is found, then the selection word is - // the line in uppercase - let line_str = if index == self.index() { - format!("{}{}>{}", row_number, description, line.to_uppercase()) - } else { - format!("{row_number}{description}{line}") - }; + // Final string with formatting + format!("{}{}", line_str, Self::end_of_line()) + } + } + DescriptionPosition::After => { + let description = description.map_or("".to_string(), |desc| { + if use_ansi_coloring { + format!( + " {}{}{}", + self.settings.color.description_style.prefix(), + desc, + RESET + ) + } else { + format!(" {desc}") + } + }); + + if use_ansi_coloring { + format!( + "{}{}{}{}{}{}", + row_number, + self.text_style(index), + &line, + RESET, + description, + Self::end_of_line(), + ) + } else { + // If no ansi coloring is found, then the selection word is + // the line in uppercase + let line_str = if index == self.index() { + format!("{}>{}{}", row_number, line.to_uppercase(), description) + } else { + format!("{row_number}{line}{description}") + }; - // Final string with formatting - format!("{}{}", line_str, Self::end_of_line()) + // Final string with formatting + format!("{}{}", line_str, Self::end_of_line()) + } + } } } } diff --git a/src/menu/mod.rs b/src/menu/mod.rs index 8d1fc165..4db656f7 100644 --- a/src/menu/mod.rs +++ b/src/menu/mod.rs @@ -12,6 +12,7 @@ pub use columnar_menu::TraversalDirection; pub use description_menu::DescriptionMenu; pub use ide_menu::DescriptionMode; pub use ide_menu::IdeMenu; +pub use list_menu::DescriptionPosition; pub use list_menu::ListMenu; use nu_ansi_term::{Color, Style};