anchor.rs

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