Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: [Fuzzy-Select] Special chars cause panic #183

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Expand Up @@ -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 = []
Expand All @@ -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"
Expand Down
89 changes: 46 additions & 43 deletions examples/fuzzyselect.rs
@@ -1,43 +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",
];

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]);
}
36 changes: 22 additions & 14 deletions src/prompts/fuzzy_select.rs
Expand Up @@ -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.
///
Expand Down Expand Up @@ -146,8 +147,9 @@ impl FuzzySelect<'_> {

/// Like `interact` but allows a specific terminal to be set.
fn _interact_on(&self, term: &Term, allow_quit: bool) -> io::Result<Option<usize>> {
let mut position = 0;
let mut search_term = String::new();
// This cursor iterates over the graphemes vec rather than the search term
let mut cursor_pos = 0;
let mut search_term: Vec<String> = Vec::new();

let mut render = TermThemeRenderer::new(term, self.theme);
let mut sel = self.default;
Expand All @@ -169,14 +171,20 @@ 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::<Vec<String>>();

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
.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::<Vec<_>>();

Expand All @@ -194,7 +202,7 @@ impl FuzzySelect<'_> {
idx == sel,
self.highlight_matches,
&matcher,
&search_term,
&search_term.concat(),
)?;
term.flush()?;
}
Expand Down Expand Up @@ -236,12 +244,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() => {
Expand All @@ -261,14 +269,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.to_string());
cursor_pos += 1;
term.flush()?;
sel = 0;
starting_row = 0;
Expand Down
38 changes: 18 additions & 20 deletions src/theme.rs
Expand Up @@ -244,21 +244,23 @@ pub trait Theme {
&self,
f: &mut dyn fmt::Write,
prompt: &str,
search_term: &str,
search_term: &Vec<String>,
cursor_pos: usize,
) -> fmt::Result {
if !prompt.is_empty() {
write!(f, "{} ", prompt,)?;
}

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)
}
}
}
Expand Down Expand Up @@ -309,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
Expand Down Expand Up @@ -650,7 +652,7 @@ impl Theme for ColorfulTheme {
&self,
f: &mut dyn fmt::Write,
prompt: &str,
search_term: &str,
search_term: &Vec<String>, // This should be Vec<str>
cursor_pos: usize,
) -> fmt::Result {
if !prompt.is_empty() {
Expand All @@ -663,23 +665,19 @@ 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 st_cursor = self
.fuzzy_cursor_style
.apply_to(search_term.to_string().chars().nth(cursor_pos).unwrap());
write!(
f,
"{} {}{}{}",
&self.prompt_suffix, st_head, st_cursor, st_tail
)
let split = search_term.split_at(cursor_pos);
let head = split.0.concat();
let cursor = self.fuzzy_cursor_style.apply_to(split.1.get(0).unwrap());
let tail = split.1[1..].concat();

write!(f, "{} {}{}{}", &self.prompt_suffix, head, cursor, tail)
} else {
let cursor = self.fuzzy_cursor_style.apply_to(" ");
write!(
f,
"{} {}{}",
&self.prompt_suffix,
search_term.to_string(),
search_term.concat(),
cursor
)
}
Expand Down Expand Up @@ -780,7 +778,7 @@ impl<'a> TermThemeRenderer<'a> {
pub fn fuzzy_select_prompt(
&mut self,
prompt: &str,
search_term: &str,
search_term: &Vec<String>,
cursor_pos: usize,
) -> io::Result<()> {
self.write_formatted_prompt(|this, buf| {
Expand Down