Skip to content

Commit

Permalink
Merge pull request #245 from stormshield-kg/fix-fuzzy-select-utf8
Browse files Browse the repository at this point in the history
Fix panic in fuzzy-select when using non-ASCII characters
  • Loading branch information
pksunkara authored Sep 12, 2023
2 parents 65f477b + c1db6ff commit ac365d8
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 39 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
* `Input` values that are invalid are now also stored in `History`
* Resolve some issues with cursor positioning in `Input` when using `utf-8` characters
* Correct page is shown when default selected option is not on the first page for `Select`
* Fix panic in `FuzzySelect` when using non-ASCII characters
* Add handling of `Delete` key for `FuzzySelect`

### Breaking

Expand All @@ -27,6 +29,7 @@
* Prompt interaction functions now take `self` instead of `&self`
* Prompt interaction functions and other operations now return `dialouger::Result` instead of `std::io::Result`
* Rename `Validator` to `InputValidator`
* The trait method `Theme::format_fuzzy_select_prompt()` now takes a byte position instead of a cursor position in order to support UTF-8.

## 0.10.4

Expand Down
33 changes: 22 additions & 11 deletions src/prompts/fuzzy_select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ impl FuzzySelect<'_> {

fn _interact_on(self, term: &Term, allow_quit: bool) -> Result<Option<usize>> {
// Place cursor at the end of the search term
let mut position = self.initial_text.len();
let mut cursor = self.initial_text.chars().count();
let mut search_term = self.initial_text.to_owned();

let mut render = TermThemeRenderer::new(term, self.theme);
Expand Down Expand Up @@ -224,8 +224,15 @@ impl FuzzySelect<'_> {
let mut vim_mode = false;

loop {
let mut byte_indices = search_term
.char_indices()
.map(|(index, _)| index)
.collect::<Vec<_>>();

byte_indices.push(search_term.len());

render.clear()?;
render.fuzzy_select_prompt(self.prompt.as_str(), &search_term, position)?;
render.fuzzy_select_prompt(self.prompt.as_str(), &search_term, byte_indices[cursor])?;

// Maps all items to a tuple of item and its match score.
let mut filtered_list = self
Expand Down Expand Up @@ -304,14 +311,14 @@ impl FuzzySelect<'_> {
}
term.flush()?;
}
(Key::ArrowLeft, _, _) | (Key::Char('h'), _, true) if position > 0 => {
position -= 1;
(Key::ArrowLeft, _, _) | (Key::Char('h'), _, true) if cursor > 0 => {
cursor -= 1;
term.flush()?;
}
(Key::ArrowRight, _, _) | (Key::Char('l'), _, true)
if position < search_term.len() =>
if cursor < byte_indices.len() - 1 =>
{
position += 1;
cursor += 1;
term.flush()?;
}
(Key::Enter, Some(sel), _) if !filtered_list.is_empty() => {
Expand All @@ -331,14 +338,18 @@ 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 > 0 => {
cursor -= 1;
search_term.remove(byte_indices[cursor]);
term.flush()?;
}
(Key::Del, _, _) if cursor < byte_indices.len() - 1 => {
search_term.remove(byte_indices[cursor]);
term.flush()?;
}
(Key::Char(chr), _, _) if !chr.is_ascii_control() => {
search_term.insert(position, chr);
position += 1;
search_term.insert(byte_indices[cursor], chr);
cursor += 1;
term.flush()?;
sel = Some(0);
starting_row = 0;
Expand Down
27 changes: 10 additions & 17 deletions src/theme/colorful.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,31 +403,24 @@ impl Theme for ColorfulTheme {
f: &mut dyn fmt::Write,
prompt: &str,
search_term: &str,
cursor_pos: usize,
bytes_pos: usize,
) -> fmt::Result {
if !prompt.is_empty() {
write!(
f,
"{} {} ",
&self.prompt_prefix,
self.prompt_prefix,
self.prompt_style.apply_to(prompt)
)?;
}

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
)
} else {
let cursor = self.fuzzy_cursor_style.apply_to(" ");
write!(f, "{} {}{}", &self.prompt_suffix, search_term, cursor)
}
let (st_head, remaining) = search_term.split_at(bytes_pos);
let mut chars = remaining.chars();
let chr = chars.next().unwrap_or(' ');
let st_cursor = self.fuzzy_cursor_style.apply_to(chr);
let st_tail = chars.as_str();

let prompt_suffix = &self.prompt_suffix;
write!(f, "{prompt_suffix} {st_head}{st_cursor}{st_tail}",)
}
}
15 changes: 4 additions & 11 deletions src/theme/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,20 +253,13 @@ pub trait Theme {
f: &mut dyn fmt::Write,
prompt: &str,
search_term: &str,
cursor_pos: usize,
bytes_pos: usize,
) -> fmt::Result {
if !prompt.is_empty() {
write!(f, "{} ", prompt)?;
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)
} else {
let cursor = "|".to_string();
write!(f, "{}{}", search_term, cursor)
}
let (st_head, st_tail) = search_term.split_at(bytes_pos);
write!(f, "{st_head}|{st_tail}")
}
}

0 comments on commit ac365d8

Please sign in to comment.