Skip to main content

unicode_width_utils/
line_iterator.rs

1use crate::UnicodeWidth;
2use crate::WidthIterator;
3use std::borrow::Cow;
4
5/// An iterator over chunks of a string based on display width.
6///
7/// It supports all configurations in [`UnicodeWidth`] such as tab expansion.
8///
9/// Unlike [`UnicodeWidth::truncate()`],
10/// each line is guaranteed to have at least one character,
11/// even if it is wider than `max_width`.
12///
13/// See also [`UnicodeWidth::lines()`].
14///
15/// # Examples
16/// ```
17/// use unicode_width_utils::UnicodeWidth;
18///
19/// let uw = UnicodeWidth::new();
20/// assert_eq!(
21///     uw.lines("12345678", 3).collect::<Vec<_>>(),
22///     vec!["123", "456", "78"]
23/// );
24/// ```
25#[derive(Debug)]
26pub struct LineIterator<'a, 'b> {
27    uw: &'a UnicodeWidth,
28    input: &'b str,
29    max_width: usize,
30}
31
32impl<'a, 'b> LineIterator<'a, 'b> {
33    /// Create a new `LineIterator` from a string slice.
34    pub(crate) fn new(uw: &'a UnicodeWidth, input: &'b str, max_width: usize) -> Self {
35        Self {
36            uw,
37            input,
38            max_width,
39        }
40    }
41}
42
43impl<'a, 'b> Iterator for LineIterator<'a, 'b> {
44    type Item = Cow<'b, str>;
45
46    fn next(&mut self) -> Option<Self::Item> {
47        if self.input.is_empty() {
48            return None;
49        }
50        let mut iter = WidthIterator::new(self.uw, self.input);
51        iter.set_max_width(self.max_width);
52        iter.set_include_at_least_one(true);
53        iter.consume_all();
54        let end_index = iter.input_end_index.unwrap();
55        self.input = &self.input[end_index..];
56        Some(iter.into())
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn no_tabs() {
66        let uw = UnicodeWidth::new();
67        let input = "hello world rust";
68        let mut iter = LineIterator::new(&uw, input, 5);
69        assert_eq!(iter.next(), Some(Cow::Borrowed("hello")));
70        assert_eq!(iter.next(), Some(Cow::Borrowed(" worl")));
71        assert_eq!(iter.next(), Some(Cow::Borrowed("d rus")));
72        assert_eq!(iter.next(), Some(Cow::Borrowed("t")));
73        assert_eq!(iter.next(), None);
74    }
75
76    #[test]
77    fn expand_tabs() {
78        let mut uw = UnicodeWidth::new();
79        uw.set_tab_size(4);
80        uw.set_expand_tab(true);
81        let input = "A\tB\tCD";
82        let mut iter = LineIterator::new(&uw, input, 5);
83        assert_eq!(iter.next(), Some(Cow::Owned("A   B".to_string())));
84        assert_eq!(iter.next(), Some(Cow::Owned("    C".to_string())));
85        assert_eq!(iter.next(), Some(Cow::Borrowed("D")));
86        assert_eq!(iter.next(), None);
87    }
88
89    #[test]
90    fn cjk_boundary() {
91        let uw = UnicodeWidth::new();
92        // CJK character "あ" has width 2.
93        // "A" (width 1) + "あ" (width 2) = 3 columns.
94        // With max_width = 2, "あ" does not fit on the first line and wraps to the second line.
95        let mut iter = LineIterator::new(&uw, "Aあ", 2);
96        assert_eq!(iter.next(), Some(Cow::Borrowed("A")));
97        assert_eq!(iter.next(), Some(Cow::Borrowed("あ")));
98        assert_eq!(iter.next(), None);
99    }
100
101    #[test]
102    fn at_least_one() {
103        let uw = UnicodeWidth::new();
104        // CJK character "あ" has width 2, but max_width is 1.
105        let mut iter = LineIterator::new(&uw, "あA", 1);
106        assert_eq!(iter.next(), Some(Cow::Borrowed("あ")));
107        assert_eq!(iter.next(), Some(Cow::Borrowed("A")));
108        assert_eq!(iter.next(), None);
109    }
110}