uncurses/unicode/segment.rs
1//! Grapheme-cluster segmentation, backed by `unicode-rs` or `icu`.
2//!
3//! See the [`unicode`](super) module docs for backend selection. Both
4//! implementations honour Unicode extended grapheme clusters.
5
6/// Iterate over the extended grapheme clusters in a string.
7///
8/// # Parameters
9///
10/// - `s`: UTF-8 string slice to segment.
11///
12/// # Returns
13///
14/// An iterator of `&str` slices, each covering one extended grapheme
15/// cluster from `s` in order.
16///
17/// # Panics
18///
19/// Never panics.
20///
21/// # Usage notes
22///
23/// The returned slices borrow from `s`; no cell-width classification is
24/// performed here.
25#[cfg(all(not(feature = "icu"), feature = "unicode-rs"))]
26pub fn graphemes(s: &str) -> impl Iterator<Item = &str> {
27 use unicode_segmentation::UnicodeSegmentation;
28 s.graphemes(true)
29}
30
31/// Iterate over the extended grapheme clusters in a string.
32///
33/// # Parameters
34///
35/// - `s`: UTF-8 string slice to segment.
36///
37/// # Returns
38///
39/// An iterator of `&str` slices, each covering one extended grapheme
40/// cluster from `s` in order.
41///
42/// # Panics
43///
44/// Never panics.
45///
46/// # Usage notes
47///
48/// The returned slices borrow from `s`; no cell-width classification is
49/// performed here.
50#[cfg(feature = "icu")]
51pub fn graphemes(s: &str) -> impl Iterator<Item = &str> {
52 use icu_segmenter::GraphemeClusterSegmenter;
53
54 // `new()` is effectively zero-cost with compiled-in data: the returned
55 // segmenter is a thin handle.
56 let segmenter = GraphemeClusterSegmenter::new();
57 let mut iter = segmenter.segment_str(s);
58 let mut prev = iter.next().unwrap_or(0);
59 core::iter::from_fn(move || {
60 let next = iter.next()?;
61 let slice = &s[prev..next];
62 prev = next;
63 Some(slice)
64 })
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn ascii() {
73 let v: Vec<_> = graphemes("abc").collect();
74 assert_eq!(v, vec!["a", "b", "c"]);
75 }
76
77 #[test]
78 fn combining() {
79 // "é" as e + combining acute should be a single grapheme.
80 let v: Vec<_> = graphemes("e\u{0301}f").collect();
81 assert_eq!(v, vec!["e\u{0301}", "f"]);
82 }
83
84 #[test]
85 fn zwj_emoji() {
86 // Family ZWJ sequence — one grapheme.
87 let s = "\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}X";
88 let v: Vec<_> = graphemes(s).collect();
89 assert_eq!(v.len(), 2);
90 assert_eq!(v[1], "X");
91 }
92
93 #[test]
94 fn empty() {
95 assert_eq!(graphemes("").count(), 0);
96 }
97}