Skip to content

Commit ac365d8

Browse files
authored
Merge pull request console-rs#245 from stormshield-kg/fix-fuzzy-select-utf8
Fix panic in fuzzy-select when using non-ASCII characters
2 parents 65f477b + c1db6ff commit ac365d8

File tree

4 files changed

+39
-39
lines changed

4 files changed

+39
-39
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
* `Input` values that are invalid are now also stored in `History`
1717
* Resolve some issues with cursor positioning in `Input` when using `utf-8` characters
1818
* Correct page is shown when default selected option is not on the first page for `Select`
19+
* Fix panic in `FuzzySelect` when using non-ASCII characters
20+
* Add handling of `Delete` key for `FuzzySelect`
1921

2022
### Breaking
2123

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

3134
## 0.10.4
3235

src/prompts/fuzzy_select.rs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ impl FuzzySelect<'_> {
195195

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

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

226226
loop {
227+
let mut byte_indices = search_term
228+
.char_indices()
229+
.map(|(index, _)| index)
230+
.collect::<Vec<_>>();
231+
232+
byte_indices.push(search_term.len());
233+
227234
render.clear()?;
228-
render.fuzzy_select_prompt(self.prompt.as_str(), &search_term, position)?;
235+
render.fuzzy_select_prompt(self.prompt.as_str(), &search_term, byte_indices[cursor])?;
229236

230237
// Maps all items to a tuple of item and its match score.
231238
let mut filtered_list = self
@@ -304,14 +311,14 @@ impl FuzzySelect<'_> {
304311
}
305312
term.flush()?;
306313
}
307-
(Key::ArrowLeft, _, _) | (Key::Char('h'), _, true) if position > 0 => {
308-
position -= 1;
314+
(Key::ArrowLeft, _, _) | (Key::Char('h'), _, true) if cursor > 0 => {
315+
cursor -= 1;
309316
term.flush()?;
310317
}
311318
(Key::ArrowRight, _, _) | (Key::Char('l'), _, true)
312-
if position < search_term.len() =>
319+
if cursor < byte_indices.len() - 1 =>
313320
{
314-
position += 1;
321+
cursor += 1;
315322
term.flush()?;
316323
}
317324
(Key::Enter, Some(sel), _) if !filtered_list.is_empty() => {
@@ -331,14 +338,18 @@ impl FuzzySelect<'_> {
331338
term.show_cursor()?;
332339
return Ok(sel_string_pos_in_items);
333340
}
334-
(Key::Backspace, _, _) if position > 0 => {
335-
position -= 1;
336-
search_term.remove(position);
341+
(Key::Backspace, _, _) if cursor > 0 => {
342+
cursor -= 1;
343+
search_term.remove(byte_indices[cursor]);
344+
term.flush()?;
345+
}
346+
(Key::Del, _, _) if cursor < byte_indices.len() - 1 => {
347+
search_term.remove(byte_indices[cursor]);
337348
term.flush()?;
338349
}
339350
(Key::Char(chr), _, _) if !chr.is_ascii_control() => {
340-
search_term.insert(position, chr);
341-
position += 1;
351+
search_term.insert(byte_indices[cursor], chr);
352+
cursor += 1;
342353
term.flush()?;
343354
sel = Some(0);
344355
starting_row = 0;

src/theme/colorful.rs

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -403,31 +403,24 @@ impl Theme for ColorfulTheme {
403403
f: &mut dyn fmt::Write,
404404
prompt: &str,
405405
search_term: &str,
406-
cursor_pos: usize,
406+
bytes_pos: usize,
407407
) -> fmt::Result {
408408
if !prompt.is_empty() {
409409
write!(
410410
f,
411411
"{} {} ",
412-
&self.prompt_prefix,
412+
self.prompt_prefix,
413413
self.prompt_style.apply_to(prompt)
414414
)?;
415415
}
416416

417-
if cursor_pos < search_term.len() {
418-
let st_head = search_term[0..cursor_pos].to_string();
419-
let st_tail = search_term[cursor_pos + 1..search_term.len()].to_string();
420-
let st_cursor = self
421-
.fuzzy_cursor_style
422-
.apply_to(search_term.to_string().chars().nth(cursor_pos).unwrap());
423-
write!(
424-
f,
425-
"{} {}{}{}",
426-
&self.prompt_suffix, st_head, st_cursor, st_tail
427-
)
428-
} else {
429-
let cursor = self.fuzzy_cursor_style.apply_to(" ");
430-
write!(f, "{} {}{}", &self.prompt_suffix, search_term, cursor)
431-
}
417+
let (st_head, remaining) = search_term.split_at(bytes_pos);
418+
let mut chars = remaining.chars();
419+
let chr = chars.next().unwrap_or(' ');
420+
let st_cursor = self.fuzzy_cursor_style.apply_to(chr);
421+
let st_tail = chars.as_str();
422+
423+
let prompt_suffix = &self.prompt_suffix;
424+
write!(f, "{prompt_suffix} {st_head}{st_cursor}{st_tail}",)
432425
}
433426
}

src/theme/mod.rs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -253,20 +253,13 @@ pub trait Theme {
253253
f: &mut dyn fmt::Write,
254254
prompt: &str,
255255
search_term: &str,
256-
cursor_pos: usize,
256+
bytes_pos: usize,
257257
) -> fmt::Result {
258258
if !prompt.is_empty() {
259-
write!(f, "{} ", prompt)?;
259+
write!(f, "{prompt} ")?;
260260
}
261261

262-
if cursor_pos < search_term.len() {
263-
let st_head = search_term[0..cursor_pos].to_string();
264-
let st_tail = search_term[cursor_pos..search_term.len()].to_string();
265-
let st_cursor = "|".to_string();
266-
write!(f, "{}{}{}", st_head, st_cursor, st_tail)
267-
} else {
268-
let cursor = "|".to_string();
269-
write!(f, "{}{}", search_term, cursor)
270-
}
262+
let (st_head, st_tail) = search_term.split_at(bytes_pos);
263+
write!(f, "{st_head}|{st_tail}")
271264
}
272265
}

0 commit comments

Comments
 (0)