From 8188ed3950b61aa580343964ca8a2f2cbfdab62c Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Fri, 1 Aug 2025 10:50:09 -0700 Subject: [PATCH] feat: implement UnicodeWidthStr for Text/Line/Span (#2030) You can now calculate the width of any Text/Line/Span using the UnicodeWidthStr trait instead of the width method on the type. This also makes it possible to use the width_cjk() method if needed. --- ratatui-core/src/text/line.rs | 14 +++++++++++++- ratatui-core/src/text/span.rs | 12 +++++++++++- ratatui-core/src/text/text.rs | 23 ++++++++++++++++++++++- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/ratatui-core/src/text/line.rs b/ratatui-core/src/text/line.rs index 0616e7e8..3b8638c5 100644 --- a/ratatui-core/src/text/line.rs +++ b/ratatui-core/src/text/line.rs @@ -7,6 +7,7 @@ use alloc::vec::Vec; use core::fmt; use unicode_truncate::UnicodeTruncateStr; +use unicode_width::UnicodeWidthStr; use crate::buffer::Buffer; use crate::layout::{Alignment, Rect}; @@ -435,8 +436,9 @@ impl<'a> Line<'a> { /// let line = Line::from(vec!["Hello".blue(), " world!".green()]); /// assert_eq!(12, line.width()); /// ``` + #[must_use] pub fn width(&self) -> usize { - self.spans.iter().map(Span::width).sum() + UnicodeWidthStr::width(self) } /// Returns an iterator over the graphemes held by this line. @@ -562,6 +564,16 @@ impl<'a> Line<'a> { } } +impl UnicodeWidthStr for Line<'_> { + fn width(&self) -> usize { + self.spans.iter().map(UnicodeWidthStr::width).sum() + } + + fn width_cjk(&self) -> usize { + self.spans.iter().map(UnicodeWidthStr::width_cjk).sum() + } +} + impl<'a> IntoIterator for Line<'a> { type Item = Span<'a>; type IntoIter = alloc::vec::IntoIter>; diff --git a/ratatui-core/src/text/span.rs b/ratatui-core/src/text/span.rs index 06d87284..82146197 100644 --- a/ratatui-core/src/text/span.rs +++ b/ratatui-core/src/text/span.rs @@ -269,7 +269,7 @@ impl<'a> Span<'a> { /// Returns the unicode width of the content held by this span. pub fn width(&self) -> usize { - self.content.width() + UnicodeWidthStr::width(self) } /// Returns an iterator over the graphemes held by this span. @@ -376,6 +376,16 @@ impl<'a> Span<'a> { } } +impl UnicodeWidthStr for Span<'_> { + fn width(&self) -> usize { + self.content.width() + } + + fn width_cjk(&self) -> usize { + self.content.width_cjk() + } +} + impl<'a, T> From for Span<'a> where T: Into>, diff --git a/ratatui-core/src/text/text.rs b/ratatui-core/src/text/text.rs index f04981e1..4525378d 100644 --- a/ratatui-core/src/text/text.rs +++ b/ratatui-core/src/text/text.rs @@ -5,6 +5,8 @@ use alloc::vec; use alloc::vec::Vec; use core::fmt; +use unicode_width::UnicodeWidthStr; + use crate::buffer::Buffer; use crate::layout::{Alignment, Rect}; use crate::style::{Style, Styled}; @@ -284,7 +286,7 @@ impl<'a> Text<'a> { /// assert_eq!(15, text.width()); /// ``` pub fn width(&self) -> usize { - self.iter().map(Line::width).max().unwrap_or_default() + UnicodeWidthStr::width(self) } /// Returns the height. @@ -559,6 +561,25 @@ impl<'a> Text<'a> { } } +impl UnicodeWidthStr for Text<'_> { + /// Returns the max width of all the lines. + fn width(&self) -> usize { + self.lines + .iter() + .map(UnicodeWidthStr::width) + .max() + .unwrap_or_default() + } + + fn width_cjk(&self) -> usize { + self.lines + .iter() + .map(UnicodeWidthStr::width_cjk) + .max() + .unwrap_or_default() + } +} + impl<'a> IntoIterator for Text<'a> { type Item = Line<'a>; type IntoIter = alloc::vec::IntoIter;