From f4c434a8031c50a5a908d68d3121b66971a1dfd8 Mon Sep 17 00:00:00 2001 From: Beh Date: Sun, 6 Mar 2022 17:32:21 +0100 Subject: [PATCH] Add Fuzzy Select match highlighting --- src/prompts/fuzzy_select.rs | 18 +++++++- src/theme.rs | 88 +++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/src/prompts/fuzzy_select.rs b/src/prompts/fuzzy_select.rs index e17b4908..3f43f911 100644 --- a/src/prompts/fuzzy_select.rs +++ b/src/prompts/fuzzy_select.rs @@ -39,6 +39,7 @@ pub struct FuzzySelect<'a> { prompt: String, report: bool, clear: bool, + highlight_matches: bool, theme: &'a dyn Theme, } @@ -101,6 +102,14 @@ impl FuzzySelect<'_> { self } + /// Indicates whether to highlight matched indices + /// + /// The default is to highlight the indices + pub fn highlight_matches(&mut self, val: bool) -> &mut Self { + self.highlight_matches = val; + self + } + /// Enables user interaction and returns the result. /// /// The user can select the items using 'Enter' and the index of selected item will be returned. @@ -180,7 +189,13 @@ impl FuzzySelect<'_> { .skip(starting_row) .take(visible_term_rows) { - render.select_prompt_item(item, idx == sel)?; + render.fuzzy_select_prompt_item( + item, + idx == sel, + self.highlight_matches, + &matcher, + &search_term, + )?; term.flush()?; } @@ -276,6 +291,7 @@ impl<'a> FuzzySelect<'a> { prompt: "".into(), report: true, clear: true, + highlight_matches: true, theme, } } diff --git a/src/theme.rs b/src/theme.rs index 43ea7a01..fb814775 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -2,6 +2,8 @@ use std::{fmt, io}; use console::{style, Style, StyledObject, Term}; +#[cfg(feature = "fuzzy-select")] +use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher}; /// Implements a theme for dialoguer. pub trait Theme { @@ -206,6 +208,36 @@ pub trait Theme { ) } + /// Formats a fuzzy select prompt item. + #[cfg(feature = "fuzzy-select")] + fn format_fuzzy_select_prompt_item( + &self, + f: &mut dyn fmt::Write, + text: &str, + active: bool, + highlight_matches: bool, + matcher: &SkimMatcherV2, + search_term: &str, + ) -> fmt::Result { + write!(f, "{} ", if active { ">" } else { " " })?; + + if highlight_matches { + if let Some((_score, indices)) = matcher.fuzzy_indices(text, &search_term) { + for (idx, c) in text.chars().into_iter().enumerate() { + if indices.contains(&idx) { + write!(f, "{}", style(c).for_stderr().bold())?; + } else { + write!(f, "{}", c)?; + } + } + + return Ok(()); + } + } + + write!(f, "{}", text) + } + /// Formats a fuzzy select prompt. #[cfg(feature = "fuzzy-select")] fn format_fuzzy_select_prompt( @@ -277,6 +309,9 @@ pub struct ColorfulTheme { /// Formats the cursor for a fuzzy select prompt #[cfg(feature = "fuzzy-select")] pub fuzzy_cursor_style: Style, + // Formats the highlighting if matched characters + #[cfg(feature = "fuzzy-select")] + pub fuzzy_match_highlight_style: Style, /// Show the selections from certain prompts inline pub inline_selections: bool, } @@ -304,6 +339,8 @@ impl Default for ColorfulTheme { unpicked_item_prefix: style(" ".to_string()).for_stderr(), #[cfg(feature = "fuzzy-select")] fuzzy_cursor_style: Style::new().for_stderr().black().on_white(), + #[cfg(feature = "fuzzy-select")] + fuzzy_match_highlight_style: Style::new().for_stderr().bold().yellow(), inline_selections: true, } } @@ -577,6 +614,36 @@ impl Theme for ColorfulTheme { write!(f, "{} {}", details.0, details.1) } + /// Formats a fuzzy select prompt item. + #[cfg(feature = "fuzzy-select")] + fn format_fuzzy_select_prompt_item( + &self, + f: &mut dyn fmt::Write, + text: &str, + active: bool, + highlight_matches: bool, + matcher: &SkimMatcherV2, + search_term: &str, + ) -> fmt::Result { + write!(f, "{} ", if active { ">" } else { " " })?; + + if highlight_matches { + if let Some((_score, indices)) = matcher.fuzzy_indices(text, &search_term) { + for (idx, c) in text.chars().into_iter().enumerate() { + if indices.contains(&idx) { + write!(f, "{}", self.fuzzy_match_highlight_style.apply_to(c))?; + } else { + write!(f, "{}", c)?; + } + } + + return Ok(()); + } + } + + write!(f, "{}", text) + } + /// Formats a fuzzy-selectprompt after selection. #[cfg(feature = "fuzzy-select")] fn format_fuzzy_select_prompt( @@ -775,6 +842,27 @@ impl<'a> TermThemeRenderer<'a> { }) } + #[cfg(feature = "fuzzy-select")] + pub fn fuzzy_select_prompt_item( + &mut self, + text: &str, + active: bool, + highlight: bool, + matcher: &SkimMatcherV2, + search_term: &str, + ) -> io::Result<()> { + self.write_formatted_line(|this, buf| { + this.theme.format_fuzzy_select_prompt_item( + buf, + text, + active, + highlight, + matcher, + search_term, + ) + }) + } + pub fn multi_select_prompt( &mut self, prompt: &str,