From 2213b7b27caf20187efddb217b3aa61110eed4a8 Mon Sep 17 00:00:00 2001 From: Max Niederman Date: Mon, 1 May 2023 21:32:27 -0700 Subject: [PATCH] fix(ui): handle non-ascii chars correctly Fixes both issues described in #77. --- src/ui.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/ui.rs b/src/ui.rs index 7e6ebea..ca5022a 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -125,10 +125,15 @@ impl ThemedWidget for &Test { .starts_with(&self.words[self.current_word].progress[..]); let (typed, untyped) = - self.words[self.current_word].text.split_at(progress_ind); + self.words[self.current_word] + .text + .split_at(ceil_char_boundary( + &self.words[self.current_word].text, + progress_ind, + )); - let untyped_formatted = format!("{} ", untyped); - let (cursor, remaining) = untyped_formatted.split_at(1); + let mut remaining = untyped.chars().chain(iter::once(' ')); + let cursor = remaining.next().unwrap(); iter::once(vec![ Span::styled( @@ -140,10 +145,10 @@ impl ThemedWidget for &Test { }, ), Span::styled( - cursor.to_owned(), + cursor.to_string(), theme.prompt_current_untyped.patch(theme.prompt_cursor), ), - Span::styled(remaining.to_owned(), theme.prompt_current_untyped), + Span::styled(remaining.collect::(), theme.prompt_current_untyped), ]) }) // remaining words @@ -323,3 +328,12 @@ impl ThemedWidget for &results::Results { wpm_chart.render(res_chunks[1], buf); } } + +// FIXME: replace with `str::ceil_char_boundary` when stable +fn ceil_char_boundary(string: &str, index: usize) -> usize { + if string.is_char_boundary(index) { + index + } else { + ceil_char_boundary(string, index + 1) + } +}