anchor.rs

  1use crate::{
  2    ExcerptSummary, MultiBufferDimension, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
  3    PathKeyIndex, find_diff_state,
  4};
  5
  6use super::{MultiBufferSnapshot, ToOffset, ToPoint};
  7use language::{BufferSnapshot, Point};
  8use std::{
  9    cmp::Ordering,
 10    ops::{Add, AddAssign, Range, Sub},
 11};
 12use sum_tree::Bias;
 13use text::BufferId;
 14
 15/// A multibuffer anchor derived from an anchor into a specific excerpted buffer.
 16#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
 17pub struct ExcerptAnchor {
 18    pub(crate) text_anchor: text::Anchor,
 19    pub(crate) path: PathKeyIndex,
 20    pub(crate) diff_base_anchor: Option<text::Anchor>,
 21}
 22
 23/// A stable reference to a position within a [`MultiBuffer`](super::MultiBuffer).
 24///
 25/// Unlike simple offsets, anchors remain valid as the text is edited, automatically
 26/// adjusting to reflect insertions and deletions around them.
 27#[derive(Clone, Copy, Eq, PartialEq, Hash)]
 28pub enum Anchor {
 29    /// An anchor that always resolves to the start of the multibuffer.
 30    Min,
 31    /// An anchor that's attached to a specific excerpted buffer.
 32    Excerpt(ExcerptAnchor),
 33    /// An anchor that always resolves to the end of the multibuffer.
 34    Max,
 35}
 36
 37pub(crate) enum AnchorSeekTarget {
 38    Excerpt {
 39        path_key: PathKey,
 40        anchor: ExcerptAnchor,
 41        // None when the buffer no longer exists in the multibuffer
 42        snapshot: Option<BufferSnapshot>,
 43    },
 44    Empty,
 45}
 46
 47impl std::fmt::Debug for AnchorSeekTarget {
 48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 49        match self {
 50            Self::Excerpt {
 51                path_key,
 52                anchor,
 53                snapshot: _,
 54            } => f
 55                .debug_struct("Excerpt")
 56                .field("path_key", path_key)
 57                .field("anchor", anchor)
 58                .finish(),
 59            Self::Empty => write!(f, "Empty"),
 60        }
 61    }
 62}
 63
 64impl std::fmt::Debug for Anchor {
 65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 66        match self {
 67            Anchor::Min => write!(f, "Anchor::Min"),
 68            Anchor::Max => write!(f, "Anchor::Max"),
 69            Anchor::Excerpt(excerpt_anchor) => write!(f, "{excerpt_anchor:?}"),
 70        }
 71    }
 72}
 73
 74impl From<ExcerptAnchor> for Anchor {
 75    fn from(anchor: ExcerptAnchor) -> Self {
 76        Anchor::Excerpt(anchor)
 77    }
 78}
 79
 80impl ExcerptAnchor {
 81    pub(crate) fn buffer_id(&self) -> BufferId {
 82        self.text_anchor.buffer_id
 83    }
 84
 85    pub(crate) fn text_anchor(&self) -> text::Anchor {
 86        self.text_anchor
 87    }
 88
 89    pub(crate) fn with_diff_base_anchor(mut self, diff_base_anchor: text::Anchor) -> Self {
 90        self.diff_base_anchor = Some(diff_base_anchor);
 91        self
 92    }
 93
 94    pub(crate) fn cmp(&self, other: &Self, snapshot: &MultiBufferSnapshot) -> Ordering {
 95        let Some(self_path_key) = snapshot.path_keys_by_index.get(&self.path) else {
 96            panic!("anchor's path was never added to multibuffer")
 97        };
 98        let Some(other_path_key) = snapshot.path_keys_by_index.get(&other.path) else {
 99            panic!("anchor's path was never added to multibuffer")
100        };
101
102        if self_path_key.cmp(other_path_key) != Ordering::Equal {
103            return self_path_key.cmp(other_path_key);
104        }
105
106        // in the case that you removed the buffer containing self,
107        // and added the buffer containing other with the same path key
108        // (ordering is arbitrary but consistent)
109        if self.text_anchor.buffer_id != other.text_anchor.buffer_id {
110            return self.text_anchor.buffer_id.cmp(&other.text_anchor.buffer_id);
111        }
112
113        let Some(buffer) = snapshot.buffer_for_path(&self_path_key) else {
114            return Ordering::Equal;
115        };
116        // Comparing two anchors into buffer A that formerly existed at path P,
117        // when path P has since been reused for a different buffer B
118        if buffer.remote_id() != self.text_anchor.buffer_id {
119            return Ordering::Equal;
120        };
121        assert_eq!(self.text_anchor.buffer_id, buffer.remote_id());
122        let text_cmp = self.text_anchor().cmp(&other.text_anchor(), buffer);
123        if text_cmp != Ordering::Equal {
124            return text_cmp;
125        }
126
127        if (self.diff_base_anchor.is_some() || other.diff_base_anchor.is_some())
128            && let Some(base_text) = find_diff_state(&snapshot.diffs, self.text_anchor.buffer_id)
129                .map(|diff| diff.base_text())
130        {
131            let self_anchor = self.diff_base_anchor.filter(|a| a.is_valid(base_text));
132            let other_anchor = other.diff_base_anchor.filter(|a| a.is_valid(base_text));
133            return match (self_anchor, other_anchor) {
134                (Some(a), Some(b)) => a.cmp(&b, base_text),
135                (Some(_), None) => match other.text_anchor().bias {
136                    Bias::Left => Ordering::Greater,
137                    Bias::Right => Ordering::Less,
138                },
139                (None, Some(_)) => match self.text_anchor().bias {
140                    Bias::Left => Ordering::Less,
141                    Bias::Right => Ordering::Greater,
142                },
143                (None, None) => Ordering::Equal,
144            };
145        }
146
147        Ordering::Equal
148    }
149
150    fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Self {
151        if self.text_anchor.bias == Bias::Left {
152            return *self;
153        }
154        let Some(buffer) = snapshot.buffer_for_id(self.text_anchor.buffer_id) else {
155            return *self;
156        };
157        let text_anchor = self.text_anchor().bias_left(&buffer);
158        let ret = Self::in_buffer(self.path, text_anchor);
159        if let Some(diff_base_anchor) = self.diff_base_anchor {
160            if let Some(diff) = find_diff_state(&snapshot.diffs, self.text_anchor.buffer_id)
161                && diff_base_anchor.is_valid(&diff.base_text())
162            {
163                ret.with_diff_base_anchor(diff_base_anchor.bias_left(diff.base_text()))
164            } else {
165                ret.with_diff_base_anchor(diff_base_anchor)
166            }
167        } else {
168            ret
169        }
170    }
171
172    fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Self {
173        if self.text_anchor.bias == Bias::Right {
174            return *self;
175        }
176        let Some(buffer) = snapshot.buffer_for_id(self.text_anchor.buffer_id) else {
177            return *self;
178        };
179        let text_anchor = self.text_anchor().bias_right(&buffer);
180        let ret = Self::in_buffer(self.path, text_anchor);
181        if let Some(diff_base_anchor) = self.diff_base_anchor {
182            if let Some(diff) = find_diff_state(&snapshot.diffs, self.text_anchor.buffer_id)
183                && diff_base_anchor.is_valid(&diff.base_text())
184            {
185                ret.with_diff_base_anchor(diff_base_anchor.bias_right(diff.base_text()))
186            } else {
187                ret.with_diff_base_anchor(diff_base_anchor)
188            }
189        } else {
190            ret
191        }
192    }
193
194    #[track_caller]
195    pub(crate) fn in_buffer(path: PathKeyIndex, text_anchor: text::Anchor) -> Self {
196        ExcerptAnchor {
197            path,
198            diff_base_anchor: None,
199            text_anchor,
200        }
201    }
202
203    fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool {
204        let Some(target) = self.try_seek_target(snapshot) else {
205            return false;
206        };
207        let Some(buffer_snapshot) = snapshot.buffer_for_id(self.buffer_id()) else {
208            return false;
209        };
210        // Early check to avoid invalid comparisons when seeking
211        if !buffer_snapshot.can_resolve(&self.text_anchor) {
212            return false;
213        }
214        let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(());
215        cursor.seek(&target, Bias::Left);
216        let Some(excerpt) = cursor.item() else {
217            return false;
218        };
219        let is_valid = self.text_anchor == excerpt.range.context.start
220            || self.text_anchor == excerpt.range.context.end
221            || self.text_anchor.is_valid(&buffer_snapshot);
222        is_valid
223            && excerpt
224                .range
225                .context
226                .start
227                .cmp(&self.text_anchor(), buffer_snapshot)
228                .is_le()
229            && excerpt
230                .range
231                .context
232                .end
233                .cmp(&self.text_anchor(), buffer_snapshot)
234                .is_ge()
235    }
236
237    pub(crate) fn seek_target(&self, snapshot: &MultiBufferSnapshot) -> AnchorSeekTarget {
238        self.try_seek_target(snapshot)
239            .expect("anchor is from different multi-buffer")
240    }
241
242    pub(crate) fn try_seek_target(
243        &self,
244        snapshot: &MultiBufferSnapshot,
245    ) -> Option<AnchorSeekTarget> {
246        let path_key = snapshot.try_path_for_anchor(*self)?;
247        let buffer = snapshot.buffer_for_path(&path_key).cloned();
248        Some(AnchorSeekTarget::Excerpt {
249            path_key,
250            anchor: *self,
251            snapshot: buffer,
252        })
253    }
254}
255
256impl ToOffset for ExcerptAnchor {
257    fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffset {
258        Anchor::from(*self).to_offset(snapshot)
259    }
260
261    fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffsetUtf16 {
262        Anchor::from(*self).to_offset_utf16(snapshot)
263    }
264}
265
266impl ToPoint for ExcerptAnchor {
267    fn to_point(&self, snapshot: &MultiBufferSnapshot) -> Point {
268        Anchor::from(*self).to_point(snapshot)
269    }
270
271    fn to_point_utf16(&self, snapshot: &MultiBufferSnapshot) -> rope::PointUtf16 {
272        Anchor::from(*self).to_point_utf16(snapshot)
273    }
274}
275
276impl Anchor {
277    pub fn is_min(&self) -> bool {
278        matches!(self, Self::Min)
279    }
280
281    pub fn is_max(&self) -> bool {
282        matches!(self, Self::Max)
283    }
284
285    pub(crate) fn in_buffer(path: PathKeyIndex, text_anchor: text::Anchor) -> Self {
286        Self::Excerpt(ExcerptAnchor::in_buffer(path, text_anchor))
287    }
288
289    pub(crate) fn range_in_buffer(path: PathKeyIndex, range: Range<text::Anchor>) -> Range<Self> {
290        Self::in_buffer(path, range.start)..Self::in_buffer(path, range.end)
291    }
292
293    pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering {
294        match (self, other) {
295            (Anchor::Min, Anchor::Min) => return Ordering::Equal,
296            (Anchor::Max, Anchor::Max) => return Ordering::Equal,
297            (Anchor::Min, _) => return Ordering::Less,
298            (Anchor::Max, _) => return Ordering::Greater,
299            (_, Anchor::Max) => return Ordering::Less,
300            (_, Anchor::Min) => return Ordering::Greater,
301            (Anchor::Excerpt(self_excerpt_anchor), Anchor::Excerpt(other_excerpt_anchor)) => {
302                self_excerpt_anchor.cmp(other_excerpt_anchor, snapshot)
303            }
304        }
305    }
306
307    pub fn bias(&self) -> Bias {
308        match self {
309            Anchor::Min => Bias::Left,
310            Anchor::Max => Bias::Right,
311            Anchor::Excerpt(anchor) => anchor.text_anchor.bias,
312        }
313    }
314
315    pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
316        match self {
317            Anchor::Min => *self,
318            Anchor::Max => snapshot.anchor_before(snapshot.max_point()),
319            Anchor::Excerpt(anchor) => Anchor::Excerpt(anchor.bias_left(snapshot)),
320        }
321    }
322
323    pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
324        match self {
325            Anchor::Max => *self,
326            Anchor::Min => snapshot.anchor_after(Point::zero()),
327            Anchor::Excerpt(anchor) => Anchor::Excerpt(anchor.bias_right(snapshot)),
328        }
329    }
330
331    pub fn summary<D>(&self, snapshot: &MultiBufferSnapshot) -> D
332    where
333        D: MultiBufferDimension
334            + Ord
335            + Sub<Output = D::TextDimension>
336            + Sub<D::TextDimension, Output = D>
337            + AddAssign<D::TextDimension>
338            + Add<D::TextDimension, Output = D>,
339        D::TextDimension: Sub<Output = D::TextDimension> + Ord,
340    {
341        snapshot.summary_for_anchor(self)
342    }
343
344    pub fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool {
345        match self {
346            Anchor::Min | Anchor::Max => true,
347            Anchor::Excerpt(excerpt_anchor) => excerpt_anchor.is_valid(snapshot),
348        }
349    }
350
351    fn to_excerpt_anchor(&self, snapshot: &MultiBufferSnapshot) -> Option<ExcerptAnchor> {
352        match self {
353            Anchor::Min => {
354                let excerpt = snapshot.excerpts.first()?;
355
356                Some(ExcerptAnchor {
357                    text_anchor: excerpt.range.context.start,
358                    path: excerpt.path_key_index,
359                    diff_base_anchor: None,
360                })
361            }
362            Anchor::Excerpt(excerpt_anchor) => Some(*excerpt_anchor),
363            Anchor::Max => {
364                let excerpt = snapshot.excerpts.last()?;
365
366                Some(ExcerptAnchor {
367                    text_anchor: excerpt.range.context.end,
368                    path: excerpt.path_key_index,
369                    diff_base_anchor: None,
370                })
371            }
372        }
373    }
374
375    pub(crate) fn seek_target(&self, snapshot: &MultiBufferSnapshot) -> AnchorSeekTarget {
376        let Some(excerpt_anchor) = self.to_excerpt_anchor(snapshot) else {
377            return AnchorSeekTarget::Empty;
378        };
379
380        excerpt_anchor.seek_target(snapshot)
381    }
382
383    pub(crate) fn excerpt_anchor(&self) -> Option<ExcerptAnchor> {
384        match self {
385            Anchor::Min | Anchor::Max => None,
386            Anchor::Excerpt(excerpt_anchor) => Some(*excerpt_anchor),
387        }
388    }
389
390    pub(crate) fn text_anchor(&self) -> Option<text::Anchor> {
391        match self {
392            Anchor::Min | Anchor::Max => None,
393            Anchor::Excerpt(excerpt_anchor) => Some(excerpt_anchor.text_anchor()),
394        }
395    }
396
397    pub fn opaque_id(&self) -> Option<[u8; 20]> {
398        self.text_anchor().map(|a| a.opaque_id())
399    }
400
401    /// Note: anchor_to_buffer_anchor is probably what you want
402    pub fn raw_text_anchor(&self) -> Option<text::Anchor> {
403        match self {
404            Anchor::Min | Anchor::Max => None,
405            Anchor::Excerpt(excerpt_anchor) => Some(excerpt_anchor.text_anchor),
406        }
407    }
408
409    pub(crate) fn try_seek_target(
410        &self,
411        snapshot: &MultiBufferSnapshot,
412    ) -> Option<AnchorSeekTarget> {
413        let Some(excerpt_anchor) = self.to_excerpt_anchor(snapshot) else {
414            return Some(AnchorSeekTarget::Empty);
415        };
416        excerpt_anchor.try_seek_target(snapshot)
417    }
418
419    /// Returns the text anchor for this anchor.
420    /// Panics if the anchor is from a different buffer.
421    pub fn text_anchor_in(&self, buffer: &BufferSnapshot) -> text::Anchor {
422        match self {
423            Anchor::Min => text::Anchor::min_for_buffer(buffer.remote_id()),
424            Anchor::Excerpt(excerpt_anchor) => {
425                let text_anchor = excerpt_anchor.text_anchor;
426                assert_eq!(text_anchor.buffer_id, buffer.remote_id());
427                text_anchor
428            }
429            Anchor::Max => text::Anchor::max_for_buffer(buffer.remote_id()),
430        }
431    }
432
433    pub fn diff_base_anchor(&self) -> Option<text::Anchor> {
434        self.excerpt_anchor()?.diff_base_anchor
435    }
436
437    #[cfg(any(test, feature = "test-support"))]
438    pub fn expect_text_anchor(&self) -> text::Anchor {
439        self.excerpt_anchor().unwrap().text_anchor
440    }
441
442    pub fn with_diff_base_anchor(mut self, diff_base_anchor: text::Anchor) -> Self {
443        match &mut self {
444            Anchor::Min | Anchor::Max => {}
445            Anchor::Excerpt(excerpt_anchor) => {
446                excerpt_anchor.diff_base_anchor = Some(diff_base_anchor);
447            }
448        }
449        self
450    }
451}
452
453impl ToOffset for Anchor {
454    fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffset {
455        self.summary(snapshot)
456    }
457    fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffsetUtf16 {
458        self.summary(snapshot)
459    }
460}
461
462impl ToPoint for Anchor {
463    fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
464        self.summary(snapshot)
465    }
466    fn to_point_utf16(&self, snapshot: &MultiBufferSnapshot) -> rope::PointUtf16 {
467        self.summary(snapshot)
468    }
469}
470
471pub trait AnchorRangeExt {
472    fn cmp(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering;
473    fn includes(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool;
474    fn overlaps(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool;
475    fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<MultiBufferOffset>;
476    fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point>;
477}
478
479impl AnchorRangeExt for Range<Anchor> {
480    fn cmp(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering {
481        match self.start.cmp(&other.start, buffer) {
482            Ordering::Equal => other.end.cmp(&self.end, buffer),
483            ord => ord,
484        }
485    }
486
487    fn includes(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool {
488        self.start.cmp(&other.start, buffer).is_le() && other.end.cmp(&self.end, buffer).is_le()
489    }
490
491    fn overlaps(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool {
492        self.end.cmp(&other.start, buffer).is_ge() && self.start.cmp(&other.end, buffer).is_le()
493    }
494
495    fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<MultiBufferOffset> {
496        self.start.to_offset(content)..self.end.to_offset(content)
497    }
498
499    fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point> {
500        self.start.to_point(content)..self.end.to_point(content)
501    }
502}