Skip to main content

unicode_ellipsis/
width.rs

1//! Helper functions related to string or grapheme width.
2
3use unicode_segmentation::UnicodeSegmentation;
4
5#[cfg(feature = "fish")]
6use crate::widecharwidth::char_width;
7
8/// Returns the width of a str `s`, breaking the string down into multiple [graphemes](https://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries).
9/// This takes into account some things like [joiners](https://unicode-explorer.com/c/200D) when calculating width.
10#[inline]
11pub fn str_width(s: &str) -> usize {
12    UnicodeSegmentation::graphemes(s, true)
13        .map(grapheme_width)
14        .sum()
15}
16
17/// Returns the width of a single grapheme `g`. This takes into account some things like
18/// [joiners](https://unicode-explorer.com/c/200D) when calculating width.
19///
20/// Note that while you *can* pass in an entire string, this function assumes you are passing in
21/// just a single grapheme (e.g. `"a"`, `"💎"`, `"大"`, `"🇨🇦"`), and therefore makes no attempt in
22/// splitting the string into its individual graphemes.
23#[inline]
24pub fn grapheme_width(g: &str) -> usize {
25    if g.contains('\u{200d}') {
26        2
27    } else {
28        #[cfg(feature = "fish")]
29        {
30            use unicode_width::UnicodeWidthChar;
31            g.chars()
32                .map(|c| {
33                    if let Some(w) = char_width(c) {
34                        w
35                    } else {
36                        UnicodeWidthChar::width(c).unwrap_or(0)
37                    }
38                })
39                .sum()
40        }
41
42        #[cfg(not(feature = "fish"))]
43        {
44            use unicode_width::UnicodeWidthStr;
45            UnicodeWidthStr::width(g)
46        }
47    }
48}
49
50#[cfg(test)]
51mod test {
52    use super::*;
53
54    #[test]
55    fn test_str_width() {
56        // cSpell:disable
57        assert_eq!(str_width("aaa"), 3);
58        assert_eq!(str_width("a"), 1);
59        assert_eq!(str_width("💎💎"), 4);
60        assert_eq!(str_width("💎"), 2);
61        assert_eq!(str_width("大大"), 4);
62        assert_eq!(str_width("大"), 2);
63        assert_eq!(str_width("🇨🇦🇨🇦"), 4);
64        assert_eq!(str_width("🇨🇦"), 2);
65
66        #[cfg(feature = "fish")]
67        {
68            assert_eq!(str_width("हिन्दी"), 3);
69            assert_eq!(str_width("हि"), 1);
70        }
71
72        #[cfg(not(feature = "fish"))]
73        {
74            assert_eq!(str_width("हिन्दी"), 5);
75            assert_eq!(str_width("हि"), 2);
76        }
77        // cSpell:enable;
78    }
79
80    #[test]
81    fn test_grapheme_width() {
82        // cSpell:disable
83        assert_eq!(grapheme_width("a"), 1);
84        assert_eq!(grapheme_width("💎"), 2);
85        assert_eq!(grapheme_width("大"), 2);
86        assert_eq!(grapheme_width("🇨🇦"), 2);
87        #[cfg(feature = "fish")]
88        assert_eq!(grapheme_width("हि"), 1);
89        #[cfg(not(feature = "fish"))]
90        assert_eq!(grapheme_width("हि"), 2);
91        // cSpell:enable;
92    }
93}