diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 3ff5ffaad4..5b4349dedb 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -739,8 +739,8 @@ async fn test_navigation_history(cx: &mut TestAppContext) { // Ensure we don't panic when navigation data contains invalid anchors *and* points. let mut invalid_anchor = editor.scroll_manager.anchor().anchor; - invalid_anchor.text_anchor = text::Anchor::Character { - + invalid_anchor.text_anchor = text::Anchor::Start { + buffer_id: BufferId::new(999).unwrap(), }; let invalid_point = Point::new(9999, 0); editor.navigate( diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index c5d48f8136..0dd1be7e15 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -2646,7 +2646,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) { for buffer in &buffers { let buffer = buffer.read(cx).snapshot(); let actual_remote_selections = buffer - .selections_in_range(Anchor::Start..Anchor::End, false) + .selections_in_range(buffer.min_anchor()..buffer.max_anchor(), false) .map(|(replica_id, _, _, selections)| (replica_id, selections.collect::>())) .collect::>(); let expected_remote_selections = active_selections diff --git a/crates/text/src/anchor.rs b/crates/text/src/anchor.rs index cda826bede..8889cd0ee7 100644 --- a/crates/text/src/anchor.rs +++ b/crates/text/src/anchor.rs @@ -6,11 +6,14 @@ use std::{cmp::Ordering, fmt::Debug, ops::Range}; use sum_tree::Bias; /// A timestamped position in a buffer -#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, Default)] +#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] pub enum Anchor { - #[default] - Start, - End, + Start { + buffer_id: BufferId, + }, + End { + buffer_id: BufferId, + }, Character { buffer_id: BufferId, insertion_id: clock::Lamport, @@ -20,41 +23,49 @@ pub enum Anchor { } impl Anchor { + pub fn buffer_id(&self) -> BufferId { + match self { + Anchor::Start { buffer_id } => *buffer_id, + Anchor::End { buffer_id } => *buffer_id, + Anchor::Character { buffer_id, .. } => *buffer_id, + } + } + pub fn cmp(&self, other: &Anchor, buffer: &BufferSnapshot) -> Ordering { + debug_assert_eq!( + self.buffer_id(), + other.buffer_id(), + "anchors belong to different buffers" + ); + match (self, other) { - (Anchor::Start, Anchor::Start) | (Anchor::End, Anchor::End) => Ordering::Equal, - (Anchor::Start, _) | (_, Anchor::End) => Ordering::Less, - (_, Anchor::Start) | (Anchor::End, _) => Ordering::Greater, + (Anchor::Start { .. }, Anchor::Start { .. }) => Ordering::Equal, + (Anchor::End { .. }, Anchor::End { .. }) => Ordering::Equal, + (Anchor::Start { .. }, _) | (_, Anchor::End { .. }) => Ordering::Less, + (_, Anchor::Start { .. }) | (Anchor::End { .. }, _) => Ordering::Greater, ( Anchor::Character { - buffer_id, insertion_id, offset, bias, + .. }, Anchor::Character { - buffer_id: other_buffer_id, insertion_id: other_insertion_id, offset: other_offset, bias: other_bias, + .. }, ) => { - debug_assert_eq!( - buffer_id, other_buffer_id, - "anchors belong to different buffers" - ); - - let fragment_id_comparison = if insertion_id == other_insertion_id { - Ordering::Equal + if insertion_id == other_insertion_id { + offset + .cmp(&other_offset) + .then_with(|| bias.cmp(&other_bias)) } else { buffer .fragment_id_for_anchor(self) .cmp(buffer.fragment_id_for_anchor(other)) - }; - - fragment_id_comparison - .then_with(|| offset.cmp(&other_offset)) - .then_with(|| bias.cmp(&other_bias)) + } } } } @@ -85,8 +96,10 @@ impl Anchor { pub fn bias_left(&self, buffer: &BufferSnapshot) -> Anchor { match self { - Anchor::Start => Anchor::Start, - Anchor::End => buffer.anchor_before(buffer.len()), + Anchor::Start { buffer_id } => Anchor::Start { + buffer_id: *buffer_id, + }, + Anchor::End { .. } => buffer.anchor_before(buffer.len()), Anchor::Character { buffer_id, insertion_id, @@ -103,8 +116,10 @@ impl Anchor { pub fn bias_right(&self, buffer: &BufferSnapshot) -> Anchor { match self { - Anchor::Start => buffer.anchor_after(0), - Anchor::End => Anchor::End, + Anchor::Start { .. } => buffer.anchor_after(0), + Anchor::End { buffer_id } => Anchor::End { + buffer_id: *buffer_id, + }, Anchor::Character { buffer_id, insertion_id, @@ -129,7 +144,9 @@ impl Anchor { /// Returns true when the [`Anchor`] is located inside a visible fragment. pub fn is_valid(&self, buffer: &BufferSnapshot) -> bool { match self { - Anchor::Start | Anchor::End => true, + Anchor::Start { buffer_id } | Anchor::End { buffer_id } => { + *buffer_id == buffer.remote_id + } Anchor::Character { buffer_id, .. } => { if *buffer_id == buffer.remote_id { let fragment_id = buffer.fragment_id_for_anchor(self); diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index e4dbd83003..a19739a62c 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1914,6 +1914,18 @@ impl BufferSnapshot { self.visible_text.summary() } + pub fn min_anchor(&self) -> Anchor { + Anchor::Start { + buffer_id: self.remote_id, + } + } + + pub fn max_anchor(&self) -> Anchor { + Anchor::End { + buffer_id: self.remote_id, + } + } + pub fn max_point(&self) -> Point { self.visible_text.max_point() } @@ -2114,42 +2126,50 @@ impl BufferSnapshot { let mut text_cursor = self.visible_text.cursor(0); let mut position = D::default(); - anchors.map(move |(anchor, payload)| match *anchor { - Anchor::Start => (D::default(), payload), - Anchor::End => (D::from_text_summary(&self.visible_text.summary()), payload), - Anchor::Character { - insertion_id, - offset, - bias, - .. - } => { - let anchor_key = InsertionFragmentKey { - timestamp: insertion_id, - split_offset: offset, - }; - 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) - { + anchors.map(move |(anchor, payload)| { + debug_assert_eq!( + anchor.buffer_id(), + self.remote_id, + "anchor belongs to a different buffer" + ); + + match *anchor { + Anchor::Start { .. } => (D::default(), payload), + Anchor::End { .. } => (D::from_text_summary(&self.visible_text.summary()), payload), + Anchor::Character { + insertion_id, + offset, + bias, + .. + } => { + let anchor_key = InsertionFragmentKey { + timestamp: insertion_id, + split_offset: offset, + }; + 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 { insertion_cursor.prev(&()); } - } else { - insertion_cursor.prev(&()); - } - let insertion = insertion_cursor.item().expect("invalid insertion"); - assert_eq!(insertion.timestamp, insertion_id, "invalid insertion"); + let insertion = insertion_cursor.item().expect("invalid insertion"); + assert_eq!(insertion.timestamp, insertion_id, "invalid insertion"); - fragment_cursor.seek_forward(&Some(&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; - } + fragment_cursor.seek_forward(&Some(&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; + } - position.add_assign(&text_cursor.summary(fragment_offset)); - (position.clone(), payload) + position.add_assign(&text_cursor.summary(fragment_offset)); + (position.clone(), payload) + } } }) } @@ -2162,9 +2182,15 @@ impl BufferSnapshot { } fn fragment_id_for_anchor(&self, anchor: &Anchor) -> &Locator { + debug_assert_eq!( + anchor.buffer_id(), + self.remote_id, + "anchor belongs to a different buffer" + ); + match *anchor { - Anchor::Start => Locator::min_ref(), - Anchor::End => Locator::max_ref(), + Anchor::Start { .. } => Locator::min_ref(), + Anchor::End { .. } => Locator::max_ref(), Anchor::Character { insertion_id, offset, @@ -2220,9 +2246,9 @@ impl BufferSnapshot { fn anchor_at_offset(&self, offset: usize, bias: Bias) -> Anchor { if bias == Bias::Left && offset == 0 { - Anchor::Start + self.min_anchor() } else if bias == Bias::Right && offset == self.len() { - Anchor::End + self.max_anchor() } else { let mut fragment_cursor = self.fragments.cursor::(); fragment_cursor.seek(&offset, bias, &None); @@ -2239,8 +2265,8 @@ impl BufferSnapshot { pub fn can_resolve(&self, anchor: &Anchor) -> bool { match *anchor { - Anchor::Start => true, - Anchor::End => true, + Anchor::Start { buffer_id } => self.remote_id == buffer_id, + Anchor::End { buffer_id } => self.remote_id == buffer_id, Anchor::Character { buffer_id, insertion_id, @@ -2272,7 +2298,7 @@ impl BufferSnapshot { where D: TextDimension + Ord, { - self.edits_since_in_range(since, Anchor::Start..Anchor::End) + self.edits_since_in_range(since, self.min_anchor()..self.max_anchor()) } pub fn anchored_edits_since<'a, D>( @@ -2282,7 +2308,7 @@ impl BufferSnapshot { where D: TextDimension + Ord, { - self.anchored_edits_since_in_range(since, Anchor::Start..Anchor::End) + self.anchored_edits_since_in_range(since, self.min_anchor()..self.max_anchor()) } pub fn edits_since_in_range<'a, D>(