Introduce FoldMapSnapshot::chunks_at, use it in FoldMap::text

Get the FoldMap randomized tests passing. This also required adding
a test-only FoldMap::prev_char_boundary method.
This commit is contained in:
Max Brunsfeld
2021-05-20 12:47:08 +02:00
committed by Antonio Scandurra
parent b3d2a70834
commit a9583d0074
3 changed files with 163 additions and 55 deletions
+2 -5
View File
@@ -5,7 +5,7 @@ mod selection;
pub use anchor::*;
pub use point::*;
pub use rope::{Rope, TextSummary};
pub use rope::{ChunksIter, Rope, TextSummary};
use seahash::SeaHasher;
pub use selection::*;
use similar::{ChangeTag, TextDiff};
@@ -637,10 +637,7 @@ impl Buffer {
self.text_for_range(0..self.len()).collect()
}
pub fn text_for_range<'a, T: ToOffset>(
&'a self,
range: Range<T>,
) -> impl 'a + Iterator<Item = &'a str> {
pub fn text_for_range<'a, T: ToOffset>(&'a self, range: Range<T>) -> ChunksIter<'a> {
let start = range.start.to_offset(self);
let end = range.end.to_offset(self);
self.visible_text.chunks_in_range(start..end)
+29 -10
View File
@@ -115,11 +115,11 @@ impl Rope {
Chars::new(self, start)
}
pub fn chunks<'a>(&'a self) -> impl Iterator<Item = &'a str> {
pub fn chunks<'a>(&'a self) -> ChunksIter<'a> {
self.chunks_in_range(0..self.len())
}
pub fn chunks_in_range<'a>(&'a self, range: Range<usize>) -> impl Iterator<Item = &'a str> {
pub fn chunks_in_range<'a>(&'a self, range: Range<usize>) -> ChunksIter<'a> {
ChunksIter::new(self, range)
}
@@ -270,21 +270,40 @@ impl<'a> ChunksIter<'a> {
chunks.seek(&range.start, SeekBias::Right, &());
Self { chunks, range }
}
pub fn offset(&self) -> usize {
self.range.start.max(*self.chunks.start())
}
pub fn advance_to(&mut self, offset: usize) {
if offset >= self.chunks.end() {
self.chunks.seek_forward(&offset, SeekBias::Right, &());
self.range.start = offset;
}
}
pub fn peek(&self) -> Option<&'a str> {
if let Some(chunk) = self.chunks.item() {
let offset = *self.chunks.start();
if self.range.end > offset {
let start = self.range.start.saturating_sub(*self.chunks.start());
let end = self.range.end - self.chunks.start();
return Some(&chunk.0[start..chunk.0.len().min(end)]);
}
}
None
}
}
impl<'a> Iterator for ChunksIter<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
if let Some(chunk) = self.chunks.item() {
if self.range.end > *self.chunks.start() {
let start = self.range.start.saturating_sub(*self.chunks.start());
let end = self.range.end - self.chunks.start();
self.chunks.next();
return Some(&chunk.0[start..chunk.0.len().min(end)]);
}
let result = self.peek();
if result.is_some() {
self.chunks.next();
}
None
result
}
}
+132 -40
View File
@@ -3,7 +3,7 @@ use super::{
Anchor, Buffer, DisplayPoint, Edit, Point, ToOffset,
};
use crate::{
editor::rope,
editor::{buffer, rope},
sum_tree::{self, Cursor, FilterCursor, SeekBias, SumTree},
time,
};
@@ -208,11 +208,7 @@ impl FoldMap {
}
pub fn to_buffer_offset(&self, point: DisplayPoint, ctx: &AppContext) -> usize {
let transforms = self.sync(ctx);
let mut cursor = transforms.cursor::<DisplayPoint, TransformSummary>();
cursor.seek(&point, SeekBias::Right, &());
let overshoot = point.0 - cursor.start().display.lines;
(cursor.start().buffer.lines + overshoot).to_offset(self.buffer.read(ctx))
self.snapshot(ctx).to_buffer_offset(point, ctx)
}
pub fn to_display_offset(&self, point: DisplayPoint, ctx: &AppContext) -> DisplayOffset {
@@ -238,6 +234,26 @@ impl FoldMap {
))
}
#[cfg(test)]
pub fn prev_char_boundary(&self, offset: DisplayOffset, ctx: &AppContext) -> DisplayOffset {
let transforms = self.sync(ctx);
let mut cursor = transforms.cursor::<DisplayOffset, TransformSummary>();
cursor.seek(&offset, SeekBias::Right, &());
if let Some(transform) = cursor.item() {
if transform.display_text.is_some() {
DisplayOffset(cursor.start().display.bytes)
} else {
let overshoot = offset.0 - cursor.start().display.bytes;
let buffer_offset = cursor.start().buffer.bytes + overshoot;
let buffer_boundary_offset =
self.buffer.read(ctx).prev_char_boundary(buffer_offset);
DisplayOffset(offset.0 - (buffer_offset - buffer_boundary_offset))
}
} else {
offset
}
}
fn sync(&self, ctx: &AppContext) -> MutexGuard<SumTree<Transform>> {
let buffer = self.buffer.read(ctx);
let mut edits = buffer.edits_since(self.last_sync.lock().clone()).peekable();
@@ -334,18 +350,20 @@ impl FoldMap {
}
if fold.end > fold.start {
let display_text = "";
let extent = Point::new(0, display_text.len() as u32);
new_transforms.push(
Transform {
summary: TransformSummary {
display: TextSummary {
bytes: ''.len_utf8(),
lines: Point::new(0, 1),
first_line_len: 1,
rightmost_point: Point::new(0, 1),
bytes: display_text.len(),
lines: extent,
first_line_len: extent.column,
rightmost_point: extent,
},
buffer: buffer.text_summary_for_range(fold.start..fold.end),
},
display_text: Some('…'),
display_text: Some(display_text),
},
&(),
);
@@ -410,6 +428,20 @@ impl FoldMapSnapshot {
}
}
pub fn chunks_at<'a>(&'a self, offset: DisplayOffset, ctx: &'a AppContext) -> Chunks<'a> {
let mut transform_cursor = self.transforms.cursor::<DisplayOffset, TransformSummary>();
transform_cursor.seek(&offset, SeekBias::Right, &());
let overshoot = offset.0 - transform_cursor.start().display.bytes;
let buffer_offset = transform_cursor.start().buffer.bytes + overshoot;
let buffer = self.buffer.read(ctx);
let rope_cursor = buffer.text_for_range(buffer_offset..buffer.len());
Chunks {
transform_cursor,
buffer_offset,
buffer_chunks: rope_cursor,
}
}
pub fn chars_at<'a>(&'a self, point: DisplayPoint, ctx: &'a AppContext) -> Chars<'a> {
let offset = self.to_display_offset(point, ctx);
let mut cursor = self.transforms.cursor();
@@ -436,12 +468,19 @@ impl FoldMapSnapshot {
}
DisplayOffset(offset)
}
pub fn to_buffer_offset(&self, point: DisplayPoint, ctx: &AppContext) -> usize {
let mut cursor = self.transforms.cursor::<DisplayPoint, TransformSummary>();
cursor.seek(&point, SeekBias::Right, &());
let overshoot = point.0 - cursor.start().display.lines;
(cursor.start().buffer.lines + overshoot).to_offset(self.buffer.read(ctx))
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct Transform {
summary: TransformSummary,
display_text: Option<char>,
display_text: Option<&'static str>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
@@ -601,7 +640,7 @@ impl<'a> Iterator for Chars<'a> {
fn next(&mut self) -> Option<Self::Item> {
if let Some(c) = self.buffer_chars.as_mut().and_then(|chars| chars.next()) {
self.offset += 1;
self.offset += c.len_utf8();
return Some(c);
}
@@ -611,8 +650,8 @@ impl<'a> Iterator for Chars<'a> {
self.cursor.item().and_then(|transform| {
if let Some(c) = transform.display_text {
self.offset += 1;
Some(c)
self.offset += c.len();
Some(c.chars().next().unwrap())
} else {
let overshoot = self.offset - self.cursor.start().display.bytes;
let buffer_start = self.cursor.start().buffer.bytes + overshoot;
@@ -624,6 +663,59 @@ impl<'a> Iterator for Chars<'a> {
}
}
pub struct Chunks<'a> {
transform_cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>,
buffer_chunks: buffer::ChunksIter<'a>,
buffer_offset: usize,
}
impl<'a> Iterator for Chunks<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
let transform = if let Some(item) = self.transform_cursor.item() {
item
} else {
return None;
};
// If we're in a fold, then return the fold's display text and
// advance the transform and buffer cursors to the end of the fold.
if let Some(display_text) = transform.display_text {
self.buffer_offset += transform.summary.buffer.bytes;
self.buffer_chunks.advance_to(self.buffer_offset);
while self.buffer_offset >= self.transform_cursor.end().buffer.bytes
&& self.transform_cursor.item().is_some()
{
self.transform_cursor.next();
}
return Some(display_text);
}
// Otherwise, take a chunk from the buffer's text.
if let Some(mut chunk) = self.buffer_chunks.peek() {
let offset_in_chunk = self.buffer_offset - self.buffer_chunks.offset();
chunk = &chunk[offset_in_chunk..];
// Truncate the chunk so that it ends at the next fold.
let region_end = self.transform_cursor.end().buffer.bytes - self.buffer_offset;
if chunk.len() >= region_end {
chunk = &chunk[0..region_end];
self.transform_cursor.next();
} else {
self.buffer_chunks.next();
}
self.buffer_offset += chunk.len();
return Some(chunk);
}
None
}
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for DisplayPoint {
fn add_summary(&mut self, summary: &'a TransformSummary) {
self.0 += &summary.display.lines;
@@ -843,8 +935,8 @@ mod tests {
let buffer = buffer.read(app);
let mut to_fold = Vec::new();
for _ in 0..rng.gen_range(1..=5) {
let end = rng.gen_range(0..=buffer.len());
let start = rng.gen_range(0..=end);
let end = buffer.next_char_boundary(rng.gen_range(0..=buffer.len()));
let start = buffer.prev_char_boundary(rng.gen_range(0..=end));
to_fold.push(start..end);
}
log::info!("folding {:?}", to_fold);
@@ -854,8 +946,8 @@ mod tests {
let buffer = buffer.read(app);
let mut to_unfold = Vec::new();
for _ in 0..rng.gen_range(1..=3) {
let end = rng.gen_range(0..=buffer.len());
let start = rng.gen_range(0..=end);
let end = buffer.next_char_boundary(rng.gen_range(0..=buffer.len()));
let start = buffer.prev_char_boundary(rng.gen_range(0..=end));
to_unfold.push(start..end);
}
log::info!("unfolding {:?}", to_unfold);
@@ -892,7 +984,7 @@ mod tests {
for (display_row, line) in expected_text.lines().enumerate() {
let line_len = map.line_len(display_row as u32, app.as_ref());
assert_eq!(line_len, line.chars().count() as u32);
assert_eq!(line_len, line.len() as u32);
}
let rightmost_point = map.rightmost_point(app.as_ref());
@@ -903,24 +995,30 @@ mod tests {
let buffer_offset = buffer_point.to_offset(buffer);
assert_eq!(
map.to_display_point(buffer_point, app.as_ref()),
display_point
display_point,
"to_display_point({:?})",
buffer_point,
);
assert_eq!(
map.to_buffer_offset(display_point, app.as_ref()),
buffer_offset
buffer_offset,
"to_buffer_offset({:?})",
display_point,
);
assert_eq!(
map.to_display_offset(display_point, app.as_ref()),
display_offset
display_offset,
"to_display_offset({:?})",
display_point,
);
if c == '\n' {
*display_point.row_mut() += 1;
*display_point.column_mut() = 0;
} else {
*display_point.column_mut() += 1;
*display_point.column_mut() += c.len_utf8() as u32;
}
display_offset.0 += 1;
display_offset.0 += c.len_utf8();
if display_point.column() > rightmost_point.column() {
panic!(
"invalid rightmost point {:?}, found point {:?}",
@@ -930,21 +1028,15 @@ mod tests {
}
for _ in 0..5 {
let row = rng.gen_range(0..=map.max_point(app.as_ref()).row());
let column = rng.gen_range(0..=map.line_len(row, app.as_ref()));
let point = DisplayPoint::new(row, column);
let offset = map.to_display_offset(point, app.as_ref()).0;
let len = rng.gen_range(0..=map.len(app.as_ref()) - offset);
let offset = map.prev_char_boundary(
DisplayOffset(rng.gen_range(0..=map.len(app.as_ref()))),
app.as_ref(),
);
assert_eq!(
map.snapshot(app.as_ref())
.chars_at(point, app.as_ref())
.take(len)
.chunks_at(offset, app.as_ref())
.collect::<String>(),
expected_text
.chars()
.skip(offset)
.take(len)
.collect::<String>()
&expected_text[offset.0..],
);
}
@@ -967,8 +1059,8 @@ mod tests {
}
for _ in 0..5 {
let end = rng.gen_range(0..=buffer.len());
let start = rng.gen_range(0..=end);
let end = buffer.next_char_boundary(rng.gen_range(0..=buffer.len()));
let start = buffer.prev_char_boundary(rng.gen_range(0..=end));
let expected_folds = map
.folds
.items()
@@ -1026,7 +1118,7 @@ mod tests {
impl FoldMap {
fn text(&self, app: &AppContext) -> String {
self.snapshot(app)
.chars_at(DisplayPoint(Point::zero()), app)
.chunks_at(DisplayOffset(0), app)
.collect()
}