anchor.rs

  1use crate::{
  2    ExcerptSummary, MultiBufferDimension, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
  3    PathKeyIndex,
  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, Eq, PartialEq, Hash)]
 17pub(crate) struct ExcerptAnchor {
 18    /// The position within the excerpt's underlying buffer. This is a stable
 19    /// reference that remains valid as the buffer text is edited.
 20    pub(crate) timestamp: clock::Lamport,
 21
 22    /// The byte offset into the text inserted in the operation
 23    /// at `timestamp`.
 24    pub(crate) offset: u32,
 25    /// Whether this anchor stays attached to the character *before* or *after*
 26    /// the offset.
 27    pub(crate) bias: Bias,
 28    pub(crate) buffer_id: BufferId,
 29    /// Refers to the path key that the buffer had when this anchor was created,
 30    /// so that ordering is stable when the path key for a buffer changes
 31    pub(crate) path: PathKeyIndex,
 32    /// When present, indicates this anchor points into deleted text within an
 33    /// expanded diff hunk. The anchor references a position in the diff base
 34    /// (original) text rather than the current buffer text. This is used when
 35    /// displaying inline diffs where deleted lines are shown.
 36    pub(crate) diff_base_anchor: Option<text::Anchor>,
 37}
 38
 39/// A stable reference to a position within a [`MultiBuffer`](super::MultiBuffer).
 40///
 41/// Unlike simple offsets, anchors remain valid as the text is edited, automatically
 42/// adjusting to reflect insertions and deletions around them.
 43#[derive(Clone, Copy, Eq, PartialEq, Hash)]
 44pub enum Anchor {
 45    Min,
 46    Excerpt(ExcerptAnchor),
 47    Max,
 48}
 49
 50impl std::fmt::Debug for ExcerptAnchor {
 51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 52        f.debug_struct("Anchor")
 53            .field("text_anchor", &self.text_anchor())
 54            .field("diff_base_anchor", &self.diff_base_anchor)
 55            .finish()
 56    }
 57}
 58
 59// todo!() should this take a lifetime?
 60pub(crate) enum AnchorSeekTarget {
 61    Min,
 62    Max,
 63    Excerpt {
 64        path_key: PathKey,
 65        anchor: ExcerptAnchor,
 66        // None when the buffer no longer exists in the multibuffer
 67        snapshot: Option<BufferSnapshot>,
 68    },
 69}
 70
 71impl std::fmt::Debug for Anchor {
 72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 73        match self {
 74            Anchor::Min => write!(f, "Anchor::Min"),
 75            Anchor::Max => write!(f, "Anchor::Max"),
 76            Anchor::Excerpt(excerpt_anchor) => write!(f, "{excerpt_anchor:?}"),
 77        }
 78    }
 79}
 80
 81impl From<ExcerptAnchor> for Anchor {
 82    fn from(anchor: ExcerptAnchor) -> Self {
 83        Anchor::Excerpt(anchor)
 84    }
 85}
 86
 87impl ExcerptAnchor {
 88    pub(crate) fn text_anchor(&self) -> text::Anchor {
 89        text::Anchor::new(self.timestamp, self.offset, self.bias, Some(self.buffer_id))
 90    }
 91
 92    pub(crate) fn with_diff_base_anchor(mut self, diff_base_anchor: text::Anchor) -> Self {
 93        self.diff_base_anchor = Some(diff_base_anchor);
 94        self
 95    }
 96
 97    pub(crate) fn cmp(&self, other: &Self, snapshot: &MultiBufferSnapshot) -> Ordering {
 98        let Some(self_path_key) = snapshot.path_keys_by_index.get(&self.path) else {
 99            panic!("anchor's path was never added to multibuffer")
100        };
101        let Some(other_path_key) = snapshot.path_keys_by_index.get(&other.path) else {
102            panic!("anchor's path was never added to multibuffer")
103        };
104
105        if self_path_key.cmp(other_path_key) != Ordering::Equal {
106            return self_path_key.cmp(other_path_key);
107        }
108
109        // in the case that you removed the buffer containing self,
110        // and added the buffer containing other with the same path key
111        // (ordering is arbitrary but consistent)
112        if self.buffer_id != other.buffer_id {
113            return self.buffer_id.cmp(&other.buffer_id);
114        }
115
116        let Some(buffer) = snapshot.buffer_for_path(&self_path_key) else {
117            return Ordering::Equal;
118        };
119        let text_cmp = self.text_anchor().cmp(&other.text_anchor(), buffer);
120        if text_cmp != Ordering::Equal {
121            return text_cmp;
122        }
123
124        if (self.diff_base_anchor.is_some() || other.diff_base_anchor.is_some())
125            && let Some(base_text) = snapshot
126                .diffs
127                .get(&self.buffer_id)
128                .map(|diff| diff.base_text())
129        {
130            let self_anchor = self.diff_base_anchor.filter(|a| a.is_valid(base_text));
131            let other_anchor = other.diff_base_anchor.filter(|a| a.is_valid(base_text));
132            return match (self_anchor, other_anchor) {
133                (Some(a), Some(b)) => a.cmp(&b, base_text),
134                (Some(_), None) => match other.text_anchor().bias {
135                    Bias::Left => Ordering::Greater,
136                    Bias::Right => Ordering::Less,
137                },
138                (None, Some(_)) => match self.text_anchor().bias {
139                    Bias::Left => Ordering::Less,
140                    Bias::Right => Ordering::Greater,
141                },
142                (None, None) => Ordering::Equal,
143            };
144        }
145
146        Ordering::Equal
147    }
148
149    fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Self {
150        if self.bias == Bias::Left {
151            return *self;
152        }
153        let Some(buffer) = snapshot.buffer_for_id(self.buffer_id) else {
154            return *self;
155        };
156        let text_anchor = self.text_anchor().bias_left(&buffer);
157        let ret = Self::in_buffer(self.path, text_anchor);
158        if let Some(diff_base_anchor) = self.diff_base_anchor {
159            if let Some(diff) = snapshot.diffs.get(&self.buffer_id)
160                && diff_base_anchor.is_valid(&diff.base_text())
161            {
162                ret.with_diff_base_anchor(diff_base_anchor.bias_left(diff.base_text()))
163            } else {
164                ret.with_diff_base_anchor(diff_base_anchor)
165            }
166        } else {
167            ret
168        }
169    }
170
171    fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Self {
172        if self.bias == Bias::Right {
173            return *self;
174        }
175        let Some(buffer) = snapshot.buffer_for_id(self.buffer_id) else {
176            return *self;
177        };
178        let text_anchor = self.text_anchor().bias_right(&buffer);
179        let ret = Self::in_buffer(self.path, text_anchor);
180        if let Some(diff_base_anchor) = self.diff_base_anchor {
181            if let Some(diff) = snapshot.diffs.get(&self.buffer_id)
182                && diff_base_anchor.is_valid(&diff.base_text())
183            {
184                ret.with_diff_base_anchor(diff_base_anchor.bias_right(diff.base_text()))
185            } else {
186                ret.with_diff_base_anchor(diff_base_anchor)
187            }
188        } else {
189            ret
190        }
191    }
192
193    pub(crate) fn in_buffer(path: PathKeyIndex, text_anchor: text::Anchor) -> Self {
194        let Some(buffer_id) = text_anchor.buffer_id else {
195            panic!("text_anchor must have a buffer_id");
196        };
197        ExcerptAnchor {
198            path,
199            diff_base_anchor: None,
200            timestamp: text_anchor.timestamp(),
201            buffer_id,
202            offset: text_anchor.offset,
203            bias: text_anchor.bias,
204        }
205    }
206
207    fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool {
208        let Some(target) = self.try_seek_target(snapshot) else {
209            return false;
210        };
211        let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(());
212        cursor.seek(&target, Bias::Left);
213        let Some(excerpt) = cursor.item() else {
214            return false;
215        };
216        excerpt.buffer_id == self.buffer_id
217            && excerpt
218                .range
219                .context
220                .start
221                .cmp(&self.text_anchor(), &excerpt.buffer_snapshot(snapshot))
222                .is_le()
223            && excerpt
224                .range
225                .context
226                .end
227                .cmp(&self.text_anchor(), &excerpt.buffer_snapshot(snapshot))
228                .is_ge()
229    }
230
231    pub(crate) fn seek_target(&self, snapshot: &MultiBufferSnapshot) -> AnchorSeekTarget {
232        self.try_seek_target(snapshot)
233            .expect("anchor is from different multi-buffer")
234    }
235
236    pub(crate) fn try_seek_target(
237        &self,
238        snapshot: &MultiBufferSnapshot,
239    ) -> Option<AnchorSeekTarget> {
240        let path_key = snapshot.try_path_for_anchor(*self)?;
241        let buffer = snapshot.buffer_for_path(&path_key).cloned();
242        Some(AnchorSeekTarget::Excerpt {
243            path_key,
244            anchor: *self,
245            snapshot: buffer,
246        })
247    }
248}
249
250impl Anchor {
251    pub fn min() -> Self {
252        Self::Min
253    }
254
255    pub fn max() -> Self {
256        Self::Max
257    }
258
259    pub fn is_min(&self) -> bool {
260        matches!(self, Self::Min)
261    }
262
263    pub fn is_max(&self) -> bool {
264        matches!(self, Self::Max)
265    }
266
267    pub fn in_buffer(path: PathKeyIndex, text_anchor: text::Anchor) -> Self {
268        Self::Excerpt(ExcerptAnchor::in_buffer(path, text_anchor))
269    }
270
271    pub fn range_in_buffer(path: PathKeyIndex, range: Range<text::Anchor>) -> Range<Self> {
272        Self::in_buffer(path, range.start)..Self::in_buffer(path, range.end)
273    }
274
275    pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering {
276        match (self, other) {
277            (Anchor::Min, Anchor::Min) => return Ordering::Equal,
278            (Anchor::Max, Anchor::Max) => return Ordering::Equal,
279            (Anchor::Min, _) => return Ordering::Less,
280            (Anchor::Max, _) => return Ordering::Greater,
281            (_, Anchor::Max) => return Ordering::Less,
282            (_, Anchor::Min) => return Ordering::Greater,
283            (Anchor::Excerpt(self_excerpt_anchor), Anchor::Excerpt(other_excerpt_anchor)) => {
284                self_excerpt_anchor.cmp(other_excerpt_anchor, snapshot)
285            }
286        }
287    }
288
289    pub fn bias(&self) -> Bias {
290        match self {
291            Anchor::Min => Bias::Left,
292            Anchor::Max => Bias::Right,
293            Anchor::Excerpt(anchor) => anchor.bias,
294        }
295    }
296
297    pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
298        match self {
299            Anchor::Min => *self,
300            Anchor::Max => snapshot.anchor_before(snapshot.max_point()),
301            Anchor::Excerpt(anchor) => Anchor::Excerpt(anchor.bias_left(snapshot)),
302        }
303    }
304
305    pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
306        match self {
307            Anchor::Max => *self,
308            Anchor::Min => snapshot.anchor_after(Point::zero()),
309            Anchor::Excerpt(anchor) => Anchor::Excerpt(anchor.bias_right(snapshot)),
310        }
311    }
312
313    pub fn summary<D>(&self, snapshot: &MultiBufferSnapshot) -> D
314    where
315        D: MultiBufferDimension
316            + Ord
317            + Sub<Output = D::TextDimension>
318            + Sub<D::TextDimension, Output = D>
319            + AddAssign<D::TextDimension>
320            + Add<D::TextDimension, Output = D>,
321        D::TextDimension: Sub<Output = D::TextDimension> + Ord,
322    {
323        snapshot.summary_for_anchor(self)
324    }
325
326    pub fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool {
327        match self {
328            Anchor::Min | Anchor::Max => true,
329            Anchor::Excerpt(excerpt_anchor) => excerpt_anchor.is_valid(snapshot),
330        }
331    }
332
333    pub(crate) fn seek_target(&self, snapshot: &MultiBufferSnapshot) -> AnchorSeekTarget {
334        match self {
335            Anchor::Min => AnchorSeekTarget::Min,
336            Anchor::Excerpt(excerpt_anchor) => excerpt_anchor.seek_target(snapshot),
337            Anchor::Max => AnchorSeekTarget::Max,
338        }
339    }
340
341    pub(crate) fn excerpt_anchor(&self) -> Option<ExcerptAnchor> {
342        match self {
343            Anchor::Min | Anchor::Max => None,
344            Anchor::Excerpt(excerpt_anchor) => Some(*excerpt_anchor),
345        }
346    }
347
348    pub(crate) fn try_seek_target(
349        &self,
350        snapshot: &MultiBufferSnapshot,
351    ) -> Option<AnchorSeekTarget> {
352        match self {
353            Anchor::Min => Some(AnchorSeekTarget::Min),
354            Anchor::Excerpt(excerpt_anchor) => excerpt_anchor.try_seek_target(snapshot),
355            Anchor::Max => Some(AnchorSeekTarget::Max),
356        }
357    }
358}
359
360impl ToOffset for Anchor {
361    fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffset {
362        self.summary(snapshot)
363    }
364    fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffsetUtf16 {
365        self.summary(snapshot)
366    }
367}
368
369impl ToPoint for Anchor {
370    fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
371        self.summary(snapshot)
372    }
373    fn to_point_utf16(&self, snapshot: &MultiBufferSnapshot) -> rope::PointUtf16 {
374        self.summary(snapshot)
375    }
376}
377
378pub trait AnchorRangeExt {
379    fn cmp(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering;
380    fn includes(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool;
381    fn overlaps(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool;
382    fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<MultiBufferOffset>;
383    fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point>;
384}
385
386impl AnchorRangeExt for Range<Anchor> {
387    fn cmp(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering {
388        match self.start.cmp(&other.start, buffer) {
389            Ordering::Equal => other.end.cmp(&self.end, buffer),
390            ord => ord,
391        }
392    }
393
394    fn includes(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool {
395        self.start.cmp(&other.start, buffer).is_le() && other.end.cmp(&self.end, buffer).is_le()
396    }
397
398    fn overlaps(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool {
399        self.end.cmp(&other.start, buffer).is_ge() && self.start.cmp(&other.end, buffer).is_le()
400    }
401
402    fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<MultiBufferOffset> {
403        self.start.to_offset(content)..self.end.to_offset(content)
404    }
405
406    fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point> {
407        self.start.to_point(content)..self.end.to_point(content)
408    }
409}