Introduce buffer::Content<'a> struct to share logic with buffer::Snapshot

Nathan Sobo created

We want to move a bunch of methods on FoldMap to fold_map::Snapshot. This captures a buffer snapshot, and we'll need a bunch of methods that currently are on Buffer to also be implemented on buffer::Snapshot. This Content reference struct will be a good zero-cost place to store that logic.

Change summary

zed/src/editor.rs        |   4 
zed/src/editor/buffer.rs | 108 +++++++++++++++++++++++++++++------------
2 files changed, 78 insertions(+), 34 deletions(-)

Detailed changes

zed/src/editor.rs 🔗

@@ -1215,8 +1215,8 @@ impl Editor {
                         String::from_iter(clipboard_chars.by_ref().take(clipboard_selection.len));
 
                     self.buffer.update(cx, |buffer, cx| {
-                        let selection_start = selection.start.to_point(buffer);
-                        let selection_end = selection.end.to_point(buffer);
+                        let selection_start = selection.start.to_point(&*buffer);
+                        let selection_end = selection.end.to_point(&*buffer);
 
                         // If the corresponding selection was empty when this slice of the
                         // clipboard text was written, then the entire line containing the

zed/src/editor/buffer.rs 🔗

@@ -601,6 +601,7 @@ impl Buffer {
     pub fn snapshot(&self) -> Snapshot {
         Snapshot {
             text: self.visible_text.clone(),
+            fragments: self.fragments.clone(),
             tree: self.syntax_tree(),
             language: self.language.clone(),
             query_cursor: QueryCursorHandle::new(),
@@ -1005,10 +1006,6 @@ impl Buffer {
         self.visible_text.summary()
     }
 
-    pub fn text_summary_for_range(&self, range: Range<usize>) -> TextSummary {
-        self.visible_text.cursor(range.start).summary(range.end)
-    }
-
     pub fn len(&self) -> usize {
         self.fragments.extent::<usize>(&None)
     }
@@ -1149,7 +1146,7 @@ impl Buffer {
         // Skip invalid ranges and coalesce contiguous ones.
         let mut ranges: Vec<Range<usize>> = Vec::new();
         for range in ranges_iter {
-            let range = range.start.to_offset(self)..range.end.to_offset(self);
+            let range = range.start.to_offset(&*self)..range.end.to_offset(&*self);
             if has_new_text || !range.is_empty() {
                 if let Some(prev_range) = ranges.last_mut() {
                     if prev_range.end >= range.start {
@@ -1860,6 +1857,14 @@ impl Buffer {
         edit
     }
 
+    fn content<'a>(&'a self) -> Content<'a> {
+        self.into()
+    }
+
+    pub fn text_summary_for_range(&self, range: Range<usize>) -> TextSummary {
+        self.content().text_summary_for_range(range)
+    }
+
     pub fn anchor_before<T: ToOffset>(&self, position: T) -> Anchor {
         self.anchor_at(position, Bias::Left)
     }
@@ -1881,18 +1886,6 @@ impl Buffer {
         }
     }
 
-    fn summary_for_anchor(&self, anchor: &Anchor) -> TextSummary {
-        let cx = Some(anchor.version.clone());
-        let mut cursor = self.fragments.cursor::<VersionedOffset, usize>();
-        cursor.seek(&VersionedOffset::Offset(anchor.offset), anchor.bias, &cx);
-        let overshoot = if cursor.item().map_or(false, |fragment| fragment.visible) {
-            anchor.offset - cursor.seek_start().offset()
-        } else {
-            0
-        };
-        self.text_summary_for_range(0..*cursor.sum_start() + overshoot)
-    }
-
     fn full_offset_for_anchor(&self, anchor: &Anchor) -> usize {
         let cx = Some(anchor.version.clone());
         let mut cursor = self
@@ -1910,7 +1903,7 @@ impl Buffer {
 
     pub fn point_for_offset(&self, offset: usize) -> Result<Point> {
         if offset <= self.len() {
-            Ok(self.text_summary_for_range(0..offset).lines)
+            Ok(self.content().text_summary_for_range(0..offset).lines)
         } else {
             Err(anyhow!("offset out of bounds"))
         }
@@ -1957,6 +1950,7 @@ impl Clone for Buffer {
 
 pub struct Snapshot {
     text: Rope,
+    fragments: SumTree<Fragment>,
     tree: Option<Tree>,
     language: Option<Arc<Language>>,
     query_cursor: QueryCursorHandle,
@@ -2020,6 +2014,56 @@ impl Snapshot {
     }
 }
 
+pub struct Content<'a> {
+    visible_text: &'a Rope,
+    fragments: &'a SumTree<Fragment>,
+}
+
+impl<'a> From<&'a Snapshot> for Content<'a> {
+    fn from(snapshot: &'a Snapshot) -> Self {
+        Self {
+            visible_text: &snapshot.text,
+            fragments: &snapshot.fragments,
+        }
+    }
+}
+
+impl<'a> From<&'a Buffer> for Content<'a> {
+    fn from(buffer: &'a Buffer) -> Self {
+        Self {
+            visible_text: &buffer.visible_text,
+            fragments: &buffer.fragments,
+        }
+    }
+}
+
+impl<'a> From<&'a mut Buffer> for Content<'a> {
+    fn from(buffer: &'a mut Buffer) -> Self {
+        Self {
+            visible_text: &buffer.visible_text,
+            fragments: &buffer.fragments,
+        }
+    }
+}
+
+impl<'a> Content<'a> {
+    fn summary_for_anchor(&self, anchor: &Anchor) -> TextSummary {
+        let cx = Some(anchor.version.clone());
+        let mut cursor = self.fragments.cursor::<VersionedOffset, usize>();
+        cursor.seek(&VersionedOffset::Offset(anchor.offset), anchor.bias, &cx);
+        let overshoot = if cursor.item().map_or(false, |fragment| fragment.visible) {
+            anchor.offset - cursor.seek_start().offset()
+        } else {
+            0
+        };
+        self.text_summary_for_range(0..*cursor.sum_start() + overshoot)
+    }
+
+    pub fn text_summary_for_range(&self, range: Range<usize>) -> TextSummary {
+        self.visible_text.cursor(range.start).summary(range.end)
+    }
+}
+
 struct RopeBuilder<'a> {
     old_visible_cursor: rope::Cursor<'a>,
     old_deleted_cursor: rope::Cursor<'a>,
@@ -2704,46 +2748,46 @@ impl operation_queue::Operation for Operation {
 }
 
 pub trait ToOffset {
-    fn to_offset(&self, buffer: &Buffer) -> usize;
+    fn to_offset<'a>(&self, content: impl Into<Content<'a>>) -> usize;
 }
 
 impl ToOffset for Point {
-    fn to_offset(&self, buffer: &Buffer) -> usize {
-        buffer.visible_text.to_offset(*self)
+    fn to_offset<'a>(&self, content: impl Into<Content<'a>>) -> usize {
+        content.into().visible_text.to_offset(*self)
     }
 }
 
 impl ToOffset for usize {
-    fn to_offset(&self, _: &Buffer) -> usize {
+    fn to_offset<'a>(&self, _: impl Into<Content<'a>>) -> usize {
         *self
     }
 }
 
 impl ToOffset for Anchor {
-    fn to_offset(&self, buffer: &Buffer) -> usize {
-        buffer.summary_for_anchor(self).bytes
+    fn to_offset<'a>(&self, content: impl Into<Content<'a>>) -> usize {
+        content.into().summary_for_anchor(self).bytes
     }
 }
 
 impl<'a> ToOffset for &'a Anchor {
-    fn to_offset(&self, buffer: &Buffer) -> usize {
-        buffer.summary_for_anchor(self).bytes
+    fn to_offset<'b>(&self, content: impl Into<Content<'b>>) -> usize {
+        content.into().summary_for_anchor(self).bytes
     }
 }
 
 pub trait ToPoint {
-    fn to_point(&self, buffer: &Buffer) -> Point;
+    fn to_point<'a>(&self, content: impl Into<Content<'a>>) -> Point;
 }
 
 impl ToPoint for Anchor {
-    fn to_point(&self, buffer: &Buffer) -> Point {
-        buffer.summary_for_anchor(self).lines
+    fn to_point<'a>(&self, content: impl Into<Content<'a>>) -> Point {
+        content.into().summary_for_anchor(self).lines
     }
 }
 
 impl ToPoint for usize {
-    fn to_point(&self, buffer: &Buffer) -> Point {
-        buffer.visible_text.to_point(*self)
+    fn to_point<'a>(&self, content: impl Into<Content<'a>>) -> Point {
+        content.into().visible_text.to_point(*self)
     }
 }
 
@@ -3411,7 +3455,7 @@ mod tests {
                 .iter()
                 .map(|selection| {
                     assert_eq!(selection.start, selection.end);
-                    selection.start.to_point(&buffer)
+                    selection.start.to_point(&*buffer)
                 })
                 .collect::<Vec<_>>();
             assert_eq!(