anchor.rs

  1use crate::{MultiBufferDimension, MultiBufferOffset, MultiBufferOffsetUtf16};
  2
  3use super::{ExcerptId, MultiBufferSnapshot, ToOffset, ToPoint};
  4use language::Point;
  5use std::{
  6    cmp::Ordering,
  7    ops::{Add, AddAssign, Range, Sub},
  8};
  9use sum_tree::Bias;
 10
 11/// A stable reference to a position within a [`MultiBuffer`](super::MultiBuffer).
 12///
 13/// Unlike simple offsets, anchors remain valid as the text is edited, automatically
 14/// adjusting to reflect insertions and deletions around them.
 15#[derive(Clone, Copy, Eq, PartialEq, Hash)]
 16pub struct Anchor {
 17    /// Identifies which excerpt within the multi-buffer this anchor belongs to.
 18    /// A multi-buffer can contain multiple excerpts from different buffers.
 19    pub excerpt_id: ExcerptId,
 20    /// The position within the excerpt's underlying buffer. This is a stable
 21    /// reference that remains valid as the buffer text is edited.
 22    pub text_anchor: text::Anchor,
 23    /// When present, indicates this anchor points into deleted text within an
 24    /// expanded diff hunk. The anchor references a position in the diff base
 25    /// (original) text rather than the current buffer text. This is used when
 26    /// displaying inline diffs where deleted lines are shown.
 27    pub diff_base_anchor: Option<text::Anchor>,
 28}
 29
 30impl std::fmt::Debug for Anchor {
 31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 32        if self.is_min() {
 33            return write!(f, "Anchor::min({:?})", self.text_anchor.buffer_id);
 34        }
 35        if self.is_max() {
 36            return write!(f, "Anchor::max({:?})", self.text_anchor.buffer_id);
 37        }
 38
 39        f.debug_struct("Anchor")
 40            .field("excerpt_id", &self.excerpt_id)
 41            .field("text_anchor", &self.text_anchor)
 42            .field("diff_base_anchor", &self.diff_base_anchor)
 43            .finish()
 44    }
 45}
 46
 47impl Anchor {
 48    pub fn with_diff_base_anchor(self, diff_base_anchor: text::Anchor) -> Self {
 49        Self {
 50            diff_base_anchor: Some(diff_base_anchor),
 51            ..self
 52        }
 53    }
 54
 55    pub fn in_buffer(excerpt_id: ExcerptId, text_anchor: text::Anchor) -> Self {
 56        Self {
 57            excerpt_id,
 58            text_anchor,
 59            diff_base_anchor: None,
 60        }
 61    }
 62
 63    pub fn range_in_buffer(excerpt_id: ExcerptId, range: Range<text::Anchor>) -> Range<Self> {
 64        Self::in_buffer(excerpt_id, range.start)..Self::in_buffer(excerpt_id, range.end)
 65    }
 66
 67    pub fn min() -> Self {
 68        Self {
 69            excerpt_id: ExcerptId::min(),
 70            text_anchor: text::Anchor::MIN,
 71            diff_base_anchor: None,
 72        }
 73    }
 74
 75    pub fn max() -> Self {
 76        Self {
 77            excerpt_id: ExcerptId::max(),
 78            text_anchor: text::Anchor::MAX,
 79            diff_base_anchor: None,
 80        }
 81    }
 82
 83    pub fn is_min(&self) -> bool {
 84        self.excerpt_id == ExcerptId::min()
 85            && self.text_anchor.is_min()
 86            && self.diff_base_anchor.is_none()
 87    }
 88
 89    pub fn is_max(&self) -> bool {
 90        self.excerpt_id == ExcerptId::max()
 91            && self.text_anchor.is_max()
 92            && self.diff_base_anchor.is_none()
 93    }
 94
 95    pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering {
 96        if self == other {
 97            return Ordering::Equal;
 98        }
 99
100        let self_excerpt_id = snapshot.latest_excerpt_id(self.excerpt_id);
101        let other_excerpt_id = snapshot.latest_excerpt_id(other.excerpt_id);
102
103        let excerpt_id_cmp = self_excerpt_id.cmp(&other_excerpt_id, snapshot);
104        if excerpt_id_cmp.is_ne() {
105            return excerpt_id_cmp;
106        }
107        if self_excerpt_id == ExcerptId::max()
108            && self.text_anchor.is_max()
109            && self.text_anchor.is_max()
110            && self.diff_base_anchor.is_none()
111            && other.diff_base_anchor.is_none()
112        {
113            return Ordering::Equal;
114        }
115        if let Some(excerpt) = snapshot.excerpt(self_excerpt_id) {
116            let text_cmp = self.text_anchor.cmp(&other.text_anchor, &excerpt.buffer);
117            if text_cmp.is_ne() {
118                return text_cmp;
119            }
120            if (self.diff_base_anchor.is_some() || other.diff_base_anchor.is_some())
121                && let Some(base_text) = snapshot
122                    .diffs
123                    .get(&excerpt.buffer_id)
124                    .map(|diff| diff.base_text())
125            {
126                let self_anchor = self.diff_base_anchor.filter(|a| a.is_valid(base_text));
127                let other_anchor = other.diff_base_anchor.filter(|a| a.is_valid(base_text));
128                return match (self_anchor, other_anchor) {
129                    (Some(a), Some(b)) => a.cmp(&b, base_text),
130                    (Some(_), None) => match other.text_anchor.bias {
131                        Bias::Left => Ordering::Greater,
132                        Bias::Right => Ordering::Less,
133                    },
134                    (None, Some(_)) => match self.text_anchor.bias {
135                        Bias::Left => Ordering::Less,
136                        Bias::Right => Ordering::Greater,
137                    },
138                    (None, None) => Ordering::Equal,
139                };
140            }
141        }
142        Ordering::Equal
143    }
144
145    pub fn bias(&self) -> Bias {
146        self.text_anchor.bias
147    }
148
149    pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
150        if self.text_anchor.bias != Bias::Left
151            && let Some(excerpt) = snapshot.excerpt(self.excerpt_id)
152        {
153            return Self {
154                excerpt_id: excerpt.id,
155                text_anchor: self.text_anchor.bias_left(&excerpt.buffer),
156                diff_base_anchor: self.diff_base_anchor.map(|a| {
157                    if let Some(base_text) = snapshot
158                        .diffs
159                        .get(&excerpt.buffer_id)
160                        .map(|diff| diff.base_text())
161                        && a.is_valid(&base_text)
162                    {
163                        return a.bias_left(base_text);
164                    }
165                    a
166                }),
167            };
168        }
169        *self
170    }
171
172    pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
173        if self.text_anchor.bias != Bias::Right
174            && let Some(excerpt) = snapshot.excerpt(self.excerpt_id)
175        {
176            return Self {
177                excerpt_id: excerpt.id,
178                text_anchor: self.text_anchor.bias_right(&excerpt.buffer),
179                diff_base_anchor: self.diff_base_anchor.map(|a| {
180                    if let Some(base_text) = snapshot
181                        .diffs
182                        .get(&excerpt.buffer_id)
183                        .map(|diff| diff.base_text())
184                        && a.is_valid(&base_text)
185                    {
186                        return a.bias_right(base_text);
187                    }
188                    a
189                }),
190            };
191        }
192        *self
193    }
194
195    pub fn summary<D>(&self, snapshot: &MultiBufferSnapshot) -> D
196    where
197        D: MultiBufferDimension
198            + Ord
199            + Sub<Output = D::TextDimension>
200            + Sub<D::TextDimension, Output = D>
201            + AddAssign<D::TextDimension>
202            + Add<D::TextDimension, Output = D>,
203        D::TextDimension: Sub<Output = D::TextDimension> + Ord,
204    {
205        snapshot.summary_for_anchor(self)
206    }
207
208    pub fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool {
209        if self.is_min() || self.is_max() {
210            true
211        } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
212            (self.text_anchor == excerpt.range.context.start
213                || self.text_anchor == excerpt.range.context.end
214                || self.text_anchor.is_valid(&excerpt.buffer))
215                && excerpt.contains(self)
216        } else {
217            false
218        }
219    }
220}
221
222impl ToOffset for Anchor {
223    fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffset {
224        self.summary(snapshot)
225    }
226    fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffsetUtf16 {
227        self.summary(snapshot)
228    }
229}
230
231impl ToPoint for Anchor {
232    fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
233        self.summary(snapshot)
234    }
235    fn to_point_utf16(&self, snapshot: &MultiBufferSnapshot) -> rope::PointUtf16 {
236        self.summary(snapshot)
237    }
238}
239
240pub trait AnchorRangeExt {
241    fn cmp(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering;
242    fn includes(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool;
243    fn overlaps(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool;
244    fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<MultiBufferOffset>;
245    fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point>;
246}
247
248impl AnchorRangeExt for Range<Anchor> {
249    fn cmp(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering {
250        match self.start.cmp(&other.start, buffer) {
251            Ordering::Equal => other.end.cmp(&self.end, buffer),
252            ord => ord,
253        }
254    }
255
256    fn includes(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool {
257        self.start.cmp(&other.start, buffer).is_le() && other.end.cmp(&self.end, buffer).is_le()
258    }
259
260    fn overlaps(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool {
261        self.end.cmp(&other.start, buffer).is_ge() && self.start.cmp(&other.end, buffer).is_le()
262    }
263
264    fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<MultiBufferOffset> {
265        self.start.to_offset(content)..self.end.to_offset(content)
266    }
267
268    fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point> {
269        self.start.to_point(content)..self.end.to_point(content)
270    }
271}
272
273/// An [`Anchor`] without a diff base anchor.
274///
275/// The main benefit of this type is that it almost half the size of a full anchor.
276/// Store this if you know you are never working with diff base anchors.
277#[derive(Clone, Copy, Eq, PartialEq, Hash)]
278pub struct DiffbaselessAnchor {
279    /// Identifies which excerpt within the multi-buffer this anchor belongs to.
280    /// A multi-buffer can contain multiple excerpts from different buffers.
281    pub excerpt_id: ExcerptId,
282    /// The position within the excerpt's underlying buffer. This is a stable
283    /// reference that remains valid as the buffer text is edited.
284    pub text_anchor: text::Anchor,
285}
286
287impl std::fmt::Debug for DiffbaselessAnchor {
288    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
289        if self.is_min() {
290            return write!(f, "Anchor::min({:?})", self.text_anchor.buffer_id);
291        }
292        if self.is_max() {
293            return write!(f, "Anchor::max({:?})", self.text_anchor.buffer_id);
294        }
295
296        f.debug_struct("Anchor")
297            .field("excerpt_id", &self.excerpt_id)
298            .field("text_anchor", &self.text_anchor)
299            .finish()
300    }
301}
302
303impl DiffbaselessAnchor {
304    pub fn in_buffer(excerpt_id: ExcerptId, text_anchor: text::Anchor) -> Self {
305        Self {
306            excerpt_id,
307            text_anchor,
308        }
309    }
310
311    pub fn range_in_buffer(excerpt_id: ExcerptId, range: Range<text::Anchor>) -> Range<Self> {
312        Self::in_buffer(excerpt_id, range.start)..Self::in_buffer(excerpt_id, range.end)
313    }
314
315    pub fn min() -> Self {
316        Self {
317            excerpt_id: ExcerptId::min(),
318            text_anchor: text::Anchor::MIN,
319        }
320    }
321
322    pub fn max() -> Self {
323        Self {
324            excerpt_id: ExcerptId::max(),
325            text_anchor: text::Anchor::MAX,
326        }
327    }
328
329    pub fn is_min(&self) -> bool {
330        self.excerpt_id == ExcerptId::min() && self.text_anchor.is_min()
331    }
332
333    pub fn is_max(&self) -> bool {
334        self.excerpt_id == ExcerptId::max() && self.text_anchor.is_max()
335    }
336
337    pub fn cmp(&self, other: &DiffbaselessAnchor, snapshot: &MultiBufferSnapshot) -> Ordering {
338        if self == other {
339            return Ordering::Equal;
340        }
341
342        let self_excerpt_id = snapshot.latest_excerpt_id(self.excerpt_id);
343        let other_excerpt_id = snapshot.latest_excerpt_id(other.excerpt_id);
344
345        let excerpt_id_cmp = self_excerpt_id.cmp(&other_excerpt_id, snapshot);
346        if excerpt_id_cmp.is_ne() {
347            return excerpt_id_cmp;
348        }
349        if self_excerpt_id == ExcerptId::max()
350            && self.text_anchor.is_max()
351            && self.text_anchor.is_max()
352        {
353            return Ordering::Equal;
354        }
355        if let Some(excerpt) = snapshot.excerpt(self_excerpt_id) {
356            let text_cmp = self.text_anchor.cmp(&other.text_anchor, &excerpt.buffer);
357            if text_cmp.is_ne() {
358                return text_cmp;
359            }
360        }
361        Ordering::Equal
362    }
363
364    pub fn bias(&self) -> Bias {
365        self.text_anchor.bias
366    }
367
368    pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> DiffbaselessAnchor {
369        if self.text_anchor.bias != Bias::Left
370            && let Some(excerpt) = snapshot.excerpt(self.excerpt_id)
371        {
372            return Self {
373                excerpt_id: excerpt.id,
374                text_anchor: self.text_anchor.bias_left(&excerpt.buffer),
375            };
376        }
377        *self
378    }
379
380    pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> DiffbaselessAnchor {
381        if self.text_anchor.bias != Bias::Right
382            && let Some(excerpt) = snapshot.excerpt(self.excerpt_id)
383        {
384            return Self {
385                excerpt_id: excerpt.id,
386                text_anchor: self.text_anchor.bias_right(&excerpt.buffer),
387            };
388        }
389        *self
390    }
391
392    pub fn summary<D>(&self, snapshot: &MultiBufferSnapshot) -> D
393    where
394        D: MultiBufferDimension
395            + Ord
396            + Sub<Output = D::TextDimension>
397            + Sub<D::TextDimension, Output = D>
398            + AddAssign<D::TextDimension>
399            + Add<D::TextDimension, Output = D>,
400        D::TextDimension: Sub<Output = D::TextDimension> + Ord,
401    {
402        snapshot.summary_for_anchor(&Anchor {
403            excerpt_id: self.excerpt_id,
404            text_anchor: self.text_anchor,
405            diff_base_anchor: None,
406        })
407    }
408
409    pub fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool {
410        if self.is_min() || self.is_max() {
411            true
412        } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
413            (self.text_anchor == excerpt.range.context.start
414                || self.text_anchor == excerpt.range.context.end
415                || self.text_anchor.is_valid(&excerpt.buffer))
416                && excerpt.contains_diffbaseless(self)
417        } else {
418            false
419        }
420    }
421}
422
423impl ToOffset for DiffbaselessAnchor {
424    fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffset {
425        self.summary(snapshot)
426    }
427    fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffsetUtf16 {
428        self.summary(snapshot)
429    }
430}
431
432impl ToPoint for DiffbaselessAnchor {
433    fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
434        self.summary(snapshot)
435    }
436    fn to_point_utf16(&self, snapshot: &MultiBufferSnapshot) -> rope::PointUtf16 {
437        self.summary(snapshot)
438    }
439}
440
441pub trait DiffbaselessAnchorRangeExt {
442    fn cmp(&self, other: &Range<DiffbaselessAnchor>, buffer: &MultiBufferSnapshot) -> Ordering;
443    fn includes(&self, other: &Range<DiffbaselessAnchor>, buffer: &MultiBufferSnapshot) -> bool;
444    fn overlaps(&self, other: &Range<DiffbaselessAnchor>, buffer: &MultiBufferSnapshot) -> bool;
445    fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<MultiBufferOffset>;
446    fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point>;
447}
448
449impl DiffbaselessAnchorRangeExt for Range<DiffbaselessAnchor> {
450    fn cmp(&self, other: &Range<DiffbaselessAnchor>, buffer: &MultiBufferSnapshot) -> Ordering {
451        match self.start.cmp(&other.start, buffer) {
452            Ordering::Equal => other.end.cmp(&self.end, buffer),
453            ord => ord,
454        }
455    }
456
457    fn includes(&self, other: &Range<DiffbaselessAnchor>, buffer: &MultiBufferSnapshot) -> bool {
458        self.start.cmp(&other.start, buffer).is_le() && other.end.cmp(&self.end, buffer).is_le()
459    }
460
461    fn overlaps(&self, other: &Range<DiffbaselessAnchor>, buffer: &MultiBufferSnapshot) -> bool {
462        self.end.cmp(&other.start, buffer).is_ge() && self.start.cmp(&other.end, buffer).is_le()
463    }
464
465    fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<MultiBufferOffset> {
466        self.start.to_offset(content)..self.end.to_offset(content)
467    }
468
469    fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point> {
470        self.start.to_point(content)..self.end.to_point(content)
471    }
472}
473
474pub struct AnchorHasDiffbaseError;
475
476impl TryFrom<Anchor> for DiffbaselessAnchor {
477    type Error = AnchorHasDiffbaseError;
478
479    fn try_from(anchor: Anchor) -> Result<Self, AnchorHasDiffbaseError> {
480        if anchor.diff_base_anchor.is_some() {
481            return Err(AnchorHasDiffbaseError);
482        }
483        Ok(DiffbaselessAnchor {
484            excerpt_id: anchor.excerpt_id,
485            text_anchor: anchor.text_anchor,
486        })
487    }
488}
489
490impl From<DiffbaselessAnchor> for Anchor {
491    fn from(diffbaseless: DiffbaselessAnchor) -> Self {
492        Anchor {
493            excerpt_id: diffbaseless.excerpt_id,
494            text_anchor: diffbaseless.text_anchor,
495            diff_base_anchor: None,
496        }
497    }
498}