Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:38:55.033Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

anchor.rs

537 lines · 18.8 KB · rust
1use crate::{
2    ExcerptSummary, MultiBufferDimension, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
3    PathKeyIndex, find_diff_state,
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, Debug, Eq, PartialEq, Hash)]
17pub struct ExcerptAnchor {
18    pub(crate) text_anchor: text::Anchor,
19    pub(crate) path: PathKeyIndex,
20    pub(crate) diff_base_anchor: Option<text::Anchor>,
21}
22
23/// A stable reference to a position within a [`MultiBuffer`](super::MultiBuffer).
24///
25/// Unlike simple offsets, anchors remain valid as the text is edited, automatically
26/// adjusting to reflect insertions and deletions around them.
27#[derive(Clone, Copy, Eq, PartialEq, Hash)]
28pub enum Anchor {
29    /// An anchor that always resolves to the start of the multibuffer.
30    Min,
31    /// An anchor that's attached to a specific excerpted buffer.
32    Excerpt(ExcerptAnchor),
33    /// An anchor that always resolves to the end of the multibuffer.
34    Max,
35}
36
37pub(crate) enum AnchorSeekTarget<'a> {
38    // buffer no longer exists at its original path key in the multibuffer
39    Missing {
40        path_key: &'a PathKey,
41    },
42    // we have excerpts for the buffer at the expected path key
43    Excerpt {
44        path_key: &'a PathKey,
45        path_key_index: PathKeyIndex,
46        anchor: text::Anchor,
47        snapshot: &'a BufferSnapshot,
48    },
49    // no excerpts and it's a min or max anchor
50    Empty,
51}
52
53impl std::fmt::Debug for AnchorSeekTarget<'_> {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        match self {
56            Self::Excerpt {
57                path_key,
58                path_key_index: _,
59                anchor,
60                snapshot: _,
61            } => f
62                .debug_struct("Excerpt")
63                .field("path_key", path_key)
64                .field("anchor", anchor)
65                .finish(),
66            Self::Missing { path_key } => f
67                .debug_struct("Missing")
68                .field("path_key", path_key)
69                .finish(),
70            Self::Empty => f.debug_struct("Empty").finish(),
71        }
72    }
73}
74
75impl std::fmt::Debug for Anchor {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        match self {
78            Anchor::Min => write!(f, "Anchor::Min"),
79            Anchor::Max => write!(f, "Anchor::Max"),
80            Anchor::Excerpt(excerpt_anchor) => write!(f, "{excerpt_anchor:?}"),
81        }
82    }
83}
84
85impl From<ExcerptAnchor> for Anchor {
86    fn from(anchor: ExcerptAnchor) -> Self {
87        Anchor::Excerpt(anchor)
88    }
89}
90
91impl ExcerptAnchor {
92    pub(crate) fn buffer_id(&self) -> BufferId {
93        self.text_anchor.buffer_id
94    }
95
96    pub(crate) fn text_anchor(&self) -> text::Anchor {
97        self.text_anchor
98    }
99
100    pub(crate) fn with_diff_base_anchor(mut self, diff_base_anchor: text::Anchor) -> Self {
101        self.diff_base_anchor = Some(diff_base_anchor);
102        self
103    }
104
105    pub(crate) fn cmp(&self, other: &Self, snapshot: &MultiBufferSnapshot) -> Ordering {
106        let Some(self_path_key) = snapshot.path_keys.get_index(self.path.0 as usize) else {
107            panic!("anchor's path was never added to multibuffer")
108        };
109        let Some(other_path_key) = snapshot.path_keys.get_index(other.path.0 as usize) else {
110            panic!("anchor's path was never added to multibuffer")
111        };
112
113        match self_path_key.cmp(other_path_key) {
114            Ordering::Equal => (),
115            ordering => return ordering,
116        }
117
118        // in the case that you removed the buffer containing self,
119        // and added the buffer containing other with the same path key
120        // (ordering is arbitrary but consistent)
121        if self.text_anchor.buffer_id != other.text_anchor.buffer_id {
122            return self.text_anchor.buffer_id.cmp(&other.text_anchor.buffer_id);
123        }
124
125        // two anchors into the same buffer at the same path
126        let Some(buffer) = snapshot
127            .buffers
128            .get(&self.text_anchor.buffer_id)
129            .filter(|buffer_state| buffer_state.path_key == *self_path_key)
130        else {
131            // buffer no longer exists at the original path (which may have been reused for a different buffer),
132            // so no way to compare the anchors
133            return Ordering::Equal;
134        };
135        // two anchors into the same buffer at the same path that still exists at that path in the multibuffer
136        let text_cmp = self
137            .text_anchor()
138            .cmp(&other.text_anchor(), &buffer.buffer_snapshot);
139        if text_cmp != Ordering::Equal {
140            return text_cmp;
141        }
142
143        if (self.diff_base_anchor.is_some() || other.diff_base_anchor.is_some())
144            && let Some(base_text) = find_diff_state(&snapshot.diffs, self.text_anchor.buffer_id)
145                .map(|diff| diff.base_text())
146        {
147            let self_anchor = self.diff_base_anchor.filter(|a| a.is_valid(base_text));
148            let other_anchor = other.diff_base_anchor.filter(|a| a.is_valid(base_text));
149            return match (self_anchor, other_anchor) {
150                (Some(a), Some(b)) => a.cmp(&b, base_text),
151                (Some(_), None) => match other.text_anchor().bias {
152                    Bias::Left => Ordering::Greater,
153                    Bias::Right => Ordering::Less,
154                },
155                (None, Some(_)) => match self.text_anchor().bias {
156                    Bias::Left => Ordering::Less,
157                    Bias::Right => Ordering::Greater,
158                },
159                (None, None) => Ordering::Equal,
160            };
161        }
162
163        Ordering::Equal
164    }
165
166    fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Self {
167        if self.text_anchor.bias == Bias::Left {
168            return *self;
169        }
170        let Some(buffer) = snapshot.buffer_for_id(self.text_anchor.buffer_id) else {
171            return *self;
172        };
173        let text_anchor = self.text_anchor().bias_left(&buffer);
174        let ret = Self::in_buffer(self.path, text_anchor);
175        if let Some(diff_base_anchor) = self.diff_base_anchor {
176            if let Some(diff) = find_diff_state(&snapshot.diffs, self.text_anchor.buffer_id)
177                && diff_base_anchor.is_valid(&diff.base_text())
178            {
179                ret.with_diff_base_anchor(diff_base_anchor.bias_left(diff.base_text()))
180            } else {
181                ret.with_diff_base_anchor(diff_base_anchor)
182            }
183        } else {
184            ret
185        }
186    }
187
188    fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Self {
189        if self.text_anchor.bias == Bias::Right {
190            return *self;
191        }
192        let Some(buffer) = snapshot.buffer_for_id(self.text_anchor.buffer_id) else {
193            return *self;
194        };
195        let text_anchor = self.text_anchor().bias_right(&buffer);
196        let ret = Self::in_buffer(self.path, text_anchor);
197        if let Some(diff_base_anchor) = self.diff_base_anchor {
198            if let Some(diff) = find_diff_state(&snapshot.diffs, self.text_anchor.buffer_id)
199                && diff_base_anchor.is_valid(&diff.base_text())
200            {
201                ret.with_diff_base_anchor(diff_base_anchor.bias_right(diff.base_text()))
202            } else {
203                ret.with_diff_base_anchor(diff_base_anchor)
204            }
205        } else {
206            ret
207        }
208    }
209
210    #[track_caller]
211    pub(crate) fn in_buffer(path: PathKeyIndex, text_anchor: text::Anchor) -> Self {
212        ExcerptAnchor {
213            path,
214            diff_base_anchor: None,
215            text_anchor,
216        }
217    }
218
219    fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool {
220        let Some(target) = self.try_seek_target(snapshot) else {
221            return false;
222        };
223        let Some(buffer_snapshot) = snapshot.buffer_for_id(self.buffer_id()) else {
224            return false;
225        };
226        // Early check to avoid invalid comparisons when seeking
227        if !buffer_snapshot.can_resolve(&self.text_anchor) {
228            return false;
229        }
230        let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(());
231        cursor.seek(&target, Bias::Left);
232        let Some(excerpt) = cursor.item() else {
233            return false;
234        };
235        if excerpt.buffer_id != buffer_snapshot.remote_id() {
236            return false;
237        }
238        let is_valid = self.text_anchor == excerpt.range.context.start
239            || self.text_anchor == excerpt.range.context.end
240            || self.text_anchor.is_valid(&buffer_snapshot);
241        is_valid
242            && excerpt
243                .range
244                .context
245                .start
246                .cmp(&self.text_anchor(), buffer_snapshot)
247                .is_le()
248            && excerpt
249                .range
250                .context
251                .end
252                .cmp(&self.text_anchor(), buffer_snapshot)
253                .is_ge()
254    }
255
256    pub(crate) fn seek_target<'a>(
257        &self,
258        snapshot: &'a MultiBufferSnapshot,
259    ) -> AnchorSeekTarget<'a> {
260        self.try_seek_target(snapshot)
261            .expect("anchor is from different multi-buffer")
262    }
263
264    pub(crate) fn try_seek_target<'a>(
265        &self,
266        snapshot: &'a MultiBufferSnapshot,
267    ) -> Option<AnchorSeekTarget<'a>> {
268        let path_key = snapshot.try_path_for_anchor(*self)?;
269
270        let Some(state) = snapshot
271            .buffers
272            .get(&self.buffer_id())
273            .filter(|state| &state.path_key == path_key)
274        else {
275            return Some(AnchorSeekTarget::Missing { path_key });
276        };
277
278        Some(AnchorSeekTarget::Excerpt {
279            path_key,
280            path_key_index: self.path,
281            anchor: self.text_anchor(),
282            snapshot: &state.buffer_snapshot,
283        })
284    }
285}
286
287impl ToOffset for ExcerptAnchor {
288    fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffset {
289        Anchor::from(*self).to_offset(snapshot)
290    }
291
292    fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffsetUtf16 {
293        Anchor::from(*self).to_offset_utf16(snapshot)
294    }
295}
296
297impl ToPoint for ExcerptAnchor {
298    fn to_point(&self, snapshot: &MultiBufferSnapshot) -> Point {
299        Anchor::from(*self).to_point(snapshot)
300    }
301
302    fn to_point_utf16(&self, snapshot: &MultiBufferSnapshot) -> rope::PointUtf16 {
303        Anchor::from(*self).to_point_utf16(snapshot)
304    }
305}
306
307impl Anchor {
308    pub fn is_min(&self) -> bool {
309        matches!(self, Self::Min)
310    }
311
312    pub fn is_max(&self) -> bool {
313        matches!(self, Self::Max)
314    }
315
316    pub(crate) fn in_buffer(path: PathKeyIndex, text_anchor: text::Anchor) -> Self {
317        Self::Excerpt(ExcerptAnchor::in_buffer(path, text_anchor))
318    }
319
320    pub(crate) fn range_in_buffer(path: PathKeyIndex, range: Range<text::Anchor>) -> Range<Self> {
321        Self::in_buffer(path, range.start)..Self::in_buffer(path, range.end)
322    }
323
324    pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering {
325        match (self, other) {
326            (Anchor::Min, Anchor::Min) => return Ordering::Equal,
327            (Anchor::Max, Anchor::Max) => return Ordering::Equal,
328            (Anchor::Min, _) => return Ordering::Less,
329            (Anchor::Max, _) => return Ordering::Greater,
330            (_, Anchor::Max) => return Ordering::Less,
331            (_, Anchor::Min) => return Ordering::Greater,
332            (Anchor::Excerpt(self_excerpt_anchor), Anchor::Excerpt(other_excerpt_anchor)) => {
333                self_excerpt_anchor.cmp(other_excerpt_anchor, snapshot)
334            }
335        }
336    }
337
338    pub fn bias(&self) -> Bias {
339        match self {
340            Anchor::Min => Bias::Left,
341            Anchor::Max => Bias::Right,
342            Anchor::Excerpt(anchor) => anchor.text_anchor.bias,
343        }
344    }
345
346    pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
347        match self {
348            Anchor::Min => *self,
349            Anchor::Max => snapshot.anchor_before(snapshot.max_point()),
350            Anchor::Excerpt(anchor) => Anchor::Excerpt(anchor.bias_left(snapshot)),
351        }
352    }
353
354    pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
355        match self {
356            Anchor::Max => *self,
357            Anchor::Min => snapshot.anchor_after(Point::zero()),
358            Anchor::Excerpt(anchor) => Anchor::Excerpt(anchor.bias_right(snapshot)),
359        }
360    }
361
362    pub fn summary<D>(&self, snapshot: &MultiBufferSnapshot) -> D
363    where
364        D: MultiBufferDimension
365            + Ord
366            + Sub<Output = D::TextDimension>
367            + Sub<D::TextDimension, Output = D>
368            + AddAssign<D::TextDimension>
369            + Add<D::TextDimension, Output = D>,
370        D::TextDimension: Sub<Output = D::TextDimension> + Ord,
371    {
372        snapshot.summary_for_anchor(self)
373    }
374
375    pub fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool {
376        match self {
377            Anchor::Min | Anchor::Max => true,
378            Anchor::Excerpt(excerpt_anchor) => excerpt_anchor.is_valid(snapshot),
379        }
380    }
381
382    fn to_excerpt_anchor(&self, snapshot: &MultiBufferSnapshot) -> Option<ExcerptAnchor> {
383        match self {
384            Anchor::Min => {
385                let excerpt = snapshot.excerpts.first()?;
386
387                Some(ExcerptAnchor {
388                    text_anchor: excerpt.range.context.start,
389                    path: excerpt.path_key_index,
390                    diff_base_anchor: None,
391                })
392            }
393            Anchor::Excerpt(excerpt_anchor) => Some(*excerpt_anchor),
394            Anchor::Max => {
395                let excerpt = snapshot.excerpts.last()?;
396
397                Some(ExcerptAnchor {
398                    text_anchor: excerpt.range.context.end,
399                    path: excerpt.path_key_index,
400                    diff_base_anchor: None,
401                })
402            }
403        }
404    }
405
406    pub(crate) fn seek_target<'a>(
407        &self,
408        snapshot: &'a MultiBufferSnapshot,
409    ) -> AnchorSeekTarget<'a> {
410        let Some(excerpt_anchor) = self.to_excerpt_anchor(snapshot) else {
411            return AnchorSeekTarget::Empty;
412        };
413
414        excerpt_anchor.seek_target(snapshot)
415    }
416
417    pub(crate) fn excerpt_anchor(&self) -> Option<ExcerptAnchor> {
418        match self {
419            Anchor::Min | Anchor::Max => None,
420            Anchor::Excerpt(excerpt_anchor) => Some(*excerpt_anchor),
421        }
422    }
423
424    pub(crate) fn text_anchor(&self) -> Option<text::Anchor> {
425        match self {
426            Anchor::Min | Anchor::Max => None,
427            Anchor::Excerpt(excerpt_anchor) => Some(excerpt_anchor.text_anchor()),
428        }
429    }
430
431    pub fn opaque_id(&self) -> Option<[u8; 20]> {
432        self.text_anchor().map(|a| a.opaque_id())
433    }
434
435    /// Note: anchor_to_buffer_anchor is probably what you want
436    pub fn raw_text_anchor(&self) -> Option<text::Anchor> {
437        match self {
438            Anchor::Min | Anchor::Max => None,
439            Anchor::Excerpt(excerpt_anchor) => Some(excerpt_anchor.text_anchor),
440        }
441    }
442
443    pub(crate) fn try_seek_target<'a>(
444        &self,
445        snapshot: &'a MultiBufferSnapshot,
446    ) -> Option<AnchorSeekTarget<'a>> {
447        let Some(excerpt_anchor) = self.to_excerpt_anchor(snapshot) else {
448            return Some(AnchorSeekTarget::Empty);
449        };
450        excerpt_anchor.try_seek_target(snapshot)
451    }
452
453    /// Returns the text anchor for this anchor.
454    /// Panics if the anchor is from a different buffer.
455    pub fn text_anchor_in(&self, buffer: &BufferSnapshot) -> text::Anchor {
456        match self {
457            Anchor::Min => text::Anchor::min_for_buffer(buffer.remote_id()),
458            Anchor::Excerpt(excerpt_anchor) => {
459                let text_anchor = excerpt_anchor.text_anchor;
460                assert_eq!(text_anchor.buffer_id, buffer.remote_id());
461                text_anchor
462            }
463            Anchor::Max => text::Anchor::max_for_buffer(buffer.remote_id()),
464        }
465    }
466
467    pub fn diff_base_anchor(&self) -> Option<text::Anchor> {
468        self.excerpt_anchor()?.diff_base_anchor
469    }
470
471    #[cfg(any(test, feature = "test-support"))]
472    pub fn expect_text_anchor(&self) -> text::Anchor {
473        self.excerpt_anchor().unwrap().text_anchor
474    }
475
476    pub fn with_diff_base_anchor(mut self, diff_base_anchor: text::Anchor) -> Self {
477        match &mut self {
478            Anchor::Min | Anchor::Max => {}
479            Anchor::Excerpt(excerpt_anchor) => {
480                excerpt_anchor.diff_base_anchor = Some(diff_base_anchor);
481            }
482        }
483        self
484    }
485}
486
487impl ToOffset for Anchor {
488    fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffset {
489        self.summary(snapshot)
490    }
491    fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffsetUtf16 {
492        self.summary(snapshot)
493    }
494}
495
496impl ToPoint for Anchor {
497    fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
498        self.summary(snapshot)
499    }
500    fn to_point_utf16(&self, snapshot: &MultiBufferSnapshot) -> rope::PointUtf16 {
501        self.summary(snapshot)
502    }
503}
504
505pub trait AnchorRangeExt {
506    fn cmp(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering;
507    fn includes(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool;
508    fn overlaps(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool;
509    fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<MultiBufferOffset>;
510    fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point>;
511}
512
513impl AnchorRangeExt for Range<Anchor> {
514    fn cmp(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering {
515        match self.start.cmp(&other.start, buffer) {
516            Ordering::Equal => other.end.cmp(&self.end, buffer),
517            ord => ord,
518        }
519    }
520
521    fn includes(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool {
522        self.start.cmp(&other.start, buffer).is_le() && other.end.cmp(&self.end, buffer).is_le()
523    }
524
525    fn overlaps(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool {
526        self.end.cmp(&other.start, buffer).is_ge() && self.start.cmp(&other.end, buffer).is_le()
527    }
528
529    fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<MultiBufferOffset> {
530        self.start.to_offset(content)..self.end.to_offset(content)
531    }
532
533    fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point> {
534        self.start.to_point(content)..self.end.to_point(content)
535    }
536}
537
Served at tenant.openagents/omega Member data and write actions are omitted.