Add FoldMapSnapshot::edits_since_creation

Max Brunsfeld created

Change summary

zed/src/editor/display_map.rs          |   2 
zed/src/editor/display_map/fold_map.rs | 280 ++++++++++++++++++++-------
2 files changed, 208 insertions(+), 74 deletions(-)

Detailed changes

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};

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<SumTree<Transform>>,
     folds: SumTree<Fold>,
     last_sync: Mutex<time::Global>,
+    history: Arc<Mutex<FoldMapHistory>>,
+}
+
+#[derive(Default)]
+struct FoldMapHistory {
+    edits: VecDeque<Edit>,
+    oldest_edit_version: usize,
+    snapshot_versions: Vec<usize>,
 }
 
 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<Edit> {
+    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::<Vec<_>>();
+        if !edits.is_empty() {
+            self.apply_edits(edits, cx);
+        }
         *self.last_sync.lock() = buffer.version();
-        edits
     }
 
-    fn apply_edits(&self, edits: Vec<Edit>, cx: &AppContext) -> Vec<Edit> {
+    fn apply_edits(&self, mut edits: Vec<Edit>, 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::<usize, DisplayOffset>();
             let mut new_transforms = new_transforms.cursor::<usize, DisplayOffset>();
+
+            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<Edit>) {
+        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<Transform>,
     folds: SumTree<Fold>,
     buffer: buffer::Snapshot,
+    history: Arc<Mutex<FoldMapHistory>>,
+    version: usize,
 }
 
 impl FoldMapSnapshot {
+    pub fn edits_since_creation(&self) -> Vec<Edit> {
+        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<T>,
-    ) -> FilterCursor<impl 'a + Fn(&FoldSummary) -> 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<T>(&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<Fold>,
+    range: Range<T>,
+) -> 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<usize>,
-    new_bytes: Range<usize>,
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Edit {
+    pub old_bytes: Range<usize>,
+    pub new_bytes: Range<usize>,
 }
 
 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::<Vec<_>>()
+            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::<Vec<_>>()
             });
             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<_>>(),
             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<Range<usize>> {
             let buffer = self.buffer.read(cx).snapshot();
             let mut folds = self.folds.items(&buffer);