From ee65297183d5d74a7883f44eb8fbeda63bb30404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dario=20Vladovi=C4=87?= Date: Sun, 2 May 2021 09:55:26 +0200 Subject: [PATCH] refactor(print): introduce `UnicodeWidthGraphemes` trait (#2670) --- src/print.rs | 64 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/src/print.rs b/src/print.rs index 0330af07e..d719cfc45 100644 --- a/src/print.rs +++ b/src/print.rs @@ -16,6 +16,43 @@ use crate::module::ALL_MODULES; use crate::modules; use crate::segment::Segment; +pub struct Grapheme<'a>(&'a str); + +impl<'a> Grapheme<'a> { + pub fn width(&self) -> usize { + self.0 + .chars() + .filter_map(UnicodeWidthChar::width) + .max() + .unwrap_or(0) + } +} + +pub trait UnicodeWidthGraphemes { + fn width_graphemes(&self) -> usize; +} + +impl UnicodeWidthGraphemes for T +where + T: AsRef, +{ + fn width_graphemes(&self) -> usize { + self.as_ref() + .graphemes(true) + .map(Grapheme) + .map(|g| g.width()) + .sum() + } +} + +#[test] +fn test_grapheme_aware_width() { + // UnicodeWidthStr::width would return 8 + assert_eq!(2, "👩‍👩‍👦‍👦".width_graphemes()); + assert_eq!(1, "Ü".width_graphemes()); + assert_eq!(11, "normal text".width_graphemes()); +} + pub fn prompt(args: ArgMatches) { let context = Context::new(args); let stdout = io::stdout(); @@ -123,12 +160,12 @@ pub fn timings(args: ArgMatches) { .filter(|module| !module.is_empty() || module.duration.as_millis() > 0) .map(|module| ModuleTiming { name: String::from(module.get_name().as_str()), - name_len: better_width(module.get_name().as_str()), + name_len: module.get_name().width_graphemes(), value: ansi_term::ANSIStrings(&module.ansi_strings()) .to_string() .replace('\n', "\\n"), duration: module.duration, - duration_len: better_width(format_duration(&module.duration).as_str()), + duration_len: format_duration(&module.duration).width_graphemes(), }) .collect::>(); @@ -174,8 +211,8 @@ pub fn explain(args: ArgMatches) { let value = module.get_segments().join(""); ModuleInfo { value: ansi_term::ANSIStrings(&module.ansi_strings()).to_string(), - value_len: better_width(value.as_str()) - + better_width(format_duration(&module.duration).as_str()), + value_len: value.width_graphemes() + + format_duration(&module.duration).width_graphemes(), desc: module.get_description().to_owned(), duration: format_duration(&module.duration), } @@ -218,7 +255,7 @@ pub fn explain(args: ArgMatches) { } // Handle normal wrapping - current_pos += grapheme_width(g); + current_pos += Grapheme(g).width(); // Wrap when hitting max width or newline if g == "\n" || current_pos > desc_width { // trim spaces on linebreak @@ -358,10 +395,6 @@ fn should_add_implicit_custom_module( .unwrap_or(false) } -fn better_width(s: &str) -> usize { - s.graphemes(true).map(grapheme_width).sum() -} - pub fn format_duration(duration: &Duration) -> String { let milis = duration.as_millis(); if milis == 0 { @@ -370,16 +403,3 @@ pub fn format_duration(duration: &Duration) -> String { format!("{:?}ms", &milis) } } - -// Assume that graphemes have width of the first character in the grapheme -fn grapheme_width(g: &str) -> usize { - g.chars().next().and_then(|i| i.width()).unwrap_or(0) -} - -#[test] -fn test_grapheme_aware_better_width() { - // UnicodeWidthStr::width would return 8 - assert_eq!(2, better_width("👩‍👩‍👦‍👦")); - assert_eq!(1, better_width("Ü")); - assert_eq!(11, better_width("normal text")); -}