Remove attribute type from spans::LinesIterator

This commit is contained in:
Alexandre Bury 2018-02-16 16:05:15 -08:00
parent c220cc679a
commit 25e65a87e8
8 changed files with 101 additions and 88 deletions

View File

@ -1,39 +1,37 @@
use super::segment::SegmentWithText;
use super::segment::Segment;
/// Non-splittable piece of text.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Chunk<'a> {
pub struct Chunk {
pub width: usize,
pub segments: Vec<SegmentWithText<'a>>,
pub segments: Vec<Segment>,
pub hard_stop: bool,
pub ends_with_space: bool,
}
impl<'a> Chunk<'a> {
impl Chunk {
/// Remove some text from the front.
///
/// We're given the length (number of bytes) and the width.
pub fn remove_front(&mut self, mut to_remove: ChunkPart) {
// Remove something from each segment until we've removed enough.
for segment in &mut self.segments {
if to_remove.length <= segment.seg.end - segment.seg.start {
if to_remove.length <= segment.end - segment.start {
// This segment is bigger than what we need to remove
// So just trim the prefix and stop there.
segment.seg.start += to_remove.length;
segment.seg.width -= to_remove.width;
segment.text = &segment.text[to_remove.length..];
segment.start += to_remove.length;
segment.width -= to_remove.width;
self.width -= to_remove.width;
break;
} else {
// This segment is too small, so it'll disapear entirely.
to_remove.length -= segment.seg.end - segment.seg.start;
to_remove.width -= segment.seg.width;
self.width -= segment.seg.width;
to_remove.length -= segment.end - segment.start;
to_remove.width -= segment.width;
self.width -= segment.width;
// Empty this segment
segment.seg.start = segment.seg.end;
segment.seg.width = 0;
segment.text = "";
segment.start = segment.end;
segment.width = 0;
}
}
}
@ -59,11 +57,11 @@ impl<'a> Chunk<'a> {
// If yes, just drop it.
let last_empty = {
let last = self.segments.last_mut().unwrap();
last.seg.end -= 1;
last.end -= 1;
if self.ends_with_space {
last.seg.width -= 1;
last.width -= 1;
}
last.seg.start == last.seg.end
last.start == last.end
};
if last_empty {
self.segments.pop().unwrap();

View File

@ -1,18 +1,16 @@
use super::chunk::Chunk;
use super::segment::{Segment, SegmentWithText};
use super::segment::{Segment};
use std::rc::Rc;
use unicode_width::UnicodeWidthStr;
use utils::span::SpannedString;
use utils::span::SpannedText;
use xi_unicode::LineBreakLeafIter;
/// Iterator that returns non-breakable chunks of text.
///
/// Works accross spans of text.
pub struct ChunkIterator<'a, T>
where
T: 'a,
{
pub struct ChunkIterator<S> {
/// Input that we want to chunk.
source: &'a SpannedString<T>,
source: Rc<S>,
/// ID of the span we are processing.
current_span: usize,
@ -21,12 +19,9 @@ where
offset: usize,
}
impl<'a, T> ChunkIterator<'a, T>
where
T: 'a,
{
impl<S> ChunkIterator<S> {
/// Creates a new ChunkIterator on the given styled string.
pub fn new(source: &'a SpannedString<T>) -> Self {
pub fn new(source: Rc<S>) -> Self {
ChunkIterator {
source,
current_span: 0,
@ -39,30 +34,30 @@ where
///
/// These chunks may go accross spans (a single word may be broken into more
/// than one span, for instance if parts of it are marked up differently).
impl<'a, T> Iterator for ChunkIterator<'a, T>
impl<S> Iterator for ChunkIterator<S>
where
T: 'a,
S: SpannedText,
{
type Item = Chunk<'a>;
type Item = Chunk;
fn next(&mut self) -> Option<Self::Item> {
if self.current_span >= self.source.spans_raw().len() {
if self.current_span >= self.source.spans().len() {
return None;
}
// Protect agains empty spans
if self.source.spans_raw()[self.current_span].is_empty() {
if self.source.spans()[self.current_span].as_ref().is_empty() {
self.current_span += 1;
return self.next();
}
let mut span = &self.source.spans_raw()[self.current_span];
let mut span_text = span.content.resolve(self.source.source());
let mut span = self.source.spans()[self.current_span].as_ref();
let mut span_text = span.resolve(self.source.source());
let mut total_width = 0;
// We'll use an iterator from xi-unicode to detect possible breaks.
let text = span.content.resolve(self.source.source());
let text = span.resolve(self.source.source());
let mut iter = LineBreakLeafIter::new(text, self.offset);
// We'll accumulate segments from spans.
@ -89,9 +84,8 @@ where
let (width, ends_with_space) = if pos == 0 {
// If pos = 0, we had a span before.
let prev_span =
&self.source.spans_raw()[self.current_span - 1];
let prev_text =
prev_span.content.resolve(self.source.source());
self.source.spans()[self.current_span - 1].as_ref();
let prev_text = prev_span.resolve(self.source.source());
(0, prev_text.ends_with(' '))
} else {
// We actually got something.
@ -108,14 +102,11 @@ where
if pos != 0 {
// If pos != 0, we got an actual segment of a span.
total_width += width;
segments.push(SegmentWithText {
seg: Segment {
span_id: self.current_span,
start: self.offset,
end: pos,
width,
},
text: &span_text[self.offset..pos],
segments.push(Segment {
span_id: self.current_span,
start: self.offset,
end: pos,
width,
});
}
@ -125,19 +116,17 @@ where
self.current_span += 1;
// Skip empty spans
while let Some(true) = self.source
.spans_raw()
.get(self.current_span)
.map(|span| {
span.content.resolve(self.source.source()).is_empty()
while let Some(true) =
self.source.spans().get(self.current_span).map(|span| {
span.as_ref().resolve(self.source.source()).is_empty()
}) {
self.current_span += 1;
}
if self.current_span >= self.source.spans_raw().len() {
if self.current_span >= self.source.spans().len() {
// If this was the last chunk, return as is!
// Well, make sure we don't end with a newline...
let text = span.content.resolve(self.source.source());
let text = span.resolve(self.source.source());
let hard_stop = hard_stop || text.ends_with('\n');
return Some(Chunk {
@ -148,8 +137,8 @@ where
});
}
span = &self.source.spans_raw()[self.current_span];
span_text = span.content.resolve(self.source.source());
span = self.source.spans()[self.current_span].as_ref();
span_text = span.resolve(self.source.source());
self.offset = 0;
continue;
}

View File

@ -5,18 +5,20 @@ use super::row::Row;
use super::segment::{Segment, SegmentWithText};
use super::segment_merge_iterator::SegmentMergeIterator;
use std::iter::Peekable;
use std::rc::Rc;
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use utils::span::SpannedString;
use utils::span::SpannedText;
/// Generates rows of text in constrainted width.
///
/// Works on spans of text.
pub struct LinesIterator<'a, T>
pub struct LinesIterator<S>
where
T: 'a,
S: SpannedText,
{
iter: Peekable<ChunkIterator<'a, T>>,
iter: Peekable<ChunkIterator<S>>,
source: Rc<S>,
/// Available width
width: usize,
@ -26,18 +28,27 @@ where
chunk_offset: ChunkPart,
}
impl<'a, T> LinesIterator<'a, T> {
impl<S> LinesIterator<S>
where
S: SpannedText,
{
/// Creates a new iterator with the given content and width.
pub fn new(source: &'a SpannedString<T>, width: usize) -> Self {
pub fn new(source: S, width: usize) -> Self {
let source = Rc::new(source);
let chunk_source = source.clone();
LinesIterator {
iter: ChunkIterator::new(source).peekable(),
iter: ChunkIterator::new(chunk_source).peekable(),
source,
width,
chunk_offset: ChunkPart::default(),
}
}
}
impl<'a, T> Iterator for LinesIterator<'a, T> {
impl<S> Iterator for LinesIterator<S>
where
S: SpannedText,
{
type Item = Row;
fn next(&mut self) -> Option<Row> {
@ -59,9 +70,13 @@ impl<'a, T> Iterator for LinesIterator<'a, T> {
chunk.remove_front(self.chunk_offset);
// Try to fit part of it?
let graphemes = chunk.segments.iter().flat_map(|seg| {
let mut offset = seg.seg.start;
seg.text.graphemes(true).map(move |g| {
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();
@ -69,14 +84,11 @@ impl<'a, T> Iterator for LinesIterator<'a, T> {
Chunk {
width,
segments: vec![
SegmentWithText {
text: g,
seg: Segment {
width,
span_id: seg.seg.span_id,
start,
end,
},
Segment {
width,
span_id: seg.span_id,
start,
end,
},
],
hard_stop: false,
@ -103,7 +115,7 @@ impl<'a, T> Iterator for LinesIterator<'a, T> {
let length: usize = chunks
.iter()
.flat_map(|chunk| chunk.segments.iter())
.map(|segment| segment.text.len())
.map(|segment| segment.end - segment.start)
.sum();
self.chunk_offset.width += width;
@ -120,7 +132,6 @@ impl<'a, T> Iterator for LinesIterator<'a, T> {
chunks
.into_iter()
.flat_map(|chunk| chunk.segments)
.map(|segment| segment.seg)
.filter(|segment| segment.start != segment.end),
).collect();

View File

@ -2,11 +2,11 @@ use super::chunk::{Chunk, ChunkPart};
use std::iter::Peekable;
/// Concatenates chunks as long as they fit in the given width.
pub fn prefix<'a, I>(
pub fn prefix<I>(
tokens: &mut Peekable<I>, width: usize, offset: &mut ChunkPart
) -> Vec<Chunk<'a>>
) -> Vec<Chunk>
where
I: Iterator<Item = Chunk<'a>>,
I: Iterator<Item = Chunk>,
{
let mut available = width;
let mut chunks = Vec::new();

View File

@ -1,5 +1,5 @@
use super::Segment;
use utils::span::{Span, SpannedString};
use utils::span::{Span, SpannedStr};
/// A list of segments representing a row of text
#[derive(Debug, Clone, PartialEq, Eq)]
@ -13,11 +13,11 @@ pub struct Row {
impl Row {
/// Resolve the row indices into string slices and attributes.
pub fn resolve<'a, T>(
&self, source: &'a SpannedString<T>
&self, source: SpannedStr<'a, T>
) -> Vec<Span<'a, T>> {
self.segments
.iter()
.map(|seg| seg.resolve(source))
.map(|seg| seg.resolve(source.clone()))
.collect()
}
}

View File

@ -1,4 +1,4 @@
use utils::span::{Span, SpannedString};
use utils::span::{SpannedStr, Span, SpannedText};
/// Refers to a part of a span
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -22,7 +22,9 @@ impl Segment {
}
/// Resolve this segment to a string slice and an attribute.
pub fn resolve<'a, T>(&self, source: &'a SpannedString<T>) -> Span<'a, T> {
pub fn resolve<'a, T>(
&self, source: SpannedStr<'a, T>
) -> Span<'a, T> {
let span = &source.spans_raw()[self.span_id];
let content = span.content.resolve(source.source());
@ -33,6 +35,19 @@ impl Segment {
attr: &span.attr,
}
}
/// Resolves this segment to plain text.
pub fn resolve_plain<'a, S>(&self, source: &'a S) -> &'a str
where
S: SpannedText,
{
let span = &source.spans()[self.span_id];
let content = span.as_ref().resolve(source.source());
let content = &content[self.start..self.end];
content
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]

View File

@ -21,7 +21,7 @@ fn test_line_breaks() {
let iter = LinesIterator::new(&input, 17);
let rows: Vec<_> = iter.map(|row| row.resolve(&input)).collect();
let rows: Vec<_> = iter.map(|row| row.resolve(input.as_spanned_str())).collect();
assert_eq!(
&rows[..],

View File

@ -461,7 +461,7 @@ impl View for TextView {
let l = row.width;
let mut x = self.align.h.get_offset(l, printer.size.x);
for span in row.resolve(&content.content) {
for span in row.resolve(content.content.as_spanned_str()) {
printer.with_style(*span.attr, |printer| {
printer.print((x, 0), span.content);
x += span.content.width();