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