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