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