mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-27 11:16:03 +00:00
Remove attribute type from spans::LinesIterator
This commit is contained in:
parent
c220cc679a
commit
25e65a87e8
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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[..],
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user