From a9583d0074c4a3f878294ed2fb450cafb8e79a0c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 17 May 2021 16:38:42 -0700 Subject: [PATCH] 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. --- zed/src/editor/buffer/mod.rs | 7 +- zed/src/editor/buffer/rope.rs | 35 +++-- zed/src/editor/display_map/fold_map.rs | 172 +++++++++++++++++++------ 3 files changed, 161 insertions(+), 53 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 4113359f9de82314bb055ce4be4a7ed1fa08e191..ce76c34133822a6a44e87dd3d5648aa351e0645a 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -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, - ) -> impl 'a + Iterator { + pub fn text_for_range<'a, T: ToOffset>(&'a self, range: Range) -> ChunksIter<'a> { let start = range.start.to_offset(self); let end = range.end.to_offset(self); self.visible_text.chunks_in_range(start..end) diff --git a/zed/src/editor/buffer/rope.rs b/zed/src/editor/buffer/rope.rs index 3128aedbd31a2b71d801646e89c5cabebe282d32..e409084e11dae751fc80130c4dad0e19d5b90d38 100644 --- a/zed/src/editor/buffer/rope.rs +++ b/zed/src/editor/buffer/rope.rs @@ -115,11 +115,11 @@ impl Rope { Chars::new(self, start) } - pub fn chunks<'a>(&'a self) -> impl Iterator { + 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) -> impl Iterator { + pub fn chunks_in_range<'a>(&'a self, range: Range) -> ChunksIter<'a> { ChunksIter::new(self, range) } @@ -270,17 +270,24 @@ impl<'a> ChunksIter<'a> { chunks.seek(&range.start, SeekBias::Right, &()); Self { chunks, range } } -} -impl<'a> Iterator for ChunksIter<'a> { - type Item = &'a str; + pub fn offset(&self) -> usize { + self.range.start.max(*self.chunks.start()) + } - fn next(&mut self) -> Option { + 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() { - if self.range.end > *self.chunks.start() { + 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(); - self.chunks.next(); return Some(&chunk.0[start..chunk.0.len().min(end)]); } } @@ -288,6 +295,18 @@ impl<'a> Iterator for ChunksIter<'a> { } } +impl<'a> Iterator for ChunksIter<'a> { + type Item = &'a str; + + fn next(&mut self) -> Option { + let result = self.peek(); + if result.is_some() { + self.chunks.next(); + } + result + } +} + #[derive(Clone, Debug, Default)] struct Chunk(ArrayString<[u8; 2 * CHUNK_BASE]>); diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 2307aa46bff9284cb5f66d24b4c221a0395253f1..4b9115a52bb896fc3e7ca7bca7301fd707cfcf38 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -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::(); - 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::(); + 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> { 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::(); + 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::(); + 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, + 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 { 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 { + 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::(), - expected_text - .chars() - .skip(offset) - .take(len) - .collect::() + &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() }