From 6c69420972d152cce86fdc1a7e7f1dc0d4a76e9c Mon Sep 17 00:00:00 2001 From: Beh Date: Mon, 7 Mar 2022 11:43:07 +0100 Subject: [PATCH 1/7] Rename position to cursor_pos --- src/prompts/fuzzy_select.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/prompts/fuzzy_select.rs b/src/prompts/fuzzy_select.rs index e17b4908..1de8a0e0 100644 --- a/src/prompts/fuzzy_select.rs +++ b/src/prompts/fuzzy_select.rs @@ -137,7 +137,7 @@ impl FuzzySelect<'_> { /// Like `interact` but allows a specific terminal to be set. fn _interact_on(&self, term: &Term, allow_quit: bool) -> io::Result> { - let mut position = 0; + let mut cursor_pos = 0; let mut search_term = String::new(); let mut render = TermThemeRenderer::new(term, self.theme); @@ -161,7 +161,7 @@ impl FuzzySelect<'_> { loop { render.clear()?; - render.fuzzy_select_prompt(self.prompt.as_str(), &search_term, position)?; + render.fuzzy_select_prompt(self.prompt.as_str(), &search_term, cursor_pos)?; // Maps all items to a tuple of item and its match score. let mut filtered_list = self @@ -221,12 +221,12 @@ impl FuzzySelect<'_> { } term.flush()?; } - Key::ArrowLeft if position > 0 => { - position -= 1; + Key::ArrowLeft if cursor_pos > 0 => { + cursor_pos -= 1; term.flush()?; } - Key::ArrowRight if position < search_term.len() => { - position += 1; + Key::ArrowRight if cursor_pos < search_term.len() => { + cursor_pos += 1; term.flush()?; } Key::Enter if !filtered_list.is_empty() => { @@ -246,14 +246,14 @@ impl FuzzySelect<'_> { term.show_cursor()?; return Ok(sel_string_pos_in_items); } - Key::Backspace if position > 0 => { - position -= 1; - search_term.remove(position); + Key::Backspace if cursor_pos > 0 => { + cursor_pos -= 1; + search_term.remove(cursor_pos); term.flush()?; } Key::Char(chr) if !chr.is_ascii_control() => { - search_term.insert(position, chr); - position += 1; + search_term.insert(cursor_pos, chr); + cursor_pos += 1; term.flush()?; sel = 0; starting_row = 0; From 6c4ebb141ba61fe84022b6b4f661ba4f408bdfc0 Mon Sep 17 00:00:00 2001 From: Beh Date: Mon, 7 Mar 2022 13:05:16 +0100 Subject: [PATCH 2/7] Add words with umlauts to example --- examples/fuzzyselect.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/fuzzyselect.rs b/examples/fuzzyselect.rs index a00fb6b1..248465c2 100644 --- a/examples/fuzzyselect.rs +++ b/examples/fuzzyselect.rs @@ -30,6 +30,9 @@ fn main() { "Brown sugar", "Blueberry", "Burger", + "Käse", + "Döner", + "Blåbär" ]; let selection = FuzzySelect::with_theme(&ColorfulTheme::default()) From 58bdc28c914326972772271f926f22ea2d21ca81 Mon Sep 17 00:00:00 2001 From: Beh Date: Mon, 7 Mar 2022 15:11:10 +0100 Subject: [PATCH 3/7] Use unicode segmentation for displaying and segmenting fuzzy select prompt search term --- Cargo.toml | 3 ++- src/prompts/fuzzy_select.rs | 11 ++++++++--- src/theme.rs | 31 ++++++++++++++++++------------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ea47af5d..75551fa7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ readme = "README.md" [features] default = ["editor", "password"] editor = ["tempfile"] -fuzzy-select = ["fuzzy-matcher"] +fuzzy-select = ["fuzzy-matcher", "unicode-segmentation"] history = [] password = ["zeroize"] completion = [] @@ -28,6 +28,7 @@ console = "0.15.0" tempfile = { version = "3", optional = true } zeroize = { version = "1.1.1", optional = true } fuzzy-matcher = { version = "0.3.7", optional = true } +unicode-segmentation = { version = "1.9.0", optional = true } [[example]] name = "password" diff --git a/src/prompts/fuzzy_select.rs b/src/prompts/fuzzy_select.rs index 1de8a0e0..c66f8087 100644 --- a/src/prompts/fuzzy_select.rs +++ b/src/prompts/fuzzy_select.rs @@ -2,6 +2,7 @@ use crate::theme::{SimpleTheme, TermThemeRenderer, Theme}; use console::{Key, Term}; use fuzzy_matcher::FuzzyMatcher; use std::{io, ops::Rem}; +use unicode_segmentation::UnicodeSegmentation; /// Renders a selection menu that user can fuzzy match to reduce set. /// @@ -137,8 +138,9 @@ impl FuzzySelect<'_> { /// Like `interact` but allows a specific terminal to be set. fn _interact_on(&self, term: &Term, allow_quit: bool) -> io::Result> { + // This cursor iterates over the graphemes vec rather than the search term let mut cursor_pos = 0; - let mut search_term = String::new(); + let mut search_term: Vec = Vec::new(); let mut render = TermThemeRenderer::new(term, self.theme); let mut sel = self.default; @@ -160,6 +162,9 @@ impl FuzzySelect<'_> { term.hide_cursor()?; loop { + let concatted_search_term = search_term.concat(); + search_term = concatted_search_term.graphemes(true).map(|s| s.to_string()).collect::>(); + render.clear()?; render.fuzzy_select_prompt(self.prompt.as_str(), &search_term, cursor_pos)?; @@ -167,7 +172,7 @@ impl FuzzySelect<'_> { let mut filtered_list = self .items .iter() - .map(|item| (item, matcher.fuzzy_match(item, &search_term))) + .map(|item| (item, matcher.fuzzy_match(item, &search_term.concat()))) .filter_map(|(item, score)| score.map(|s| (item, s))) .collect::>(); @@ -252,7 +257,7 @@ impl FuzzySelect<'_> { term.flush()?; } Key::Char(chr) if !chr.is_ascii_control() => { - search_term.insert(cursor_pos, chr); + search_term.insert(cursor_pos, chr.to_string()); cursor_pos += 1; term.flush()?; sel = 0; diff --git a/src/theme.rs b/src/theme.rs index 43ea7a01..1c0a0f68 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -212,7 +212,7 @@ pub trait Theme { &self, f: &mut dyn fmt::Write, prompt: &str, - search_term: &str, + search_term: &Vec, cursor_pos: usize, ) -> fmt::Result { if !prompt.is_empty() { @@ -220,13 +220,15 @@ pub trait Theme { } if cursor_pos < search_term.len() { - let st_head = search_term[0..cursor_pos].to_string(); - let st_tail = search_term[cursor_pos..search_term.len()].to_string(); - let st_cursor = "|".to_string(); - write!(f, "{}{}{}", st_head, st_cursor, st_tail) + let split = search_term.split_at(cursor_pos); + let head = split.0.concat(); + let cursor = "|".to_string(); + let tail = split.1.concat(); + + write!(f, "{}{}{}", head, cursor, tail) } else { let cursor = "|".to_string(); - write!(f, "{}{}", search_term.to_string(), cursor) + write!(f, "{}{}", search_term.concat(), cursor) } } } @@ -583,7 +585,7 @@ impl Theme for ColorfulTheme { &self, f: &mut dyn fmt::Write, prompt: &str, - search_term: &str, + search_term: &Vec, // This should be Vec cursor_pos: usize, ) -> fmt::Result { if !prompt.is_empty() { @@ -596,15 +598,18 @@ impl Theme for ColorfulTheme { } if cursor_pos < search_term.len() { - let st_head = search_term[0..cursor_pos].to_string(); - let st_tail = search_term[cursor_pos + 1..search_term.len()].to_string(); + let split = search_term.split_at(cursor_pos); + let head = split.0.concat(); + let cursor = split.1.get(0).unwrap(); + let tail = split.1[1..].concat(); + let st_cursor = self .fuzzy_cursor_style - .apply_to(search_term.to_string().chars().nth(cursor_pos).unwrap()); + .apply_to(cursor); write!( f, "{} {}{}{}", - &self.prompt_suffix, st_head, st_cursor, st_tail + &self.prompt_suffix, head, st_cursor, tail ) } else { let cursor = self.fuzzy_cursor_style.apply_to(" "); @@ -612,7 +617,7 @@ impl Theme for ColorfulTheme { f, "{} {}{}", &self.prompt_suffix, - search_term.to_string(), + search_term.concat(), cursor ) } @@ -713,7 +718,7 @@ impl<'a> TermThemeRenderer<'a> { pub fn fuzzy_select_prompt( &mut self, prompt: &str, - search_term: &str, + search_term: &Vec, cursor_pos: usize, ) -> io::Result<()> { self.write_formatted_prompt(|this, buf| { From 5cd8093a6e48d9a4e9157032475e0f6690050283 Mon Sep 17 00:00:00 2001 From: Beh Date: Mon, 7 Mar 2022 15:13:29 +0100 Subject: [PATCH 4/7] fmt --- examples/fuzzyselect.rs | 92 ++++++++++++++++++------------------- src/prompts/fuzzy_select.rs | 5 +- src/theme.rs | 12 ++--- 3 files changed, 53 insertions(+), 56 deletions(-) diff --git a/examples/fuzzyselect.rs b/examples/fuzzyselect.rs index 248465c2..74373830 100644 --- a/examples/fuzzyselect.rs +++ b/examples/fuzzyselect.rs @@ -1,46 +1,46 @@ -use dialoguer::{theme::ColorfulTheme, FuzzySelect}; - -fn main() { - let selections = &[ - "Ice Cream", - "Vanilla Cupcake", - "Chocolate Muffin", - "A Pile of sweet, sweet mustard", - "Carrots", - "Peas", - "Pistacio", - "Mustard", - "Cream", - "Banana", - "Chocolate", - "Flakes", - "Corn", - "Cake", - "Tarte", - "Cheddar", - "Vanilla", - "Hazelnut", - "Flour", - "Sugar", - "Salt", - "Potato", - "French Fries", - "Pizza", - "Mousse au chocolat", - "Brown sugar", - "Blueberry", - "Burger", - "Käse", - "Döner", - "Blåbär" - ]; - - let selection = FuzzySelect::with_theme(&ColorfulTheme::default()) - .with_prompt("Pick your flavor") - .default(0) - .items(&selections[..]) - .interact() - .unwrap(); - - println!("Enjoy your {}!", selections[selection]); -} +use dialoguer::{theme::ColorfulTheme, FuzzySelect}; + +fn main() { + let selections = &[ + "Ice Cream", + "Vanilla Cupcake", + "Chocolate Muffin", + "A Pile of sweet, sweet mustard", + "Carrots", + "Peas", + "Pistacio", + "Mustard", + "Cream", + "Banana", + "Chocolate", + "Flakes", + "Corn", + "Cake", + "Tarte", + "Cheddar", + "Vanilla", + "Hazelnut", + "Flour", + "Sugar", + "Salt", + "Potato", + "French Fries", + "Pizza", + "Mousse au chocolat", + "Brown sugar", + "Blueberry", + "Burger", + "Käse", + "Döner", + "Blåbär", + ]; + + let selection = FuzzySelect::with_theme(&ColorfulTheme::default()) + .with_prompt("Pick your flavor") + .default(0) + .items(&selections[..]) + .interact() + .unwrap(); + + println!("Enjoy your {}!", selections[selection]); +} diff --git a/src/prompts/fuzzy_select.rs b/src/prompts/fuzzy_select.rs index e92aac1e..93be7eec 100644 --- a/src/prompts/fuzzy_select.rs +++ b/src/prompts/fuzzy_select.rs @@ -172,7 +172,10 @@ impl FuzzySelect<'_> { loop { let concatted_search_term = search_term.concat(); - search_term = concatted_search_term.graphemes(true).map(|s| s.to_string()).collect::>(); + search_term = concatted_search_term + .graphemes(true) + .map(|s| s.to_string()) + .collect::>(); render.clear()?; render.fuzzy_select_prompt(self.prompt.as_str(), &search_term, cursor_pos)?; diff --git a/src/theme.rs b/src/theme.rs index e6192f8a..e043624b 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -256,7 +256,7 @@ pub trait Theme { let head = split.0.concat(); let cursor = "|".to_string(); let tail = split.1.concat(); - + write!(f, "{}{}{}", head, cursor, tail) } else { let cursor = "|".to_string(); @@ -670,14 +670,8 @@ impl Theme for ColorfulTheme { let cursor = split.1.get(0).unwrap(); let tail = split.1[1..].concat(); - let st_cursor = self - .fuzzy_cursor_style - .apply_to(cursor); - write!( - f, - "{} {}{}{}", - &self.prompt_suffix, head, st_cursor, tail - ) + let st_cursor = self.fuzzy_cursor_style.apply_to(cursor); + write!(f, "{} {}{}{}", &self.prompt_suffix, head, st_cursor, tail) } else { let cursor = self.fuzzy_cursor_style.apply_to(" "); write!( From 38d88f75393f148b6ddb1b832465580bc8796702 Mon Sep 17 00:00:00 2001 From: Beh Date: Mon, 7 Mar 2022 16:26:42 +0100 Subject: [PATCH 5/7] Fix wrong param type for prompt item --- src/prompts/fuzzy_select.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prompts/fuzzy_select.rs b/src/prompts/fuzzy_select.rs index 93be7eec..3e0e72f9 100644 --- a/src/prompts/fuzzy_select.rs +++ b/src/prompts/fuzzy_select.rs @@ -202,7 +202,7 @@ impl FuzzySelect<'_> { idx == sel, self.highlight_matches, &matcher, - &search_term, + &search_term.concat(), )?; term.flush()?; } From df0a9fd9c587d548ecaa8fcfa0d4bbfe3d60ea0c Mon Sep 17 00:00:00 2001 From: Beh Date: Mon, 7 Mar 2022 16:29:05 +0100 Subject: [PATCH 6/7] Change cursor assignment --- src/theme.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/theme.rs b/src/theme.rs index e043624b..f030f015 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -667,11 +667,10 @@ impl Theme for ColorfulTheme { if cursor_pos < search_term.len() { let split = search_term.split_at(cursor_pos); let head = split.0.concat(); - let cursor = split.1.get(0).unwrap(); + let cursor = self.fuzzy_cursor_style.apply_to(split.1.get(0).unwrap()); let tail = split.1[1..].concat(); - let st_cursor = self.fuzzy_cursor_style.apply_to(cursor); - write!(f, "{} {}{}{}", &self.prompt_suffix, head, st_cursor, tail) + write!(f, "{} {}{}{}", &self.prompt_suffix, head, cursor, tail) } else { let cursor = self.fuzzy_cursor_style.apply_to(" "); write!( From 9c15cc2856d20bf831b4c6f533c01985dc5bb6d9 Mon Sep 17 00:00:00 2001 From: Beh Date: Mon, 7 Mar 2022 16:30:23 +0100 Subject: [PATCH 7/7] Fix typo in comment --- src/theme.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/theme.rs b/src/theme.rs index f030f015..cc8d24e1 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -311,7 +311,7 @@ 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 + // Formats the highlighting of matched characters #[cfg(feature = "fuzzy-select")] pub fuzzy_match_highlight_style: Style, /// Show the selections from certain prompts inline