diff --git a/src/text.rs b/src/text.rs index 02ab7d96c..875910faf 100644 --- a/src/text.rs +++ b/src/text.rs @@ -60,6 +60,29 @@ fn cursor_index(string: &str, byte_position: usize) -> usize { } +/// Clip a string at `max_width` characters, at a proper character +/// boundary. +pub(crate) fn clip(string: &str, max_width: usize) -> &str { + let extended = true; + let result = + string + .grapheme_indices(extended) + .try_fold(0, |mut total_width, (byte_idx, grapheme)| { + total_width += grapheme.width(); + if total_width > max_width { + ControlFlow::Break(byte_idx) + } else { + ControlFlow::Continue(total_width) + } + }); + + match result { + ControlFlow::Break(byte_idx) => &string[..byte_idx], + ControlFlow::Continue(..) => string, + } +} + + /// Some Unicode aware text. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct Text { @@ -350,6 +373,27 @@ mod tests { assert_eq!(cursor_index(s, 6), 4); } + /// Check that we can correctly "clip" a string to a maximum width. + #[test] + fn string_clipping() { + assert_eq!(clip("", 0), ""); + assert_eq!(clip("a", 0), ""); + assert_eq!(clip("ab", 0), ""); + + assert_eq!(clip("", 1), ""); + assert_eq!(clip("a", 1), "a"); + assert_eq!(clip("ab", 1), "a"); + + assert_eq!(clip("⚠️attn⚠️", 0), ""); + assert_eq!(clip("⚠️attn⚠️", 1), "⚠️"); + assert_eq!(clip("⚠️attn⚠️", 2), "⚠️a"); + + assert_eq!(clip("|a|b|", 0), ""); + assert_eq!(clip("|a|b|", 1), ""); + assert_eq!(clip("|a|b|", 2), "|"); + assert_eq!(clip("|a|b|", 3), "|a"); + } + /// Check that `EditableText::substr` behaves as it should. #[test] fn text_substr() { diff --git a/src/ui/term_renderer.rs b/src/ui/term_renderer.rs index 5ec1f4af8..a957c6d7f 100644 --- a/src/ui/term_renderer.rs +++ b/src/ui/term_renderer.rs @@ -28,6 +28,7 @@ use gui::Renderer; use crate::colors::Color; use crate::colors::Colors; +use crate::text; use super::dialog::Dialog; use super::dialog::SetUnsetTag; @@ -97,11 +98,7 @@ fn clip(x: u16, y: u16, string: &str, bbox: BBox) -> &str { let h = bbox.h; if y < h { - if x + string.len() as u16 >= w { - &string[..(w - x) as usize] - } else { - string - } + text::clip(string, (w - x).into()) } else { "" }