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}