diff --git a/src/utils/lines/spans/chunk.rs b/src/utils/lines/spans/chunk.rs index f42c644..6643c60 100644 --- a/src/utils/lines/spans/chunk.rs +++ b/src/utils/lines/spans/chunk.rs @@ -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>, + pub segments: Vec, 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(); diff --git a/src/utils/lines/spans/chunk_iterator.rs b/src/utils/lines/spans/chunk_iterator.rs index f06d5ba..f2afff2 100644 --- a/src/utils/lines/spans/chunk_iterator.rs +++ b/src/utils/lines/spans/chunk_iterator.rs @@ -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 { /// Input that we want to chunk. - source: &'a SpannedString, + source: Rc, /// 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 ChunkIterator { /// Creates a new ChunkIterator on the given styled string. - pub fn new(source: &'a SpannedString) -> Self { + pub fn new(source: Rc) -> 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 Iterator for ChunkIterator where - T: 'a, + S: SpannedText, { - type Item = Chunk<'a>; + type Item = Chunk; fn next(&mut self) -> Option { - 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; } diff --git a/src/utils/lines/spans/lines_iterator.rs b/src/utils/lines/spans/lines_iterator.rs index f8c42ea..eddb737 100644 --- a/src/utils/lines/spans/lines_iterator.rs +++ b/src/utils/lines/spans/lines_iterator.rs @@ -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 where - T: 'a, + S: SpannedText, { - iter: Peekable>, + iter: Peekable>, + source: Rc, /// Available width width: usize, @@ -26,18 +28,27 @@ where chunk_offset: ChunkPart, } -impl<'a, T> LinesIterator<'a, T> { +impl LinesIterator +where + S: SpannedText, +{ /// Creates a new iterator with the given content and width. - pub fn new(source: &'a SpannedString, 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 Iterator for LinesIterator +where + S: SpannedText, +{ type Item = Row; fn next(&mut self) -> Option { @@ -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(); diff --git a/src/utils/lines/spans/prefix.rs b/src/utils/lines/spans/prefix.rs index f713dc2..ddece70 100644 --- a/src/utils/lines/spans/prefix.rs +++ b/src/utils/lines/spans/prefix.rs @@ -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( tokens: &mut Peekable, width: usize, offset: &mut ChunkPart -) -> Vec> +) -> Vec where - I: Iterator>, + I: Iterator, { let mut available = width; let mut chunks = Vec::new(); diff --git a/src/utils/lines/spans/row.rs b/src/utils/lines/spans/row.rs index 64d3a94..e27efc4 100644 --- a/src/utils/lines/spans/row.rs +++ b/src/utils/lines/spans/row.rs @@ -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 + &self, source: SpannedStr<'a, T> ) -> Vec> { self.segments .iter() - .map(|seg| seg.resolve(source)) + .map(|seg| seg.resolve(source.clone())) .collect() } } diff --git a/src/utils/lines/spans/segment.rs b/src/utils/lines/spans/segment.rs index 66ddfd1..0ef167d 100644 --- a/src/utils/lines/spans/segment.rs +++ b/src/utils/lines/spans/segment.rs @@ -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) -> 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)] diff --git a/src/utils/lines/spans/tests.rs b/src/utils/lines/spans/tests.rs index a3a1f11..8289bf2 100644 --- a/src/utils/lines/spans/tests.rs +++ b/src/utils/lines/spans/tests.rs @@ -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[..], diff --git a/src/views/text_view.rs b/src/views/text_view.rs index e0f3370..484adc5 100644 --- a/src/views/text_view.rs +++ b/src/views/text_view.rs @@ -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();