From 34af84ebe010406a566d4f8b861a7cf44d050513 Mon Sep 17 00:00:00 2001 From: maxomatic458 <104733404+maxomatic458@users.noreply.github.com> Date: Mon, 29 Jan 2024 06:36:58 +0100 Subject: [PATCH] Typed text style (#730) * add match style to MenuTextStyle * cargo fmt * fix external style --- examples/completions.rs | 17 +++++++++ src/menu/columnar_menu.rs | 74 ++++++++++++++++++++++++++------------- src/menu/ide_menu.rs | 57 +++++++++++++++++------------- src/menu/mod.rs | 8 +++++ 4 files changed, 106 insertions(+), 50 deletions(-) diff --git a/examples/completions.rs b/examples/completions.rs index bb80142d..164b5c30 100644 --- a/examples/completions.rs +++ b/examples/completions.rs @@ -36,10 +36,27 @@ fn main() -> io::Result<()> { let commands = vec![ "test".into(), + "clear".into(), + "exit".into(), + "history 1".into(), + "history 2".into(), + "logout".into(), + "login".into(), "hello world".into(), "hello world reedline".into(), + "hello world something".into(), + "hello world another".into(), + "hello world 1".into(), + "hello world 2".into(), + "hello another very large option for hello word that will force one column".into(), "this is the reedline crate".into(), + "abaaabas".into(), + "abaaacas".into(), + "ababac".into(), + "abacaxyc".into(), + "abadarabc".into(), ]; + let completer = Box::new(DefaultCompleter::new_with_wordlen(commands, 2)); // Use the interactive menu to select options from the completer diff --git a/src/menu/columnar_menu.rs b/src/menu/columnar_menu.rs index fb9e3a53..dea2ab22 100644 --- a/src/menu/columnar_menu.rs +++ b/src/menu/columnar_menu.rs @@ -37,6 +37,8 @@ struct ColumnDetails { pub columns: u16, /// Column width pub col_width: usize, + /// The shortest of the strings, which the suggestions are based on + pub shortest_base_string: String, } /// Menu to present suggestions in a columnar fashion @@ -296,18 +298,29 @@ impl ColumnarMenu { use_ansi_coloring: bool, ) -> String { if use_ansi_coloring { + let match_len = self.working_details.shortest_base_string.len(); + + // Split string so the match text can be styled + let (match_str, remaining_str) = suggestion.value.split_at(match_len); + + let suggestion_style_prefix = suggestion + .style + .unwrap_or(self.settings.color.text_style) + .prefix(); + if index == self.index() { if let Some(description) = &suggestion.description { let left_text_size = self.longest_suggestion + self.default_details.col_padding; let right_text_size = self.get_width().saturating_sub(left_text_size); format!( - "{}{}{:max$}{}{}{}{}{}{}", - suggestion - .style - .unwrap_or(self.settings.color.text_style) - .prefix(), + "{}{}{}{}{}{}{:max$}{}{}{}{}{}{}", + suggestion_style_prefix, + self.settings.color.selected_match_style.prefix(), + match_str, + RESET, + suggestion_style_prefix, self.settings.color.selected_text_style.prefix(), - &suggestion.value, + &remaining_str, RESET, self.settings.color.description_style.prefix(), self.settings.color.selected_text_style.prefix(), @@ -322,13 +335,14 @@ impl ColumnarMenu { ) } else { format!( - "{}{}{}{}{:>empty$}{}", - suggestion - .style - .unwrap_or(self.settings.color.text_style) - .prefix(), + "{}{}{}{}{}{}{}{}{:>empty$}{}", + suggestion_style_prefix, + self.settings.color.selected_match_style.prefix(), + match_str, + RESET, + suggestion_style_prefix, self.settings.color.selected_text_style.prefix(), - &suggestion.value, + remaining_str, RESET, "", self.end_of_line(column), @@ -339,12 +353,13 @@ impl ColumnarMenu { let left_text_size = self.longest_suggestion + self.default_details.col_padding; let right_text_size = self.get_width().saturating_sub(left_text_size); format!( - "{}{:max$}{}{}{}{}{}", - suggestion - .style - .unwrap_or(self.settings.color.text_style) - .prefix(), - &suggestion.value, + "{}{}{}{}{}{:max$}{}{}{}{}{}", + suggestion_style_prefix, + self.settings.color.match_style.prefix(), + match_str, + RESET, + suggestion_style_prefix, + remaining_str, RESET, self.settings.color.description_style.prefix(), description @@ -358,12 +373,13 @@ impl ColumnarMenu { ) } else { format!( - "{}{}{}{}{:>empty$}{}{}", - suggestion - .style - .unwrap_or(self.settings.color.text_style) - .prefix(), - &suggestion.value, + "{}{}{}{}{}{}{}{}{:>empty$}{}{}", + suggestion_style_prefix, + self.settings.color.match_style.prefix(), + match_str, + RESET, + suggestion_style_prefix, + remaining_str, RESET, self.settings.color.description_style.prefix(), "", @@ -476,7 +492,15 @@ impl Menu for ColumnarMenu { self.input.as_deref(), self.settings.only_buffer_difference, ); - self.values = completer.complete(&input, pos); + + let (values, base_ranges) = completer.complete_with_base_ranges(&input, pos); + + self.values = values; + self.working_details.shortest_base_string = base_ranges + .iter() + .map(|range| editor.get_buffer()[range.clone()].to_string()) + .min_by_key(|s| s.len()) + .unwrap_or_default(); self.reset_position(); } diff --git a/src/menu/ide_menu.rs b/src/menu/ide_menu.rs index 2c5d42e2..1e36069f 100644 --- a/src/menu/ide_menu.rs +++ b/src/menu/ide_menu.rs @@ -125,9 +125,8 @@ struct IdeMenuDetails { pub space_right: u16, /// Corrected description offset, based on the available space pub description_offset: u16, - /// The ranges of the strings, the suggestions are based on (ranges in [`Editor::get_buffer`]) - /// This is required to adjust the suggestion boxes position, when `correct_cursor_pos` in [`DefaultIdeMenuDetails`] is true - pub base_strings: Vec, + /// The shortest of the strings, which the suggestions are based on + pub shortest_base_string: String, } /// Menu to present suggestions like similar to Ide completion menus @@ -513,31 +512,43 @@ impl IdeMenu { }; if use_ansi_coloring { + let match_len = self.working_details.shortest_base_string.len(); + + // Split string so the match text can be styled + let (match_str, remaining_str) = string.split_at(match_len); + + let suggestion_style_prefix = suggestion + .style + .unwrap_or(self.settings.color.text_style) + .prefix(); + if index == self.index() { format!( - "{}{}{}{}{}{}{}{}", + "{}{}{}{}{}{}{}{}{}{}{}{}", vertical_border, - suggestion - .style - .unwrap_or(self.settings.color.text_style) - .prefix(), - self.settings.color.selected_text_style.prefix(), + suggestion_style_prefix, " ".repeat(padding), - string, + self.settings.color.selected_match_style.prefix(), + match_str, + RESET, + suggestion_style_prefix, + self.settings.color.selected_text_style.prefix(), + remaining_str, " ".repeat(padding_right), RESET, vertical_border, ) } else { format!( - "{}{}{}{}{}{}{}", + "{}{}{}{}{}{}{}{}{}{}{}", vertical_border, - suggestion - .style - .unwrap_or(self.settings.color.text_style) - .prefix(), + suggestion_style_prefix, " ".repeat(padding), - string, + self.settings.color.match_style.prefix(), + match_str, + RESET, + suggestion_style_prefix, + remaining_str, " ".repeat(padding_right), RESET, vertical_border, @@ -623,10 +634,11 @@ impl Menu for IdeMenu { let (values, base_ranges) = completer.complete_with_base_ranges(&input, pos); self.values = values; - self.working_details.base_strings = base_ranges + self.working_details.shortest_base_string = base_ranges .iter() .map(|range| editor.get_buffer()[range.clone()].to_string()) - .collect::>(); + .min_by_key(|s| s.len()) + .unwrap_or_default(); self.reset_position(); } @@ -684,13 +696,8 @@ impl Menu for IdeMenu { let mut cursor_pos = self.working_details.cursor_col; if self.default_details.correct_cursor_pos { - let base_string = self - .working_details - .base_strings - .iter() - .min_by_key(|s| s.len()) - .cloned() - .unwrap_or_default(); + let base_string = &self.working_details.shortest_base_string; + cursor_pos = cursor_pos.saturating_sub(base_string.width() as u16); } diff --git a/src/menu/mod.rs b/src/menu/mod.rs index 0a2eaa18..e313e229 100644 --- a/src/menu/mod.rs +++ b/src/menu/mod.rs @@ -22,6 +22,12 @@ pub struct MenuTextStyle { pub text_style: Style, /// Text style for the item description pub description_style: Style, + /// Text style of the parts of the suggestions that match the + /// typed text when the suggestion is selected + pub selected_match_style: Style, + /// Text style of the parts of the suggestions that match the + /// typed text + pub match_style: Style, } impl Default for MenuTextStyle { @@ -30,6 +36,8 @@ impl Default for MenuTextStyle { selected_text_style: Color::Green.bold().reverse(), text_style: Color::DarkGray.normal(), description_style: Color::Yellow.normal(), + selected_match_style: Color::Green.bold().reverse().underline(), + match_style: Style::default().underline(), } } }