From e0930ddc091ddad3be4b281c967cb42605f21304 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 15 Jul 2021 18:27:59 -0700 Subject: [PATCH] Add FoldMapSnapshot::edits_since_creation --- zed/src/editor/display_map.rs | 2 +- zed/src/editor/display_map/fold_map.rs | 280 ++++++++++++++++++------- 2 files changed, 208 insertions(+), 74 deletions(-) diff --git a/zed/src/editor/display_map.rs b/zed/src/editor/display_map.rs index 03827fb1761eee9692742266616c08f1ad260741..30acefa280714200ae03e83e76ddb28dff153a9d 100644 --- a/zed/src/editor/display_map.rs +++ b/zed/src/editor/display_map.rs @@ -3,7 +3,7 @@ mod wrap_map; use crate::settings::StyleId; -use super::{buffer, Anchor, Bias, Buffer, Edit, Point, ToOffset, ToPoint}; +use super::{buffer, Anchor, Bias, Buffer, Point, ToOffset, ToPoint}; pub use fold_map::BufferRows; use fold_map::{FoldMap, FoldMapSnapshot}; use gpui::{AppContext, ModelHandle}; diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 1fa13f8aa44e9a7031ae424f6e61de2063dc640f..bb7c1d30892f267740a80f83295e61e80585eee1 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -13,8 +13,10 @@ use gpui::{AppContext, ModelHandle}; use parking_lot::Mutex; use std::{ cmp::{self, Ordering}, + collections::VecDeque, iter, ops::Range, + sync::Arc, }; pub struct FoldMap { @@ -22,6 +24,14 @@ pub struct FoldMap { transforms: Mutex>, folds: SumTree, last_sync: Mutex, + history: Arc>, +} + +#[derive(Default)] +struct FoldMapHistory { + edits: VecDeque, + oldest_edit_version: usize, + snapshot_versions: Vec, } impl FoldMap { @@ -41,15 +51,21 @@ impl FoldMap { &(), )), last_sync: Mutex::new(buffer.version()), + history: Default::default(), } } pub fn snapshot(&self, cx: &AppContext) -> FoldMapSnapshot { self.sync(cx); + let mut history = self.history.lock(); + let version = history.oldest_edit_version + history.edits.len(); + history.snapshot_versions.push(version); FoldMapSnapshot { transforms: self.transforms.lock().clone(), folds: self.folds.clone(), buffer: self.buffer.read(cx).snapshot(), + history: self.history.clone(), + version, } } @@ -76,12 +92,6 @@ impl FoldMap { } folds.sort_unstable_by(|a, b| sum_tree::SeekDimension::cmp(a, b, &buffer)); - edits.sort_unstable_by(|a, b| { - a.old_bytes - .start - .cmp(&b.old_bytes.start) - .then_with(|| b.old_bytes.end.cmp(&a.old_bytes.end)) - }); self.folds = { let mut new_tree = SumTree::new(); @@ -93,6 +103,8 @@ impl FoldMap { new_tree.push_tree(cursor.suffix(&buffer), &buffer); new_tree }; + + self.consolidate_edits(&mut edits); self.apply_edits(edits, cx); } @@ -103,14 +115,12 @@ impl FoldMap { ) { self.sync(cx); - let buffer = self.buffer.read(cx).snapshot(); - let snapshot = self.snapshot(cx); - let mut edits = Vec::new(); let mut fold_ixs_to_delete = Vec::new(); + let buffer = self.buffer.read(cx).snapshot(); for range in ranges.into_iter() { // Remove intersecting folds and add their ranges to edits that are passed to apply_edits. - let mut folds_cursor = snapshot.intersecting_folds(range); + let mut folds_cursor = intersecting_folds(&buffer, &self.folds, range); while let Some(fold) = folds_cursor.item() { let offset_range = fold.0.start.to_offset(&buffer)..fold.0.end.to_offset(&buffer); edits.push(Edit { @@ -124,12 +134,6 @@ impl FoldMap { fold_ixs_to_delete.sort_unstable(); fold_ixs_to_delete.dedup(); - edits.sort_unstable_by(|a, b| { - a.old_bytes - .start - .cmp(&b.old_bytes.start) - .then_with(|| b.old_bytes.end.cmp(&a.old_bytes.end)) - }); self.folds = { let mut cursor = self.folds.cursor::<_, ()>(); @@ -141,25 +145,24 @@ impl FoldMap { folds.push_tree(cursor.suffix(&buffer), &buffer); folds }; + + self.consolidate_edits(&mut edits); self.apply_edits(edits, cx); } - fn sync(&self, cx: &AppContext) -> Vec { + fn sync(&self, cx: &AppContext) { let buffer = self.buffer.read(cx); - let mut edits = buffer + let edits = buffer .edits_since(self.last_sync.lock().clone()) .map(Into::into) - .peekable(); - let edits = if edits.peek().is_some() { - self.apply_edits(edits, cx) - } else { - Vec::new() - }; + .collect::>(); + if !edits.is_empty() { + self.apply_edits(edits, cx); + } *self.last_sync.lock() = buffer.version(); - edits } - fn apply_edits(&self, edits: Vec, cx: &AppContext) -> Vec { + fn apply_edits(&self, mut edits: Vec, cx: &AppContext) { let buffer = self.buffer.read(cx).snapshot(); let mut edits_iter = edits.iter().cloned().peekable(); @@ -311,6 +314,8 @@ impl FoldMap { { let mut old_transforms = transforms.cursor::(); let mut new_transforms = new_transforms.cursor::(); + + let mut delta = 0; for edit in &mut edits { old_transforms.seek_forward(&edit.old_bytes.start, Bias::Right, &()); edit.old_bytes.start = old_transforms.sum_start().0 @@ -324,12 +329,38 @@ impl FoldMap { new_transforms.seek_forward(&edit.new_bytes.end, Bias::Right, &()); edit.new_bytes.end = new_transforms.sum_start().0 + (edit.new_bytes.end - new_transforms.seek_start()); + + edit.old_bytes.start = (edit.old_bytes.start as isize + delta) as usize; + edit.old_bytes.end = (edit.old_bytes.end as isize + delta) as usize; + delta += edit.delta(); } } *transforms = new_transforms; + self.history.lock().edits.extend(edits); + } - edits + fn consolidate_edits(&self, edits: &mut Vec) { + edits.sort_unstable_by(|a, b| { + a.old_bytes + .start + .cmp(&b.old_bytes.start) + .then_with(|| b.old_bytes.end.cmp(&a.old_bytes.end)) + }); + + let mut i = 0; + while i < edits.len() { + let range = edits[i].old_bytes.clone(); + if i > 0 { + if edits[i - 1].old_bytes.end >= range.start { + edits[i - 1].old_bytes.end = edits[i - 1].old_bytes.end.max(range.end); + edits[i - 1].new_bytes.end = edits[i - 1].new_bytes.end.max(range.end); + edits.remove(i); + continue; + } + } + i += 1; + } } } @@ -337,9 +368,22 @@ pub struct FoldMapSnapshot { transforms: SumTree, folds: SumTree, buffer: buffer::Snapshot, + history: Arc>, + version: usize, } impl FoldMapSnapshot { + pub fn edits_since_creation(&self) -> Vec { + let history = self.history.lock(); + let index = self.version - history.oldest_edit_version; + history.edits.range(index..).cloned().collect() + } + + #[cfg(test)] + pub fn text(&self) -> String { + self.chunks_at(DisplayOffset(0)).collect() + } + pub fn len(&self) -> usize { self.transforms.summary().display.bytes } @@ -384,7 +428,7 @@ impl FoldMapSnapshot { where T: ToOffset, { - let mut folds = self.intersecting_folds(range); + let mut folds = intersecting_folds(&self.buffer, &self.folds, range); iter::from_fn(move || { let item = folds.item().map(|f| &f.0); folds.next(&self.buffer); @@ -392,26 +436,6 @@ impl FoldMapSnapshot { }) } - fn intersecting_folds<'a, T>( - &'a self, - range: Range, - ) -> FilterCursor bool, Fold, usize> - where - T: ToOffset, - { - let start = self - .buffer - .anchor_before(range.start.to_offset(&self.buffer)); - let end = self.buffer.anchor_after(range.end.to_offset(&self.buffer)); - self.folds.filter::<_, usize>( - move |summary| { - start.cmp(&summary.max_end, &self.buffer).unwrap() == Ordering::Less - && end.cmp(&summary.min_start, &self.buffer).unwrap() == Ordering::Greater - }, - &self.buffer, - ) - } - pub fn intersects_fold(&self, offset: T) -> bool where T: ToOffset, @@ -569,6 +593,56 @@ impl FoldMapSnapshot { } } +impl Drop for FoldMapSnapshot { + fn drop(&mut self) { + let mut history = self.history.lock(); + + // Remove this snapshot's version number from the set of currently-snapshotted + // version numbers. + let mut i = 0; + let mut min_snapshot_version = None; + while i < history.snapshot_versions.len() { + let version = history.snapshot_versions[i]; + if version == self.version { + history.snapshot_versions.remove(i); + } else { + if min_snapshot_version.map_or(true, |min_version| min_version > version) { + min_snapshot_version = Some(version); + } + i += 1; + } + } + + // Discard any edits that predate any outstanding snapshot. + if let Some(min_snapshot_version) = min_snapshot_version { + if min_snapshot_version > history.oldest_edit_version { + let remove_count = min_snapshot_version - history.oldest_edit_version; + drop(history.edits.drain(..remove_count)); + history.oldest_edit_version = min_snapshot_version; + } + } + } +} + +fn intersecting_folds<'a, T>( + buffer: &'a buffer::Snapshot, + folds: &'a SumTree, + range: Range, +) -> FilterCursor<'a, impl 'a + Fn(&FoldSummary) -> bool, Fold, usize> +where + T: ToOffset, +{ + let start = buffer.anchor_before(range.start.to_offset(buffer)); + let end = buffer.anchor_after(range.end.to_offset(buffer)); + folds.filter::<_, usize>( + move |summary| { + start.cmp(&summary.max_end, buffer).unwrap() == Ordering::Less + && end.cmp(&summary.min_start, buffer).unwrap() == Ordering::Greater + }, + buffer, + ) +} + #[derive(Clone, Debug, Default, Eq, PartialEq)] struct Transform { summary: TransformSummary, @@ -667,6 +741,7 @@ impl sum_tree::Summary for FoldSummary { assert!(self.end.cmp(&other.end, buffer).unwrap() >= Ordering::Equal); } } + self.start = other.start.clone(); self.end = other.end.clone(); self.count += other.count; @@ -864,10 +939,10 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize { } } -#[derive(Clone)] -struct Edit { - old_bytes: Range, - new_bytes: Range, +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Edit { + pub old_bytes: Range, + pub new_bytes: Range, } impl Edit { @@ -903,6 +978,7 @@ mod tests { fn test_basic_folds(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6), cx)); let mut map = FoldMap::new(buffer.clone(), cx.as_ref()); + let snapshot1 = map.snapshot(cx.as_ref()); map.fold( vec![ @@ -911,7 +987,23 @@ mod tests { ], cx.as_ref(), ); - assert_eq!(map.text(cx.as_ref()), "aa…cc…eeeee"); + let snapshot2 = map.snapshot(cx.as_ref()); + assert_eq!(snapshot2.text(), "aa…cc…eeeee"); + + assert_eq!(snapshot2.edits_since_creation(), &[]); + assert_eq!( + snapshot1.edits_since_creation(), + &[ + Edit { + old_bytes: 2..16, + new_bytes: 2..5, + }, + Edit { + old_bytes: 7..18, + new_bytes: 7..10 + }, + ] + ); buffer.update(cx, |buffer, cx| { buffer.edit( @@ -923,17 +1015,60 @@ mod tests { cx, ); }); - assert_eq!(map.text(cx.as_ref()), "123a…c123c…eeeee"); + let snapshot3 = map.snapshot(cx.as_ref()); + assert_eq!(snapshot3.text(), "123a…c123c…eeeee"); + + assert_eq!(snapshot3.edits_since_creation(), &[]); + assert_eq!( + snapshot2.edits_since_creation(), + &[ + Edit { + old_bytes: 0..1, + new_bytes: 0..3, + }, + Edit { + old_bytes: 8..8, + new_bytes: 8..11, + }, + ] + ); + assert_eq!( + snapshot1.edits_since_creation(), + &[ + Edit { + old_bytes: 2..16, + new_bytes: 2..5, + }, + Edit { + old_bytes: 7..18, + new_bytes: 7..10 + }, + Edit { + old_bytes: 0..1, + new_bytes: 0..3, + }, + Edit { + old_bytes: 8..8, + new_bytes: 8..11, + }, + ] + ); buffer.update(cx, |buffer, cx| { - let start_version = buffer.version.clone(); - buffer.edit(Some(Point::new(2, 6)..Point::new(4, 3)), "456", cx); - buffer.edits_since(start_version).collect::>() + buffer.edit(vec![Point::new(2, 6)..Point::new(4, 3)], "456", cx) }); - assert_eq!(map.text(cx.as_ref()), "123a…c123456eee"); + let snapshot4 = map.snapshot(cx.as_ref()); + assert_eq!(snapshot4.text(), "123a…c123456eee"); map.unfold(Some(Point::new(0, 4)..Point::new(0, 5)), cx.as_ref()); - assert_eq!(map.text(cx.as_ref()), "123aaaaa\nbbbbbb\nccc123456eee"); + let snapshot5 = map.snapshot(cx.as_ref()); + assert_eq!(snapshot5.text(), "123aaaaa\nbbbbbb\nccc123456eee"); + + drop(snapshot1); + drop(snapshot2); + drop(snapshot3); + drop(snapshot4); + assert_eq!(map.history.lock().edits, &[]); } #[gpui::test] @@ -945,17 +1080,17 @@ mod tests { map.fold(vec![5..8], cx.as_ref()); map.check_invariants(cx.as_ref()); - assert_eq!(map.text(cx.as_ref()), "abcde…ijkl"); + assert_eq!(map.snapshot(cx.as_ref()).text(), "abcde…ijkl"); // Create an fold adjacent to the start of the first fold. map.fold(vec![0..1, 2..5], cx.as_ref()); map.check_invariants(cx.as_ref()); - assert_eq!(map.text(cx.as_ref()), "…b…ijkl"); + assert_eq!(map.snapshot(cx.as_ref()).text(), "…b…ijkl"); // Create an fold adjacent to the end of the first fold. map.fold(vec![11..11, 8..10], cx.as_ref()); map.check_invariants(cx.as_ref()); - assert_eq!(map.text(cx.as_ref()), "…b…kl"); + assert_eq!(map.snapshot(cx.as_ref()).text(), "…b…kl"); } { @@ -964,7 +1099,7 @@ mod tests { // Create two adjacent folds. map.fold(vec![0..2, 2..5], cx.as_ref()); map.check_invariants(cx.as_ref()); - assert_eq!(map.text(cx.as_ref()), "…fghijkl"); + assert_eq!(map.snapshot(cx.as_ref()).text(), "…fghijkl"); // Edit within one of the folds. buffer.update(cx, |buffer, cx| { @@ -973,7 +1108,7 @@ mod tests { buffer.edits_since(version).collect::>() }); map.check_invariants(cx.as_ref()); - assert_eq!(map.text(cx.as_ref()), "12345…fghijkl"); + assert_eq!(map.snapshot(cx.as_ref()).text(), "12345…fghijkl"); } } @@ -990,7 +1125,7 @@ mod tests { ], cx.as_ref(), ); - assert_eq!(map.text(cx.as_ref()), "aa…eeeee"); + assert_eq!(map.snapshot(cx.as_ref()).text(), "aa…eeeee"); } #[gpui::test] @@ -1005,12 +1140,12 @@ mod tests { ], cx.as_ref(), ); - assert_eq!(map.text(cx.as_ref()), "aa…cccc\nd…eeeee"); + assert_eq!(map.snapshot(cx.as_ref()).text(), "aa…cccc\nd…eeeee"); buffer.update(cx, |buffer, cx| { buffer.edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", cx) }); - assert_eq!(map.text(cx.as_ref()), "aa…eeeee"); + assert_eq!(map.snapshot(cx.as_ref()).text(), "aa…eeeee"); } #[gpui::test] @@ -1126,7 +1261,7 @@ mod tests { expected_buffer_rows.extend((0..=next_row).rev()); expected_buffer_rows.reverse(); - assert_eq!(map.text(cx.as_ref()), expected_text); + assert_eq!(map.snapshot(cx.as_ref()).text(), expected_text); let snapshot = map.snapshot(cx.as_ref()); for (display_row, line) in expected_text.lines().enumerate() { @@ -1255,7 +1390,10 @@ mod tests { cx.as_ref(), ); - assert_eq!(map.text(cx.as_ref()), "aa…cccc\nd…eeeee\nffffff\n"); + assert_eq!( + map.snapshot(cx.as_ref()).text(), + "aa…cccc\nd…eeeee\nffffff\n" + ); assert_eq!( map.snapshot(cx.as_ref()).buffer_rows(0).collect::>(), vec![0, 3, 5, 6] @@ -1267,10 +1405,6 @@ mod tests { } impl FoldMap { - fn text(&self, cx: &AppContext) -> String { - self.snapshot(cx).chunks_at(DisplayOffset(0)).collect() - } - fn merged_fold_ranges(&self, cx: &AppContext) -> Vec> { let buffer = self.buffer.read(cx).snapshot(); let mut folds = self.folds.items(&buffer);