Don't use an enum for anchors and model min/max more implicitly

Antonio Scandurra created

This will make it easier to serialize an anchor.

Change summary

crates/clock/src/clock.rs |   9 ++
crates/text/src/anchor.rs |  98 +++++++++++------------------
crates/text/src/text.rs   | 134 +++++++++++++++++++---------------------
3 files changed, 108 insertions(+), 133 deletions(-)

Detailed changes

crates/clock/src/clock.rs 🔗

@@ -21,6 +21,15 @@ pub struct Lamport {
 }
 
 impl Local {
+    pub const MIN: Self = Self {
+        replica_id: ReplicaId::MIN,
+        value: Seq::MIN,
+    };
+    pub const MAX: Self = Self {
+        replica_id: ReplicaId::MAX,
+        value: Seq::MAX,
+    };
+
     pub fn new(replica_id: ReplicaId) -> Self {
         Self {
             replica_id,

crates/text/src/anchor.rs 🔗

@@ -1,88 +1,57 @@
-use crate::{rope::TextDimension, Snapshot};
-
-use super::{Buffer, ToOffset};
+use super::{rope::TextDimension, Buffer, Point, Snapshot, ToOffset};
 use anyhow::Result;
 use std::{cmp::Ordering, fmt::Debug, ops::Range};
 use sum_tree::Bias;
 
 #[derive(Clone, Eq, PartialEq, Debug, Hash)]
-pub enum Anchor {
-    Min,
-    Insertion {
-        timestamp: clock::Local,
-        offset: usize,
-        bias: Bias,
-    },
-    Max,
+pub struct Anchor {
+    pub timestamp: clock::Local,
+    pub offset: usize,
+    pub bias: Bias,
 }
 
 impl Anchor {
     pub fn min() -> Self {
-        Self::Min
+        Self {
+            timestamp: clock::Local::MIN,
+            offset: usize::MIN,
+            bias: Bias::Left,
+        }
     }
 
     pub fn max() -> Self {
-        Self::Max
+        Self {
+            timestamp: clock::Local::MAX,
+            offset: usize::MAX,
+            bias: Bias::Right,
+        }
     }
 
     pub fn cmp<'a>(&self, other: &Anchor, buffer: &Snapshot) -> Result<Ordering> {
-        match (self, other) {
-            (Self::Min, Self::Min) => Ok(Ordering::Equal),
-            (Self::Min, _) => Ok(Ordering::Less),
-            (_, Self::Min) => Ok(Ordering::Greater),
-            (Self::Max, Self::Max) => Ok(Ordering::Equal),
-            (Self::Max, _) => Ok(Ordering::Greater),
-            (_, Self::Max) => Ok(Ordering::Less),
-            (
-                Self::Insertion {
-                    timestamp: lhs_id,
-                    bias: lhs_bias,
-                    offset: lhs_offset,
-                },
-                Self::Insertion {
-                    timestamp: rhs_id,
-                    bias: rhs_bias,
-                    offset: rhs_offset,
-                },
-            ) => {
-                let offset_comparison = if lhs_id == rhs_id {
-                    lhs_offset.cmp(&rhs_offset)
-                } else {
-                    buffer
-                        .full_offset_for_anchor(self)
-                        .cmp(&buffer.full_offset_for_anchor(other))
-                };
+        let offset_comparison = if self.timestamp == other.timestamp {
+            self.offset.cmp(&other.offset)
+        } else {
+            buffer
+                .full_offset_for_anchor(self)
+                .cmp(&buffer.full_offset_for_anchor(other))
+        };
 
-                Ok(offset_comparison.then_with(|| lhs_bias.cmp(&rhs_bias)))
-            }
-        }
+        Ok(offset_comparison.then_with(|| self.bias.cmp(&other.bias)))
     }
 
     pub fn bias_left(&self, buffer: &Buffer) -> Anchor {
-        match self {
-            Anchor::Min => Anchor::Min,
-            Anchor::Insertion { bias, .. } => {
-                if *bias == Bias::Left {
-                    self.clone()
-                } else {
-                    buffer.anchor_before(self)
-                }
-            }
-            Anchor::Max => 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::Min => buffer.anchor_after(self),
-            Anchor::Insertion { bias, .. } => {
-                if *bias == Bias::Right {
-                    self.clone()
-                } else {
-                    buffer.anchor_after(self)
-                }
-            }
-            Anchor::Max => Anchor::Max,
+        if self.bias == Bias::Right {
+            self.clone()
+        } else {
+            buffer.anchor_after(self)
         }
     }
 
@@ -97,6 +66,7 @@ impl Anchor {
 pub trait AnchorRangeExt {
     fn cmp(&self, b: &Range<Anchor>, buffer: &Snapshot) -> Result<Ordering>;
     fn to_offset(&self, content: &Snapshot) -> Range<usize>;
+    fn to_point(&self, content: &Snapshot) -> Range<Point>;
 }
 
 impl AnchorRangeExt for Range<Anchor> {
@@ -110,4 +80,8 @@ impl AnchorRangeExt for Range<Anchor> {
     fn to_offset(&self, content: &Snapshot) -> Range<usize> {
         self.start.to_offset(&content)..self.end.to_offset(&content)
     }
+
+    fn to_point(&self, content: &Snapshot) -> Range<Point> {
+        self.start.summary::<Point>(&content)..self.end.summary::<Point>(&content)
+    }
 }

crates/text/src/text.rs 🔗

@@ -1136,11 +1136,9 @@ impl Buffer {
     }
 
     fn can_resolve(&self, anchor: &Anchor) -> bool {
-        match anchor {
-            Anchor::Min => true,
-            Anchor::Insertion { timestamp, .. } => self.version.observed(*timestamp),
-            Anchor::Max => true,
-        }
+        *anchor == Anchor::min()
+            || *anchor == Anchor::max()
+            || self.version.observed(anchor.timestamp)
     }
 
     pub fn peek_undo_stack(&self) -> Option<&Transaction> {
@@ -1680,80 +1678,74 @@ impl Snapshot {
     where
         D: TextDimension<'a>,
     {
-        match anchor {
-            Anchor::Min => D::default(),
-            Anchor::Insertion {
-                timestamp,
-                offset,
-                bias,
-            } => {
-                let anchor_key = InsertionFragmentKey {
-                    timestamp: *timestamp,
-                    split_offset: *offset,
-                };
-                let mut insertion_cursor = self.insertions.cursor::<InsertionFragmentKey>();
-                insertion_cursor.seek(&anchor_key, *bias, &());
-                if let Some(insertion) = insertion_cursor.item() {
-                    let comparison = sum_tree::KeyedItem::key(insertion).cmp(&anchor_key);
-                    if comparison == Ordering::Greater
-                        || (*bias == Bias::Left && comparison == Ordering::Equal && *offset > 0)
-                    {
-                        insertion_cursor.prev(&());
-                    }
-                } else {
+        if *anchor == Anchor::min() {
+            D::default()
+        } else if *anchor == Anchor::max() {
+            D::from_text_summary(&self.visible_text.summary())
+        } else {
+            let anchor_key = InsertionFragmentKey {
+                timestamp: anchor.timestamp,
+                split_offset: anchor.offset,
+            };
+            let mut insertion_cursor = self.insertions.cursor::<InsertionFragmentKey>();
+            insertion_cursor.seek(&anchor_key, anchor.bias, &());
+            if let Some(insertion) = insertion_cursor.item() {
+                let comparison = sum_tree::KeyedItem::key(insertion).cmp(&anchor_key);
+                if comparison == Ordering::Greater
+                    || (anchor.bias == Bias::Left
+                        && comparison == Ordering::Equal
+                        && anchor.offset > 0)
+                {
                     insertion_cursor.prev(&());
                 }
-                let insertion = insertion_cursor.item().expect("invalid insertion");
-                debug_assert_eq!(insertion.timestamp, *timestamp, "invalid insertion");
+            } else {
+                insertion_cursor.prev(&());
+            }
+            let insertion = insertion_cursor.item().expect("invalid insertion");
+            debug_assert_eq!(insertion.timestamp, anchor.timestamp, "invalid insertion");
 
-                let mut fragment_cursor = self.fragments.cursor::<(Locator, usize)>();
-                fragment_cursor.seek(&insertion.fragment_id, Bias::Left, &None);
-                let fragment = fragment_cursor.item().unwrap();
-                let mut fragment_offset = fragment_cursor.start().1;
-                if fragment.visible {
-                    fragment_offset += *offset - insertion.split_offset;
-                }
-                self.text_summary_for_range(0..fragment_offset)
+            let mut fragment_cursor = self.fragments.cursor::<(Locator, usize)>();
+            fragment_cursor.seek(&insertion.fragment_id, Bias::Left, &None);
+            let fragment = fragment_cursor.item().unwrap();
+            let mut fragment_offset = fragment_cursor.start().1;
+            if fragment.visible {
+                fragment_offset += anchor.offset - insertion.split_offset;
             }
-            Anchor::Max => D::from_text_summary(&self.visible_text.summary()),
+            self.text_summary_for_range(0..fragment_offset)
         }
     }
 
     fn full_offset_for_anchor(&self, anchor: &Anchor) -> FullOffset {
-        match anchor {
-            Anchor::Min => Default::default(),
-            Anchor::Insertion {
-                timestamp,
-                offset,
-                bias,
-            } => {
-                let anchor_key = InsertionFragmentKey {
-                    timestamp: *timestamp,
-                    split_offset: *offset,
-                };
-                let mut insertion_cursor = self.insertions.cursor::<InsertionFragmentKey>();
-                insertion_cursor.seek(&anchor_key, *bias, &());
-                if let Some(insertion) = insertion_cursor.item() {
-                    let comparison = sum_tree::KeyedItem::key(insertion).cmp(&anchor_key);
-                    if comparison == Ordering::Greater
-                        || (*bias == Bias::Left && comparison == Ordering::Equal && *offset > 0)
-                    {
-                        insertion_cursor.prev(&());
-                    }
-                } else {
+        if *anchor == Anchor::min() {
+            Default::default()
+        } else if *anchor == Anchor::max() {
+            let text = self.fragments.summary().text;
+            FullOffset(text.visible + text.deleted)
+        } else {
+            let anchor_key = InsertionFragmentKey {
+                timestamp: anchor.timestamp,
+                split_offset: anchor.offset,
+            };
+            let mut insertion_cursor = self.insertions.cursor::<InsertionFragmentKey>();
+            insertion_cursor.seek(&anchor_key, anchor.bias, &());
+            if let Some(insertion) = insertion_cursor.item() {
+                let comparison = sum_tree::KeyedItem::key(insertion).cmp(&anchor_key);
+                if comparison == Ordering::Greater
+                    || (anchor.bias == Bias::Left
+                        && comparison == Ordering::Equal
+                        && anchor.offset > 0)
+                {
                     insertion_cursor.prev(&());
                 }
-                let insertion = insertion_cursor.item().expect("invalid insertion");
-                debug_assert_eq!(insertion.timestamp, *timestamp, "invalid insertion");
-
-                let mut fragment_cursor = self.fragments.cursor::<(Locator, FullOffset)>();
-                fragment_cursor.seek(&insertion.fragment_id, Bias::Left, &None);
-                fragment_cursor.start().1 + (*offset - insertion.split_offset)
-            }
-            Anchor::Max => {
-                let text = self.fragments.summary().text;
-                FullOffset(text.visible + text.deleted)
+            } else {
+                insertion_cursor.prev(&());
             }
+            let insertion = insertion_cursor.item().expect("invalid insertion");
+            debug_assert_eq!(insertion.timestamp, anchor.timestamp, "invalid insertion");
+
+            let mut fragment_cursor = self.fragments.cursor::<(Locator, FullOffset)>();
+            fragment_cursor.seek(&insertion.fragment_id, Bias::Left, &None);
+            fragment_cursor.start().1 + (anchor.offset - insertion.split_offset)
         }
     }
 
@@ -1777,15 +1769,15 @@ impl Snapshot {
     pub fn anchor_at<T: ToOffset>(&self, position: T, bias: Bias) -> Anchor {
         let offset = position.to_offset(self);
         if bias == Bias::Left && offset == 0 {
-            Anchor::Min
+            Anchor::min()
         } else if bias == Bias::Right && offset == self.len() {
-            Anchor::Max
+            Anchor::max()
         } else {
             let mut fragment_cursor = self.fragments.cursor::<(usize, Locator)>();
             fragment_cursor.seek(&offset, bias, &None);
             let fragment = fragment_cursor.item().unwrap();
             let overshoot = offset - fragment_cursor.start().0;
-            Anchor::Insertion {
+            Anchor {
                 timestamp: fragment.insertion_timestamp.local(),
                 offset: fragment.insertion_offset + overshoot,
                 bias,