Always represent anchor as a versioned offset

Max Brunsfeld created

Remove the `Start` and `End` variants, and always
use the structure that was previously called `Middle`.
This makes Anchors simpler to serialize and deserialize.

Change summary

zed/src/editor.rs                      |   8 
zed/src/editor/buffer.rs               | 145 ++++++++-------------------
zed/src/editor/buffer/anchor.rs        |  81 ++++++--------
zed/src/editor/display_map/fold_map.rs |  12 +-
4 files changed, 91 insertions(+), 155 deletions(-)

Detailed changes

zed/src/editor.rs 🔗

@@ -1629,7 +1629,7 @@ impl Editor {
 
     pub fn select_to_beginning(&mut self, _: &(), cx: &mut ViewContext<Self>) {
         let mut selection = self.selections(cx.as_ref()).last().unwrap().clone();
-        selection.set_head(self.buffer.read(cx), Anchor::Start);
+        selection.set_head(self.buffer.read(cx), Anchor::min());
         self.update_selections(vec![selection], true, cx);
     }
 
@@ -1648,15 +1648,15 @@ impl Editor {
 
     pub fn select_to_end(&mut self, _: &(), cx: &mut ViewContext<Self>) {
         let mut selection = self.selections(cx.as_ref()).last().unwrap().clone();
-        selection.set_head(self.buffer.read(cx), Anchor::End);
+        selection.set_head(self.buffer.read(cx), Anchor::max());
         self.update_selections(vec![selection], true, cx);
     }
 
     pub fn select_all(&mut self, _: &(), cx: &mut ViewContext<Self>) {
         let selection = Selection {
             id: post_inc(&mut self.next_selection_id),
-            start: Anchor::Start,
-            end: Anchor::End,
+            start: Anchor::min(),
+            end: Anchor::max(),
             reversed: false,
             goal: SelectionGoal::None,
         };

zed/src/editor/buffer.rs 🔗

@@ -1493,14 +1493,8 @@ impl Buffer {
                 Operation::UpdateSelections { selections, .. } => {
                     if let Some(selections) = selections {
                         selections.iter().all(|selection| {
-                            let contains_start = match &selection.start {
-                                Anchor::Middle { version, .. } => self.version >= *version,
-                                _ => true,
-                            };
-                            let contains_end = match &selection.end {
-                                Anchor::Middle { version, .. } => self.version >= *version,
-                                _ => true,
-                            };
+                            let contains_start = self.version >= selection.start.version;
+                            let contains_end = self.version >= selection.end.version;
                             contains_start && contains_end
                         })
                     } else {
@@ -1645,76 +1639,42 @@ impl Buffer {
         let offset = position.to_offset(self);
         let max_offset = self.len();
         assert!(offset <= max_offset, "offset is out of range");
-
-        if offset == 0 && bias == Bias::Left {
-            Anchor::Start
-        } else if offset == max_offset && bias == Bias::Right {
-            Anchor::End
-        } else {
-            let mut cursor = self.fragments.cursor::<usize, FragmentTextSummary>();
-            cursor.seek(&offset, bias, &None);
-            Anchor::Middle {
-                offset: offset + cursor.start().deleted,
-                bias,
-                version: self.version(),
-            }
+        let mut cursor = self.fragments.cursor::<usize, FragmentTextSummary>();
+        cursor.seek(&offset, bias, &None);
+        Anchor {
+            offset: offset + cursor.start().deleted,
+            bias,
+            version: self.version(),
         }
     }
 
     fn summary_for_anchor(&self, anchor: &Anchor) -> TextSummary {
-        match anchor {
-            Anchor::Start => TextSummary::default(),
-            Anchor::End => self.text_summary(),
-            Anchor::Middle {
-                offset,
-                bias,
-                version,
-            } => {
-                let mut cursor = self
-                    .fragments
-                    .cursor::<VersionedOffset, (VersionedOffset, usize)>();
-                cursor.seek(
-                    &VersionedOffset::Offset(*offset),
-                    *bias,
-                    &Some(version.clone()),
-                );
-                let fragment = cursor.item().unwrap();
-                let overshoot = if fragment.visible {
-                    offset - cursor.start().0.offset()
-                } else {
-                    0
-                };
-
-                self.text_summary_for_range(0..cursor.start().1 + overshoot)
-            }
-        }
+        let cx = Some(anchor.version.clone());
+        let mut cursor = self
+            .fragments
+            .cursor::<VersionedOffset, (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.start().0.offset()
+        } else {
+            0
+        };
+        self.text_summary_for_range(0..cursor.start().1 + overshoot)
     }
 
     fn full_offset_for_anchor(&self, anchor: &Anchor) -> usize {
-        match anchor {
-            Anchor::Start => 0,
-            Anchor::End => {
-                let summary = self.fragments.summary();
-                summary.text.visible + summary.text.deleted
-            }
-            Anchor::Middle {
-                offset,
-                bias,
-                version,
-            } => {
-                let mut cursor = self
-                    .fragments
-                    .cursor::<VersionedOffset, (VersionedOffset, FragmentTextSummary)>();
-                cursor.seek(
-                    &VersionedOffset::Offset(*offset),
-                    *bias,
-                    &Some(version.clone()),
-                );
-                let overshoot = offset - cursor.start().0.offset();
-                let summary = cursor.start().1;
-                summary.visible + summary.deleted + overshoot
-            }
-        }
+        let cx = Some(anchor.version.clone());
+        let mut cursor = self
+            .fragments
+            .cursor::<VersionedOffset, (VersionedOffset, FragmentTextSummary)>();
+        cursor.seek(&VersionedOffset::Offset(anchor.offset), anchor.bias, &cx);
+        let overshoot = if cursor.item().is_some() {
+            anchor.offset - cursor.start().0.offset()
+        } else {
+            0
+        };
+        let summary = cursor.start().1;
+        summary.visible + summary.deleted + overshoot
     }
 
     pub fn point_for_offset(&self, offset: usize) -> Result<Point> {
@@ -2311,34 +2271,19 @@ impl<'a> Into<proto::operation::Edit> for &'a EditOperation {
 
 impl<'a> Into<proto::Anchor> for &'a Anchor {
     fn into(self) -> proto::Anchor {
-        match self {
-            Anchor::Middle {
-                offset,
-                bias,
-                version,
-            } => proto::Anchor {
-                version: version
-                    .iter()
-                    .map(|entry| proto::VectorClockEntry {
-                        replica_id: entry.replica_id as u32,
-                        timestamp: entry.value,
-                    })
-                    .collect(),
-                offset: *offset as u64,
-                bias: match bias {
-                    Bias::Left => proto::anchor::Bias::Left as i32,
-                    Bias::Right => proto::anchor::Bias::Right as i32,
-                },
-            },
-            Anchor::Start => proto::Anchor {
-                version: Vec::new(),
-                bias: proto::anchor::Bias::Left as i32,
-                offset: 0,
-            },
-            Anchor::End => proto::Anchor {
-                version: Vec::new(),
-                bias: proto::anchor::Bias::Right as i32,
-                offset: u64::MAX,
+        proto::Anchor {
+            version: self
+                .version
+                .iter()
+                .map(|entry| proto::VectorClockEntry {
+                    replica_id: entry.replica_id as u32,
+                    timestamp: entry.value,
+                })
+                .collect(),
+            offset: self.offset as u64,
+            bias: match self.bias {
+                Bias::Left => proto::anchor::Bias::Left as i32,
+                Bias::Right => proto::anchor::Bias::Right as i32,
             },
         }
     }
@@ -2449,7 +2394,7 @@ impl TryFrom<proto::Anchor> for Anchor {
             });
         }
 
-        Ok(Self::Middle {
+        Ok(Self {
             offset: message.offset as usize,
             bias: if message.bias == proto::anchor::Bias::Left as i32 {
                 Bias::Left

zed/src/editor/buffer/anchor.rs 🔗

@@ -4,67 +4,58 @@ use anyhow::Result;
 use std::{cmp::Ordering, ops::Range};
 
 #[derive(Clone, Eq, PartialEq, Debug, Hash)]
-pub enum Anchor {
-    Start,
-    End,
-    Middle {
-        offset: usize,
-        bias: Bias,
-        version: time::Global,
-    },
+pub struct Anchor {
+    pub offset: usize,
+    pub bias: Bias,
+    pub version: time::Global,
 }
 
 impl Anchor {
+    pub fn min() -> Self {
+        Self {
+            offset: 0,
+            bias: Bias::Left,
+            version: Default::default(),
+        }
+    }
+
+    pub fn max() -> Self {
+        Self {
+            offset: usize::MAX,
+            bias: Bias::Right,
+            version: Default::default(),
+        }
+    }
+
     pub fn cmp(&self, other: &Anchor, buffer: &Buffer) -> Result<Ordering> {
         if self == other {
             return Ok(Ordering::Equal);
         }
 
-        Ok(match (self, other) {
-            (Anchor::Start, _) | (_, Anchor::End) => Ordering::Less,
-            (Anchor::End, _) | (_, Anchor::Start) => Ordering::Greater,
-            (
-                Anchor::Middle {
-                    offset: self_offset,
-                    bias: self_bias,
-                    version: self_version,
-                },
-                Anchor::Middle {
-                    offset: other_offset,
-                    bias: other_bias,
-                    version: other_version,
-                },
-            ) => {
-                let offset_comparison = if self_version == other_version {
-                    self_offset.cmp(other_offset)
-                } else {
-                    buffer
-                        .full_offset_for_anchor(self)
-                        .cmp(&buffer.full_offset_for_anchor(other))
-                };
+        let offset_comparison = if self.version == other.version {
+            self.offset.cmp(&other.offset)
+        } else {
+            buffer
+                .full_offset_for_anchor(self)
+                .cmp(&buffer.full_offset_for_anchor(other))
+        };
 
-                offset_comparison.then_with(|| self_bias.cmp(&other_bias))
-            }
-        })
+        Ok(offset_comparison.then_with(|| self.bias.cmp(&other.bias)))
     }
 
     pub fn bias_left(&self, buffer: &Buffer) -> Anchor {
-        match self {
-            Anchor::Start
-            | Anchor::Middle {
-                bias: Bias::Left, ..
-            } => self.clone(),
-            _ => buffer.anchor_before(self),
+        if self.bias == Bias::Left {
+            self.clone()
+        } else {
+            buffer.anchor_before(self)
         }
     }
 
     pub fn bias_right(&self, buffer: &Buffer) -> Anchor {
-        match self {
-            Anchor::End
-            | Anchor::Middle {
-                bias: Bias::Right, ..
-            } => self.clone(),
-            _ => buffer.anchor_after(self),
+        if self.bias == Bias::Right {
+            self.clone()
+        } else {
+            buffer.anchor_after(self)
         }
     }
 }

zed/src/editor/display_map/fold_map.rs 🔗

@@ -313,7 +313,7 @@ impl FoldMap {
 
             let anchor = buffer.anchor_before(edit.new_range.start);
             let mut folds_cursor = self.folds.cursor::<_, ()>();
-            folds_cursor.seek(&Fold(anchor..Anchor::End), Bias::Left, buffer);
+            folds_cursor.seek(&Fold(anchor..Anchor::max()), Bias::Left, buffer);
             let mut folds = iter::from_fn(move || {
                 let item = folds_cursor
                     .item()
@@ -597,7 +597,7 @@ struct Fold(Range<Anchor>);
 
 impl Default for Fold {
     fn default() -> Self {
-        Self(Anchor::Start..Anchor::End)
+        Self(Anchor::min()..Anchor::max())
     }
 }
 
@@ -627,10 +627,10 @@ struct FoldSummary {
 impl Default for FoldSummary {
     fn default() -> Self {
         Self {
-            start: Anchor::Start,
-            end: Anchor::End,
-            min_start: Anchor::End,
-            max_end: Anchor::Start,
+            start: Anchor::min(),
+            end: Anchor::max(),
+            min_start: Anchor::max(),
+            max_end: Anchor::min(),
             count: 0,
         }
     }