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}