cursive/src/utils/lines_iterator.rs

161 lines
4.5 KiB
Rust
Raw Normal View History

2016-10-02 22:22:29 +00:00
2016-07-29 06:05:08 +00:00
use With;
2016-10-02 22:22:29 +00:00
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use utils::prefix;
2016-07-29 06:05:08 +00:00
/// Generates rows of text in constrained width.
///
/// Given a long text and a width constraint, it iterates over
/// substrings of the text, each within the constraint.
pub struct LinesIterator<'a> {
2016-08-02 07:32:16 +00:00
/// Content to iterate on.
2016-07-29 06:05:08 +00:00
content: &'a str,
2016-08-02 07:32:16 +00:00
/// Current offset in the content.
offset: usize,
/// Available width. Don't output lines wider than that.
2016-07-29 06:05:08 +00:00
width: usize,
2016-08-02 07:32:16 +00:00
/// If `true`, keep a blank cell at the end of lines
/// when a whitespace or newline should be.
show_spaces: bool,
2016-07-29 06:05:08 +00:00
}
impl<'a> LinesIterator<'a> {
/// Returns a new `LinesIterator` on `content`.
///
/// Yields rows of `width` cells or less.
pub fn new(content: &'a str, width: usize) -> Self {
LinesIterator {
content: content,
width: width,
2016-08-02 07:32:16 +00:00
offset: 0,
show_spaces: false,
2016-07-29 06:05:08 +00:00
}
}
2016-08-02 07:32:16 +00:00
/// Leave a blank cell at the end of lines.
///
/// Unless a word had to be truncated, in which case
/// it takes the entire width.
pub fn show_spaces(mut self) -> Self {
self.show_spaces = true;
self
}
2016-07-29 06:05:08 +00:00
}
/// Represents a row of text within a `String`.
///
2016-08-02 07:32:16 +00:00
/// A row is made of offsets into a parent `String`.
2016-07-29 06:05:08 +00:00
/// The corresponding substring should take `width` cells when printed.
2016-08-02 07:32:16 +00:00
#[derive(Debug, Clone, Copy)]
2016-07-29 06:05:08 +00:00
pub struct Row {
/// Beginning of the row in the parent `String`.
pub start: usize,
2016-08-02 07:32:16 +00:00
/// End of the row (excluded)
2016-07-29 06:05:08 +00:00
pub end: usize,
/// Width of the row, in cells.
pub width: usize,
}
2016-08-02 07:32:16 +00:00
impl Row {
/// Shift a row start and end by `offset`.
pub fn shift(&mut self, offset: usize) {
2016-08-02 07:32:16 +00:00
self.start += offset;
self.end += offset;
}
/// Shift a row start and end by `offset`.
///
/// Chainable variant;
pub fn shifted(self, offset: usize) -> Self {
self.with(|s| s.shift(offset))
}
/// Shift back a row start and end by `offset`.
pub fn rev_shift(&mut self, offset: usize) {
self.start -= offset;
self.end -= offset;
2016-08-02 07:32:16 +00:00
}
}
2016-07-29 06:05:08 +00:00
impl<'a> Iterator for LinesIterator<'a> {
type Item = Row;
fn next(&mut self) -> Option<Row> {
2016-08-02 07:32:16 +00:00
if self.offset >= self.content.len() {
2016-07-29 06:05:08 +00:00
// This is the end.
return None;
}
2016-08-02 07:32:16 +00:00
// We start at the current offset.
let start = self.offset;
let content = &self.content[start..];
2016-07-29 06:05:08 +00:00
2016-08-02 07:32:16 +00:00
// Find the ideal line, in an infinitely wide world.
// We'll make a line larger than that.
2017-01-19 19:11:57 +00:00
let next = content.find('\n').unwrap_or_else(|| content.len());
2016-07-29 06:05:08 +00:00
let content = &content[..next];
2016-08-02 07:32:16 +00:00
let allowed_width = if self.show_spaces {
2017-08-01 16:47:08 +00:00
// Remove 1 from the available space, if possible.
self.width.saturating_sub(1)
2016-08-02 07:32:16 +00:00
} else {
self.width
};
2016-07-29 06:05:08 +00:00
let line_width = content.width();
2016-08-02 07:32:16 +00:00
if line_width <= allowed_width {
2016-07-29 06:05:08 +00:00
// We found a newline before the allowed limit.
// Break early.
2016-08-02 07:32:16 +00:00
// Advance the cursor to after the newline.
self.offset += next + 1;
2016-07-29 06:05:08 +00:00
return Some(Row {
start: start,
2016-08-02 07:32:16 +00:00
end: start + next,
2016-07-29 06:05:08 +00:00
width: line_width,
});
}
2016-08-02 07:32:16 +00:00
// First attempt: only break on spaces.
2016-07-29 06:05:08 +00:00
let prefix_length =
match prefix(content.split(' '), allowed_width, " ").length {
2016-08-02 07:32:16 +00:00
// If this fail, fallback: only break on graphemes.
// There's no whitespace to skip there.
// And don't reserve the white space anymore.
0 => prefix(content.graphemes(true), self.width, "").length,
2016-07-29 06:05:08 +00:00
other => {
2016-08-02 07:32:16 +00:00
// If it works, advance the cursor by 1
// to jump the whitespace.
// We don't want to add 1 to `prefix_length` though, it
// would include the whitespace in the row.
self.offset += 1;
2016-07-29 06:05:08 +00:00
other
2016-09-23 05:10:14 +00:00
}
2016-07-29 06:05:08 +00:00
};
if prefix_length == 0 {
// This mean we can't even get a single char?
// Sucks. Let's bail.
return None;
}
2016-08-02 07:32:16 +00:00
// Advance the offset to the end of the line.
self.offset += prefix_length;
2016-07-29 06:05:08 +00:00
Some(Row {
start: start,
end: start + prefix_length,
width: self.width,
})
}
}
2016-08-02 07:32:16 +00:00
#[cfg(test)]
mod tests {
#[test]
2016-09-23 05:10:14 +00:00
fn test_layout() {}
2016-08-02 07:32:16 +00:00
}