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}