cursive_core/utils/lines/spans/
lines_iterator.rs

1use super::chunk::{Chunk, ChunkPart};
2use super::chunk_iterator::ChunkIterator;
3use super::prefix::prefix;
4use super::row::Row;
5use super::segment::Segment;
6use super::segment_merge_iterator::SegmentMergeIterator;
7use crate::utils::span::SpannedText;
8use std::iter::Peekable;
9use std::rc::Rc;
10use unicode_segmentation::UnicodeSegmentation;
11use unicode_width::UnicodeWidthStr;
12
13/// Generates rows of text in constrained width.
14///
15/// Works on spans of text.
16pub struct LinesIterator<S>
17where
18    S: SpannedText,
19{
20    iter: Peekable<ChunkIterator<S>>,
21    source: Rc<S>,
22
23    /// Available width
24    width: usize,
25
26    /// If a chunk wouldn't fit, we had to cut it in pieces.
27    /// This is how far in the current chunk we are.
28    chunk_offset: ChunkPart,
29
30    /// If `true`, keep a blank cell at the end of lines
31    /// when a whitespace or newline should be.
32    show_spaces: bool,
33}
34
35impl<S> LinesIterator<S>
36where
37    S: SpannedText,
38{
39    /// Creates a new iterator with the given content and width.
40    pub fn new(source: S, width: usize) -> Self {
41        let source = Rc::new(source);
42        let chunk_source = source.clone();
43        LinesIterator {
44            iter: ChunkIterator::new(chunk_source).peekable(),
45            source,
46            width,
47            chunk_offset: ChunkPart::default(),
48            show_spaces: false,
49        }
50    }
51
52    /// Leave a blank cell at the end of lines.
53    ///
54    /// Unless a word had to be truncated, in which case
55    /// it can take the entire width.
56    #[must_use]
57    pub fn show_spaces(mut self) -> Self {
58        self.show_spaces = true;
59        self
60    }
61}
62
63impl<S> Iterator for LinesIterator<S>
64where
65    S: SpannedText,
66{
67    type Item = Row;
68
69    fn next(&mut self) -> Option<Row> {
70        // Let's build a beautiful row.
71        let allowed_width = if self.show_spaces {
72            // Remove 1 from the available space, if possible.
73            // But only for regular words.
74            // If we have to split a chunk, forget about that.
75            self.width.saturating_sub(1)
76        } else {
77            self.width
78        };
79
80        let mut chunks = prefix(&mut self.iter, allowed_width, &mut self.chunk_offset);
81
82        // println!("Chunks..: {:?}", chunks);
83
84        if chunks.is_empty() {
85            // Desperate action to make something fit:
86            // Look at the current chunk. We'll try to return a part of it.
87            // So now, consider each individual grapheme as a valid chunk.
88            // Note: it may not be the first time we try to fit this chunk,
89            // so remember to trim the offset we may have stored.
90            match self.iter.peek() {
91                None => return None,
92                Some(chunk) => {
93                    let mut chunk = chunk.clone();
94                    chunk.remove_front(self.chunk_offset);
95
96                    // Try to fit part of it?
97                    let source = self.source.as_ref();
98                    let graphemes = chunk.segments.iter().flat_map(move |seg| {
99                        let mut offset = seg.start;
100
101                        let text = seg.resolve_plain(source);
102
103                        text.graphemes(true).map(move |g| {
104                            let width = g.width();
105                            let start = offset;
106                            let end = offset + g.len();
107                            offset = end;
108                            Chunk {
109                                width,
110                                segments: vec![Segment {
111                                    width,
112                                    span_id: seg.span_id,
113                                    start,
114                                    end,
115                                }],
116                                hard_stop: false,
117                                ends_with_space: false, // should we?
118                            }
119                        })
120                    });
121                    chunks = prefix(
122                        &mut graphemes.peekable(),
123                        self.width,
124                        &mut ChunkPart::default(),
125                    );
126
127                    if chunks.is_empty() {
128                        // Seriously? After everything we did for you?
129                        return None;
130                    }
131
132                    // We are going to return a part of a chunk.
133                    // So remember what we selected,
134                    // so we can skip it next time.
135                    let width: usize = chunks.iter().map(|chunk| chunk.width).sum();
136                    let length: usize = chunks
137                        .iter()
138                        .flat_map(|chunk| chunk.segments.iter())
139                        .map(|segment| segment.end - segment.start)
140                        .sum();
141
142                    self.chunk_offset.width += width;
143                    self.chunk_offset.length += length;
144                }
145            }
146        }
147
148        // We can know text was wrapped if the stop was optional,
149        // and there's more coming.
150        let is_wrapped =
151            !chunks.last().map(|c| c.hard_stop).unwrap_or(true) && self.iter.peek().is_some();
152
153        let width = chunks.iter().map(|c| c.width).sum();
154
155        assert!(width <= self.width);
156
157        // Concatenate all segments
158        let segments = SegmentMergeIterator::new(
159            chunks.into_iter().flat_map(|chunk| chunk.segments), //.filter(|segment| segment.start != segment.end),
160        )
161        .collect();
162
163        // TODO: merge consecutive segments of the same span
164
165        Some(Row {
166            segments,
167            width,
168            is_wrapped,
169        })
170    }
171}