Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:33:21.442Z 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

inlay_map.rs

2575 lines · 96.4 KB · rust
1//! The inlay map. See the [`display_map`][super] docs for an overview of how the inlay map fits
2//! into the rest of the [`DisplayMap`][super::DisplayMap]. Much of the documentation for this
3//! module generalizes to other layers.
4//!
5//! The core of this module is the [`InlayMap`] struct, which maintains a vec of [`Inlay`]s, and
6//! [`InlaySnapshot`], which holds a sum tree of [`Transform`]s.
7
8use crate::{
9    ChunkRenderer, HighlightStyles,
10    inlays::{Inlay, InlayContent},
11};
12use collections::BTreeSet;
13use language::{Chunk, Edit, LanguageAwareStyling, Point, TextSummary};
14use multi_buffer::{
15    MBTextSummary, MultiBufferOffset, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot,
16    RowInfo, ToOffset,
17};
18use project::InlayId;
19use smallvec::SmallVec;
20use std::{
21    cmp, iter,
22    ops::{Add, AddAssign, Range, Sub, SubAssign},
23    sync::Arc,
24};
25use sum_tree::{Bias, Cursor, Dimensions, SumTree};
26use text::{ChunkBitmaps, Patch};
27use ui::{ActiveTheme, IntoElement as _, ParentElement as _, Styled as _, div};
28
29use super::{Highlights, custom_highlights::CustomHighlightsChunks, fold_map::ChunkRendererId};
30
31/// Decides where the [`Inlay`]s should be displayed.
32///
33/// See the [`display_map` module documentation](crate::display_map) for more information.
34pub struct InlayMap {
35    snapshot: InlaySnapshot,
36    inlays: Vec<Inlay>,
37}
38
39#[derive(Clone)]
40pub struct InlaySnapshot {
41    pub buffer: MultiBufferSnapshot,
42    transforms: SumTree<Transform>,
43    pub version: usize,
44}
45
46impl std::ops::Deref for InlaySnapshot {
47    type Target = MultiBufferSnapshot;
48
49    fn deref(&self) -> &Self::Target {
50        &self.buffer
51    }
52}
53
54#[derive(Clone, Debug)]
55enum Transform {
56    Isomorphic(MBTextSummary),
57    Inlay(Inlay),
58}
59
60impl sum_tree::Item for Transform {
61    type Summary = TransformSummary;
62
63    #[ztracing::instrument(skip_all)]
64    fn summary(&self, _: ()) -> Self::Summary {
65        match self {
66            Transform::Isomorphic(summary) => TransformSummary {
67                input: *summary,
68                output: *summary,
69            },
70            Transform::Inlay(inlay) => TransformSummary {
71                input: MBTextSummary::default(),
72                output: MBTextSummary::from(inlay.text().summary()),
73            },
74        }
75    }
76}
77
78#[derive(Clone, Debug, Default)]
79struct TransformSummary {
80    /// Summary of the text before inlays have been applied.
81    input: MBTextSummary,
82    /// Summary of the text after inlays have been applied.
83    output: MBTextSummary,
84}
85
86impl TransformSummary {
87    fn has_inlays(&self) -> bool {
88        self.input.len != self.output.len
89    }
90}
91
92impl sum_tree::ContextLessSummary for TransformSummary {
93    fn zero() -> Self {
94        Default::default()
95    }
96
97    fn add_summary(&mut self, other: &Self) {
98        self.input += other.input;
99        self.output += other.output;
100    }
101}
102
103pub type InlayEdit = Edit<InlayOffset>;
104
105#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
106pub struct InlayOffset(pub MultiBufferOffset);
107
108impl Add for InlayOffset {
109    type Output = Self;
110
111    fn add(self, rhs: Self) -> Self::Output {
112        Self(self.0 + rhs.0)
113    }
114}
115
116impl Sub for InlayOffset {
117    type Output = <MultiBufferOffset as Sub>::Output;
118
119    fn sub(self, rhs: Self) -> Self::Output {
120        self.0 - rhs.0
121    }
122}
123
124impl<T> SubAssign<T> for InlayOffset
125where
126    MultiBufferOffset: SubAssign<T>,
127{
128    fn sub_assign(&mut self, rhs: T) {
129        self.0 -= rhs;
130    }
131}
132
133impl<T> Add<T> for InlayOffset
134where
135    MultiBufferOffset: Add<T, Output = MultiBufferOffset>,
136{
137    type Output = Self;
138
139    fn add(self, rhs: T) -> Self::Output {
140        Self(self.0 + rhs)
141    }
142}
143
144impl AddAssign for InlayOffset {
145    fn add_assign(&mut self, rhs: Self) {
146        self.0 += rhs.0;
147    }
148}
149
150impl<T> AddAssign<T> for InlayOffset
151where
152    MultiBufferOffset: AddAssign<T>,
153{
154    fn add_assign(&mut self, rhs: T) {
155        self.0 += rhs;
156    }
157}
158
159impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
160    fn zero(_cx: ()) -> Self {
161        Default::default()
162    }
163
164    fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
165        self.0 += summary.output.len;
166    }
167}
168
169#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
170pub struct InlayPoint(pub Point);
171
172impl Add for InlayPoint {
173    type Output = Self;
174
175    fn add(self, rhs: Self) -> Self::Output {
176        Self(self.0 + rhs.0)
177    }
178}
179
180impl Sub for InlayPoint {
181    type Output = Self;
182
183    fn sub(self, rhs: Self) -> Self::Output {
184        Self(self.0 - rhs.0)
185    }
186}
187
188impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
189    fn zero(_cx: ()) -> Self {
190        Default::default()
191    }
192
193    fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
194        self.0 += &summary.output.lines;
195    }
196}
197
198impl<'a> sum_tree::Dimension<'a, TransformSummary> for MultiBufferOffset {
199    fn zero(_cx: ()) -> Self {
200        Default::default()
201    }
202
203    fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
204        *self += summary.input.len;
205    }
206}
207
208impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
209    fn zero(_cx: ()) -> Self {
210        Default::default()
211    }
212
213    fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
214        *self += &summary.input.lines;
215    }
216}
217
218#[derive(Clone)]
219pub struct InlayBufferRows<'a> {
220    transforms: Cursor<'a, 'static, Transform, Dimensions<InlayPoint, Point>>,
221    buffer_rows: MultiBufferRows<'a>,
222    inlay_row: u32,
223    max_buffer_row: MultiBufferRow,
224}
225
226pub struct InlayChunks<'a> {
227    transforms: Cursor<'a, 'static, Transform, Dimensions<InlayOffset, MultiBufferOffset>>,
228    buffer_chunks: CustomHighlightsChunks<'a>,
229    buffer_chunk: Option<Chunk<'a>>,
230    inlay_chunks: Option<text::ChunkWithBitmaps<'a>>,
231    /// text, char bitmap, tabs bitmap
232    inlay_chunk: Option<ChunkBitmaps<'a>>,
233    output_offset: InlayOffset,
234    max_output_offset: InlayOffset,
235    highlight_styles: HighlightStyles,
236    highlights: Highlights<'a>,
237    snapshot: &'a InlaySnapshot,
238}
239
240#[derive(Clone)]
241pub struct InlayChunk<'a> {
242    pub chunk: Chunk<'a>,
243    /// Whether the inlay should be customly rendered.
244    pub renderer: Option<ChunkRenderer>,
245}
246
247impl InlayChunks<'_> {
248    #[ztracing::instrument(skip_all)]
249    pub fn seek(&mut self, new_range: Range<InlayOffset>) {
250        self.transforms.seek(&new_range.start, Bias::Right);
251
252        let buffer_range = self.snapshot.to_buffer_offset(new_range.start)
253            ..self.snapshot.to_buffer_offset(new_range.end);
254        self.buffer_chunks.seek(buffer_range);
255        self.inlay_chunks = None;
256        self.buffer_chunk = None;
257        self.output_offset = new_range.start;
258        self.max_output_offset = new_range.end;
259    }
260
261    pub fn offset(&self) -> InlayOffset {
262        self.output_offset
263    }
264}
265
266impl<'a> Iterator for InlayChunks<'a> {
267    type Item = InlayChunk<'a>;
268
269    #[ztracing::instrument(skip_all)]
270    fn next(&mut self) -> Option<Self::Item> {
271        if self.output_offset == self.max_output_offset {
272            return None;
273        }
274
275        let chunk = match self.transforms.item()? {
276            Transform::Isomorphic(_) => {
277                let chunk = self
278                    .buffer_chunk
279                    .get_or_insert_with(|| self.buffer_chunks.next().unwrap());
280                if chunk.text.is_empty() {
281                    *chunk = self.buffer_chunks.next().unwrap();
282                }
283
284                let desired_bytes = self.transforms.end().0.0 - self.output_offset.0;
285
286                // If we're already at the transform boundary, skip to the next transform
287                if desired_bytes == 0 {
288                    self.inlay_chunks = None;
289                    self.transforms.next();
290                    return self.next();
291                }
292
293                // Determine split index handling edge cases
294                let split_index = if desired_bytes >= chunk.text.len() {
295                    chunk.text.len()
296                } else {
297                    chunk.text.ceil_char_boundary(desired_bytes)
298                };
299
300                let (prefix, suffix) = chunk.text.split_at(split_index);
301                self.output_offset.0 += prefix.len();
302
303                let mask = 1u128.unbounded_shl(split_index as u32).wrapping_sub(1);
304                let chars = chunk.chars & mask;
305                let tabs = chunk.tabs & mask;
306                let newlines = chunk.newlines & mask;
307
308                chunk.chars = chunk.chars.unbounded_shr(split_index as u32);
309                chunk.tabs = chunk.tabs.unbounded_shr(split_index as u32);
310                chunk.newlines = chunk.newlines.unbounded_shr(split_index as u32);
311                chunk.text = suffix;
312
313                InlayChunk {
314                    chunk: Chunk {
315                        text: prefix,
316                        chars,
317                        tabs,
318                        newlines,
319                        ..chunk.clone()
320                    },
321                    renderer: None,
322                }
323            }
324            Transform::Inlay(inlay) => {
325                let mut inlay_style_and_highlight = None;
326                if let Some(inlay_highlights) = self.highlights.inlay_highlights {
327                    for (_, inlay_id_to_data) in inlay_highlights.iter() {
328                        let style_and_highlight = inlay_id_to_data.get(&inlay.id);
329                        if style_and_highlight.is_some() {
330                            inlay_style_and_highlight = style_and_highlight;
331                            break;
332                        }
333                    }
334                }
335
336                let mut renderer = None;
337                let mut highlight_style = match inlay.id {
338                    InlayId::EditPrediction(_) => self.highlight_styles.edit_prediction.map(|s| {
339                        if inlay.text().chars().all(|c| c.is_whitespace()) {
340                            s.whitespace
341                        } else {
342                            s.insertion
343                        }
344                    }),
345                    InlayId::Hint(_) => self.highlight_styles.inlay_hint,
346                    InlayId::DebuggerValue(_) => self.highlight_styles.inlay_hint,
347                    InlayId::ReplResult(_) => {
348                        let text = inlay.text().to_string();
349                        renderer = Some(ChunkRenderer {
350                            id: ChunkRendererId::Inlay(inlay.id),
351                            render: Arc::new(move |cx| {
352                                let colors = cx.theme().colors();
353                                div()
354                                    .flex()
355                                    .flex_row()
356                                    .items_center()
357                                    .child(div().w_4())
358                                    .child(
359                                        div()
360                                            .px_1()
361                                            .rounded_sm()
362                                            .bg(colors.surface_background)
363                                            .text_color(colors.text_muted)
364                                            .text_xs()
365                                            .child(text.trim().to_string()),
366                                    )
367                                    .into_any_element()
368                            }),
369                            constrain_width: false,
370                            measured_width: None,
371                        });
372                        self.highlight_styles.inlay_hint
373                    }
374                    InlayId::Color(_) => {
375                        if let InlayContent::Color(color) = inlay.content {
376                            renderer = Some(ChunkRenderer {
377                                id: ChunkRendererId::Inlay(inlay.id),
378                                render: Arc::new(move |cx| {
379                                    div()
380                                        .relative()
381                                        .size_3p5()
382                                        .child(
383                                            div()
384                                                .absolute()
385                                                .right_1()
386                                                .size_3()
387                                                .border_1()
388                                                .border_color(
389                                                    if cx.theme().appearance().is_light() {
390                                                        gpui::black().opacity(0.5)
391                                                    } else {
392                                                        gpui::white().opacity(0.5)
393                                                    },
394                                                )
395                                                .bg(color),
396                                        )
397                                        .into_any_element()
398                                }),
399                                constrain_width: false,
400                                measured_width: None,
401                            });
402                        }
403                        self.highlight_styles.inlay_hint
404                    }
405                };
406                let next_inlay_highlight_endpoint;
407                let offset_in_inlay = self.output_offset - self.transforms.start().0;
408                if let Some((style, highlight)) = inlay_style_and_highlight {
409                    let range = &highlight.range;
410                    if offset_in_inlay < range.start {
411                        next_inlay_highlight_endpoint = range.start - offset_in_inlay;
412                    } else if offset_in_inlay >= range.end {
413                        next_inlay_highlight_endpoint = usize::MAX;
414                    } else {
415                        next_inlay_highlight_endpoint = range.end - offset_in_inlay;
416                        highlight_style = highlight_style
417                            .map(|highlight| highlight.highlight(*style))
418                            .or_else(|| Some(*style));
419                    }
420                } else {
421                    next_inlay_highlight_endpoint = usize::MAX;
422                }
423
424                let inlay_chunks = self.inlay_chunks.get_or_insert_with(|| {
425                    let start = offset_in_inlay;
426                    let end = cmp::min(self.max_output_offset, self.transforms.end().0)
427                        - self.transforms.start().0;
428                    let chunks = inlay.text().chunks_in_range(start..end);
429                    text::ChunkWithBitmaps(chunks)
430                });
431                let ChunkBitmaps {
432                    text: inlay_chunk,
433                    chars,
434                    tabs,
435                    newlines,
436                } = self
437                    .inlay_chunk
438                    .get_or_insert_with(|| inlay_chunks.next().unwrap());
439
440                // Determine split index handling edge cases
441                let split_index = if next_inlay_highlight_endpoint >= inlay_chunk.len() {
442                    inlay_chunk.len()
443                } else if next_inlay_highlight_endpoint == 0 {
444                    // Need to take at least one character to make progress
445                    inlay_chunk
446                        .chars()
447                        .next()
448                        .map(|c| c.len_utf8())
449                        .unwrap_or(1)
450                } else {
451                    inlay_chunk.ceil_char_boundary(next_inlay_highlight_endpoint)
452                };
453
454                let (chunk, remainder) = inlay_chunk.split_at(split_index);
455                *inlay_chunk = remainder;
456
457                let mask = 1u128.unbounded_shl(split_index as u32).wrapping_sub(1);
458                let new_chars = *chars & mask;
459                let new_tabs = *tabs & mask;
460                let new_newlines = *newlines & mask;
461
462                *chars = chars.unbounded_shr(split_index as u32);
463                *tabs = tabs.unbounded_shr(split_index as u32);
464                *newlines = newlines.unbounded_shr(split_index as u32);
465
466                if inlay_chunk.is_empty() {
467                    self.inlay_chunk = None;
468                }
469
470                self.output_offset.0 += chunk.len();
471
472                InlayChunk {
473                    chunk: Chunk {
474                        text: chunk,
475                        chars: new_chars,
476                        tabs: new_tabs,
477                        newlines: new_newlines,
478                        highlight_style,
479                        is_inlay: true,
480                        ..Chunk::default()
481                    },
482                    renderer,
483                }
484            }
485        };
486
487        if self.output_offset >= self.transforms.end().0 {
488            self.inlay_chunks = None;
489            self.transforms.next();
490        }
491
492        Some(chunk)
493    }
494}
495
496impl InlayBufferRows<'_> {
497    #[ztracing::instrument(skip_all)]
498    pub fn seek(&mut self, row: u32) {
499        let inlay_point = InlayPoint::new(row, 0);
500        self.transforms.seek(&inlay_point, Bias::Left);
501
502        let mut buffer_point = self.transforms.start().1;
503        let buffer_row = MultiBufferRow(if row == 0 {
504            0
505        } else {
506            match self.transforms.item() {
507                Some(Transform::Isomorphic(_)) => {
508                    buffer_point += inlay_point.0 - self.transforms.start().0.0;
509                    buffer_point.row
510                }
511                _ => cmp::min(buffer_point.row + 1, self.max_buffer_row.0),
512            }
513        });
514        self.inlay_row = inlay_point.row();
515        self.buffer_rows.seek(buffer_row);
516    }
517}
518
519impl Iterator for InlayBufferRows<'_> {
520    type Item = RowInfo;
521
522    #[ztracing::instrument(skip_all)]
523    fn next(&mut self) -> Option<Self::Item> {
524        let buffer_row = if self.inlay_row == 0 {
525            self.buffer_rows.next().unwrap()
526        } else {
527            match self.transforms.item()? {
528                Transform::Inlay(_) => Default::default(),
529                Transform::Isomorphic(_) => self.buffer_rows.next().unwrap(),
530            }
531        };
532
533        self.inlay_row += 1;
534        self.transforms
535            .seek_forward(&InlayPoint::new(self.inlay_row, 0), Bias::Left);
536
537        Some(buffer_row)
538    }
539}
540
541impl InlayPoint {
542    pub fn new(row: u32, column: u32) -> Self {
543        Self(Point::new(row, column))
544    }
545
546    pub fn row(self) -> u32 {
547        self.0.row
548    }
549}
550
551impl InlayMap {
552    #[ztracing::instrument(skip_all)]
553    pub fn new(buffer: MultiBufferSnapshot) -> (Self, InlaySnapshot) {
554        let version = 0;
555        let snapshot = InlaySnapshot {
556            transforms: SumTree::from_iter(
557                iter::once(Transform::Isomorphic(buffer.text_summary())),
558                (),
559            ),
560            buffer,
561            version,
562        };
563
564        (
565            Self {
566                snapshot: snapshot.clone(),
567                inlays: Vec::new(),
568            },
569            snapshot,
570        )
571    }
572
573    #[ztracing::instrument(skip_all)]
574    pub fn sync(
575        &mut self,
576        buffer_snapshot: MultiBufferSnapshot,
577        mut buffer_edits: Vec<text::Edit<MultiBufferOffset>>,
578    ) -> (InlaySnapshot, Vec<InlayEdit>) {
579        let snapshot = &mut self.snapshot;
580
581        if buffer_edits.is_empty()
582            && snapshot.buffer.trailing_excerpt_update_count()
583                != buffer_snapshot.trailing_excerpt_update_count()
584        {
585            buffer_edits.push(Edit {
586                old: snapshot.buffer.len()..snapshot.buffer.len(),
587                new: buffer_snapshot.len()..buffer_snapshot.len(),
588            });
589        }
590
591        if buffer_edits.is_empty() {
592            if snapshot.buffer.edit_count() != buffer_snapshot.edit_count()
593                || snapshot.buffer.non_text_state_update_count()
594                    != buffer_snapshot.non_text_state_update_count()
595                || snapshot.buffer.trailing_excerpt_update_count()
596                    != buffer_snapshot.trailing_excerpt_update_count()
597            {
598                snapshot.version += 1;
599            }
600
601            snapshot.buffer = buffer_snapshot;
602            (snapshot.clone(), Vec::new())
603        } else if self.inlays.is_empty() && !snapshot.transforms.summary().has_inlays() {
604            // Fast path: without inlays, the InlayMap is a passthrough, so rebuild a single
605            // isomorphic transform and forward buffer edits as inlay edits verbatim.
606            let mut new_transforms = SumTree::default();
607            push_isomorphic(&mut new_transforms, buffer_snapshot.text_summary());
608            if new_transforms.is_empty() {
609                new_transforms.push(Transform::Isomorphic(Default::default()), ());
610            }
611
612            let mut inlay_edits = Patch::default();
613            for buffer_edit in &buffer_edits {
614                inlay_edits.push(Edit {
615                    old: InlayOffset(buffer_edit.old.start)..InlayOffset(buffer_edit.old.end),
616                    new: InlayOffset(buffer_edit.new.start)..InlayOffset(buffer_edit.new.end),
617                });
618            }
619
620            snapshot.transforms = new_transforms;
621            snapshot.version += 1;
622            snapshot.buffer = buffer_snapshot;
623            snapshot.check_invariants();
624
625            (snapshot.clone(), inlay_edits.into_inner())
626        } else {
627            let mut inlay_edits = Patch::default();
628            let mut new_transforms = SumTree::default();
629            let mut cursor = snapshot
630                .transforms
631                .cursor::<Dimensions<MultiBufferOffset, InlayOffset>>(());
632            let mut buffer_edits_iter = buffer_edits.iter().peekable();
633            while let Some(buffer_edit) = buffer_edits_iter.next() {
634                new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left), ());
635                if let Some(Transform::Isomorphic(transform)) = cursor.item()
636                    && cursor.end().0 == buffer_edit.old.start
637                {
638                    push_isomorphic(&mut new_transforms, *transform);
639                    cursor.next();
640                }
641
642                // Remove all the inlays and transforms contained by the edit.
643                let old_start = cursor.start().1 + (buffer_edit.old.start - cursor.start().0);
644                cursor.seek(&buffer_edit.old.end, Bias::Right);
645                let old_end = cursor.start().1 + (buffer_edit.old.end - cursor.start().0);
646
647                // Push the unchanged prefix.
648                let prefix_start = new_transforms.summary().input.len;
649                let prefix_end = buffer_edit.new.start;
650                push_isomorphic(
651                    &mut new_transforms,
652                    buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
653                );
654                let new_start = InlayOffset(new_transforms.summary().output.len);
655
656                let start_ix = match self.inlays.binary_search_by(|probe| {
657                    probe
658                        .position
659                        .to_offset(&buffer_snapshot)
660                        .cmp(&buffer_edit.new.start)
661                        .then(std::cmp::Ordering::Greater)
662                }) {
663                    Ok(ix) | Err(ix) => ix,
664                };
665
666                for inlay in &self.inlays[start_ix..] {
667                    if !inlay.position.is_valid(&buffer_snapshot) {
668                        continue;
669                    }
670                    let buffer_offset = inlay.position.to_offset(&buffer_snapshot);
671                    if buffer_offset > buffer_edit.new.end {
672                        break;
673                    }
674
675                    let prefix_start = new_transforms.summary().input.len;
676                    let prefix_end = buffer_offset;
677                    push_isomorphic(
678                        &mut new_transforms,
679                        buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
680                    );
681
682                    new_transforms.push(Transform::Inlay(inlay.clone()), ());
683                }
684
685                // Apply the rest of the edit.
686                let transform_start = new_transforms.summary().input.len;
687                push_isomorphic(
688                    &mut new_transforms,
689                    buffer_snapshot.text_summary_for_range(transform_start..buffer_edit.new.end),
690                );
691                let new_end = InlayOffset(new_transforms.summary().output.len);
692                inlay_edits.push(Edit {
693                    old: old_start..old_end,
694                    new: new_start..new_end,
695                });
696
697                // If the next edit doesn't intersect the current isomorphic transform, then
698                // we can push its remainder.
699                if buffer_edits_iter
700                    .peek()
701                    .is_none_or(|edit| edit.old.start >= cursor.end().0)
702                {
703                    let transform_start = new_transforms.summary().input.len;
704                    let transform_end =
705                        buffer_edit.new.end + (cursor.end().0 - buffer_edit.old.end);
706                    push_isomorphic(
707                        &mut new_transforms,
708                        buffer_snapshot.text_summary_for_range(transform_start..transform_end),
709                    );
710                    cursor.next();
711                }
712            }
713
714            new_transforms.append(cursor.suffix(), ());
715            if new_transforms.is_empty() {
716                new_transforms.push(Transform::Isomorphic(Default::default()), ());
717            }
718
719            drop(cursor);
720            snapshot.transforms = new_transforms;
721            snapshot.version += 1;
722            snapshot.buffer = buffer_snapshot;
723            snapshot.check_invariants();
724
725            (snapshot.clone(), inlay_edits.into_inner())
726        }
727    }
728
729    #[ztracing::instrument(skip_all)]
730    pub fn splice(
731        &mut self,
732        to_remove: &[InlayId],
733        to_insert: Vec<Inlay>,
734    ) -> (InlaySnapshot, Vec<InlayEdit>) {
735        let snapshot = &mut self.snapshot;
736        let mut edits = BTreeSet::new();
737
738        self.inlays.retain(|inlay| {
739            let retain = !to_remove.contains(&inlay.id);
740            if !retain {
741                let offset = inlay.position.to_offset(&snapshot.buffer);
742                edits.insert(offset);
743            }
744            retain
745        });
746
747        for inlay_to_insert in to_insert {
748            // Avoid inserting empty inlays.
749            if inlay_to_insert.text().is_empty() {
750                continue;
751            }
752
753            let offset = inlay_to_insert.position.to_offset(&snapshot.buffer);
754            match self.inlays.binary_search_by(|probe| {
755                probe
756                    .position
757                    .cmp(&inlay_to_insert.position, &snapshot.buffer)
758                    .then(std::cmp::Ordering::Less)
759            }) {
760                Ok(ix) | Err(ix) => {
761                    self.inlays.insert(ix, inlay_to_insert);
762                }
763            }
764
765            edits.insert(offset);
766        }
767
768        let buffer_edits = edits
769            .into_iter()
770            .map(|offset| Edit {
771                old: offset..offset,
772                new: offset..offset,
773            })
774            .collect();
775        let buffer_snapshot = snapshot.buffer.clone();
776        let (snapshot, edits) = self.sync(buffer_snapshot, buffer_edits);
777        (snapshot, edits)
778    }
779
780    #[ztracing::instrument(skip_all)]
781    pub fn current_inlays(&self) -> impl Iterator<Item = &Inlay> + Default {
782        self.inlays.iter()
783    }
784
785    #[cfg(test)]
786    #[ztracing::instrument(skip_all)]
787    pub(crate) fn randomly_mutate(
788        &mut self,
789        next_inlay_id: &mut usize,
790        rng: &mut rand::rngs::StdRng,
791    ) -> (InlaySnapshot, Vec<InlayEdit>) {
792        use rand::prelude::*;
793        use util::post_inc;
794
795        let mut to_remove = Vec::new();
796        let mut to_insert = Vec::new();
797        let snapshot = &mut self.snapshot;
798        for i in 0..rng.random_range(1..=5) {
799            if self.inlays.is_empty() || rng.random() {
800                let position = snapshot
801                    .buffer
802                    .random_byte_range(MultiBufferOffset(0), rng)
803                    .start;
804                let bias = if rng.random() {
805                    Bias::Left
806                } else {
807                    Bias::Right
808                };
809                let len = if rng.random_bool(0.01) {
810                    0
811                } else {
812                    rng.random_range(1..=5)
813                };
814                let text = util::RandomCharIter::new(&mut *rng)
815                    .filter(|ch| *ch != '\r')
816                    .take(len)
817                    .collect::<String>();
818
819                let next_inlay = if i % 2 == 0 {
820                    Inlay::mock_hint(
821                        post_inc(next_inlay_id),
822                        snapshot.buffer.anchor_at(position, bias),
823                        &text,
824                    )
825                } else {
826                    Inlay::edit_prediction(
827                        post_inc(next_inlay_id),
828                        snapshot.buffer.anchor_at(position, bias),
829                        &text,
830                    )
831                };
832                let inlay_id = next_inlay.id;
833                log::info!(
834                    "creating inlay {inlay_id:?} at buffer offset {position} with bias {bias:?} and text {text:?}"
835                );
836                to_insert.push(next_inlay);
837            } else {
838                to_remove.push(
839                    self.inlays
840                        .iter()
841                        .choose(rng)
842                        .map(|inlay| inlay.id)
843                        .unwrap(),
844                );
845            }
846        }
847        log::info!("removing inlays: {:?}", to_remove);
848
849        let (snapshot, edits) = self.splice(&to_remove, to_insert);
850        (snapshot, edits)
851    }
852}
853
854impl InlaySnapshot {
855    #[ztracing::instrument(skip_all)]
856    pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
857        let (start, _, item) = self.transforms.find::<Dimensions<
858            InlayOffset,
859            InlayPoint,
860            MultiBufferOffset,
861        >, _>((), &offset, Bias::Right);
862        let overshoot = offset.0 - start.0.0;
863        match item {
864            Some(Transform::Isomorphic(_)) => {
865                let buffer_offset_start = start.2;
866                let buffer_offset_end = buffer_offset_start + overshoot;
867                let buffer_start = self.buffer.offset_to_point(buffer_offset_start);
868                let buffer_end = self.buffer.offset_to_point(buffer_offset_end);
869                InlayPoint(start.1.0 + (buffer_end - buffer_start))
870            }
871            Some(Transform::Inlay(inlay)) => {
872                let overshoot = inlay.text().offset_to_point(overshoot);
873                InlayPoint(start.1.0 + overshoot)
874            }
875            None => self.max_point(),
876        }
877    }
878
879    #[ztracing::instrument(skip_all)]
880    pub fn len(&self) -> InlayOffset {
881        InlayOffset(self.transforms.summary().output.len)
882    }
883
884    #[ztracing::instrument(skip_all)]
885    pub fn max_point(&self) -> InlayPoint {
886        InlayPoint(self.transforms.summary().output.lines)
887    }
888
889    #[ztracing::instrument(skip_all, fields(point))]
890    pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
891        let (start, _, item) = self
892            .transforms
893            .find::<Dimensions<InlayPoint, InlayOffset, Point>, _>((), &point, Bias::Right);
894        let overshoot = point.0 - start.0.0;
895        match item {
896            Some(Transform::Isomorphic(_)) => {
897                let buffer_point_start = start.2;
898                let buffer_point_end = buffer_point_start + overshoot;
899                let buffer_offset_start = self.buffer.point_to_offset(buffer_point_start);
900                let buffer_offset_end = self.buffer.point_to_offset(buffer_point_end);
901                InlayOffset(start.1.0 + (buffer_offset_end - buffer_offset_start))
902            }
903            Some(Transform::Inlay(inlay)) => {
904                let overshoot = inlay.text().point_to_offset(overshoot);
905                InlayOffset(start.1.0 + overshoot)
906            }
907            None => self.len(),
908        }
909    }
910    #[ztracing::instrument(skip_all)]
911    pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
912        let (start, _, item) =
913            self.transforms
914                .find::<Dimensions<InlayPoint, Point>, _>((), &point, Bias::Right);
915        match item {
916            Some(Transform::Isomorphic(_)) => {
917                let overshoot = point.0 - start.0.0;
918                start.1 + overshoot
919            }
920            Some(Transform::Inlay(_)) => start.1,
921            None => self.buffer.max_point(),
922        }
923    }
924    #[ztracing::instrument(skip_all)]
925    pub fn to_buffer_offset(&self, offset: InlayOffset) -> MultiBufferOffset {
926        let (start, _, item) = self
927            .transforms
928            .find::<Dimensions<InlayOffset, MultiBufferOffset>, _>((), &offset, Bias::Right);
929        match item {
930            Some(Transform::Isomorphic(_)) => {
931                let overshoot = offset - start.0;
932                start.1 + overshoot
933            }
934            Some(Transform::Inlay(_)) => start.1,
935            None => self.buffer.len(),
936        }
937    }
938
939    #[ztracing::instrument(skip_all)]
940    pub fn to_inlay_offset(&self, offset: MultiBufferOffset) -> InlayOffset {
941        let mut cursor = self
942            .transforms
943            .cursor::<Dimensions<MultiBufferOffset, InlayOffset>>(());
944        cursor.seek(&offset, Bias::Left);
945        loop {
946            match cursor.item() {
947                Some(Transform::Isomorphic(_)) => {
948                    if offset == cursor.end().0 {
949                        while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
950                            if inlay.position.bias() == Bias::Right {
951                                break;
952                            } else {
953                                cursor.next();
954                            }
955                        }
956                        return cursor.end().1;
957                    } else {
958                        let overshoot = offset - cursor.start().0;
959                        return InlayOffset(cursor.start().1.0 + overshoot);
960                    }
961                }
962                Some(Transform::Inlay(inlay)) => {
963                    if inlay.position.bias() == Bias::Left {
964                        cursor.next();
965                    } else {
966                        return cursor.start().1;
967                    }
968                }
969                None => {
970                    return self.len();
971                }
972            }
973        }
974    }
975
976    #[ztracing::instrument(skip_all)]
977    pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
978        self.inlay_point_cursor().map(point, Bias::Left)
979    }
980
981    /// Converts a buffer offset range into one or more `InlayOffset` ranges that
982    /// cover only the actual buffer text, skipping any inlay hint text that falls
983    /// within the range. When there are no inlays the returned vec contains a
984    /// single element identical to the input mapped into inlay-offset space.
985    pub fn buffer_offset_to_inlay_ranges(
986        &self,
987        range: Range<MultiBufferOffset>,
988    ) -> impl Iterator<Item = Range<InlayOffset>> {
989        let mut cursor = self
990            .transforms
991            .cursor::<Dimensions<MultiBufferOffset, InlayOffset>>(());
992        cursor.seek(&range.start, Bias::Right);
993
994        std::iter::from_fn(move || {
995            loop {
996                match cursor.item()? {
997                    Transform::Isomorphic(_) => {
998                        let seg_buffer_start = cursor.start().0;
999                        let seg_buffer_end = cursor.end().0;
1000                        let seg_inlay_start = cursor.start().1;
1001
1002                        let overlap_start = cmp::max(range.start, seg_buffer_start);
1003                        let overlap_end = cmp::min(range.end, seg_buffer_end);
1004
1005                        let past_end = seg_buffer_end >= range.end;
1006                        cursor.next();
1007
1008                        if overlap_start < overlap_end {
1009                            let inlay_start =
1010                                InlayOffset(seg_inlay_start.0 + (overlap_start - seg_buffer_start));
1011                            let inlay_end =
1012                                InlayOffset(seg_inlay_start.0 + (overlap_end - seg_buffer_start));
1013                            return Some(inlay_start..inlay_end);
1014                        }
1015
1016                        if past_end {
1017                            return None;
1018                        }
1019                    }
1020                    Transform::Inlay(_) => cursor.next(),
1021                }
1022            }
1023        })
1024    }
1025
1026    pub fn buffer_offset_to_inlay_point_cursor(&self) -> BufferOffsetToInlayPointCursor<'_> {
1027        BufferOffsetToInlayPointCursor {
1028            snapshot: self,
1029            cursor: self
1030                .transforms
1031                .cursor::<Dimensions<MultiBufferOffset, InlayPoint>>(()),
1032        }
1033    }
1034
1035    #[ztracing::instrument(skip_all)]
1036    pub fn inlay_point_cursor(&self) -> InlayPointCursor<'_> {
1037        let cursor = self.transforms.cursor::<Dimensions<Point, InlayPoint>>(());
1038        InlayPointCursor {
1039            cursor,
1040            transforms: &self.transforms,
1041        }
1042    }
1043
1044    #[ztracing::instrument(skip_all)]
1045    pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint {
1046        let mut cursor = self.transforms.cursor::<Dimensions<InlayPoint, Point>>(());
1047        cursor.seek(&point, Bias::Left);
1048        loop {
1049            match cursor.item() {
1050                Some(Transform::Isomorphic(transform)) => {
1051                    if cursor.start().0 == point {
1052                        if let Some(Transform::Inlay(inlay)) = cursor.prev_item() {
1053                            if inlay.position.bias() == Bias::Left {
1054                                return point;
1055                            } else if bias == Bias::Left {
1056                                cursor.prev();
1057                            } else if transform.first_line_chars == 0 {
1058                                point.0 += Point::new(1, 0);
1059                            } else {
1060                                point.0 += Point::new(0, 1);
1061                            }
1062                        } else {
1063                            return point;
1064                        }
1065                    } else if cursor.end().0 == point {
1066                        if let Some(Transform::Inlay(inlay)) = cursor.next_item() {
1067                            if inlay.position.bias() == Bias::Right {
1068                                return point;
1069                            } else if bias == Bias::Right {
1070                                cursor.next();
1071                            } else if point.0.column == 0 {
1072                                point.0.row -= 1;
1073                                point.0.column = self.line_len(point.0.row);
1074                            } else {
1075                                point.0.column -= 1;
1076                            }
1077                        } else {
1078                            return point;
1079                        }
1080                    } else {
1081                        let overshoot = point.0 - cursor.start().0.0;
1082                        let buffer_point = cursor.start().1 + overshoot;
1083                        let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias);
1084                        let clipped_overshoot = clipped_buffer_point - cursor.start().1;
1085                        let clipped_point = InlayPoint(cursor.start().0.0 + clipped_overshoot);
1086                        if clipped_point == point {
1087                            return clipped_point;
1088                        } else {
1089                            point = clipped_point;
1090                        }
1091                    }
1092                }
1093                Some(Transform::Inlay(inlay)) => {
1094                    if point == cursor.start().0 && inlay.position.bias() == Bias::Right {
1095                        match cursor.prev_item() {
1096                            Some(Transform::Inlay(inlay)) => {
1097                                if inlay.position.bias() == Bias::Left {
1098                                    return point;
1099                                }
1100                            }
1101                            _ => return point,
1102                        }
1103                    } else if point == cursor.end().0 && inlay.position.bias() == Bias::Left {
1104                        match cursor.next_item() {
1105                            Some(Transform::Inlay(inlay)) => {
1106                                if inlay.position.bias() == Bias::Right {
1107                                    return point;
1108                                }
1109                            }
1110                            _ => return point,
1111                        }
1112                    }
1113
1114                    if bias == Bias::Left {
1115                        point = cursor.start().0;
1116                        cursor.prev();
1117                    } else {
1118                        cursor.next();
1119                        point = cursor.start().0;
1120                    }
1121                }
1122                None => {
1123                    bias = bias.invert();
1124                    if bias == Bias::Left {
1125                        point = cursor.start().0;
1126                        cursor.prev();
1127                    } else {
1128                        cursor.next();
1129                        point = cursor.start().0;
1130                    }
1131                }
1132            }
1133        }
1134    }
1135
1136    pub fn inlay_bias_at_point(&self, point: InlayPoint) -> Option<Bias> {
1137        let mut cursor = self.transforms.cursor::<Dimensions<InlayPoint, Point>>(());
1138        cursor.seek(&point, Bias::Left);
1139        match cursor.item() {
1140            Some(Transform::Inlay(inlay)) => Some(inlay.position.bias()),
1141            _ => None,
1142        }
1143    }
1144
1145    #[ztracing::instrument(skip_all)]
1146    pub fn text_summary(&self) -> MBTextSummary {
1147        self.transforms.summary().output
1148    }
1149
1150    #[ztracing::instrument(skip_all)]
1151    pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> MBTextSummary {
1152        let mut summary = MBTextSummary::default();
1153
1154        let mut cursor = self
1155            .transforms
1156            .cursor::<Dimensions<InlayOffset, MultiBufferOffset>>(());
1157        cursor.seek(&range.start, Bias::Right);
1158
1159        let overshoot = range.start.0 - cursor.start().0.0;
1160        match cursor.item() {
1161            Some(Transform::Isomorphic(_)) => {
1162                let buffer_start = cursor.start().1;
1163                let suffix_start = buffer_start + overshoot;
1164                let suffix_end =
1165                    buffer_start + (cmp::min(cursor.end().0, range.end).0 - cursor.start().0.0);
1166                summary = self.buffer.text_summary_for_range(suffix_start..suffix_end);
1167                cursor.next();
1168            }
1169            Some(Transform::Inlay(inlay)) => {
1170                let suffix_start = overshoot;
1171                let suffix_end = cmp::min(cursor.end().0, range.end).0 - cursor.start().0.0;
1172                summary = MBTextSummary::from(
1173                    inlay
1174                        .text()
1175                        .cursor(suffix_start)
1176                        .summary::<TextSummary>(suffix_end),
1177                );
1178                cursor.next();
1179            }
1180            None => {}
1181        }
1182
1183        if range.end > cursor.start().0 {
1184            summary += cursor
1185                .summary::<_, TransformSummary>(&range.end, Bias::Right)
1186                .output;
1187
1188            let overshoot = range.end.0 - cursor.start().0.0;
1189            match cursor.item() {
1190                Some(Transform::Isomorphic(_)) => {
1191                    let prefix_start = cursor.start().1;
1192                    let prefix_end = prefix_start + overshoot;
1193                    summary += self
1194                        .buffer
1195                        .text_summary_for_range::<MBTextSummary, _>(prefix_start..prefix_end);
1196                }
1197                Some(Transform::Inlay(inlay)) => {
1198                    let prefix_end = overshoot;
1199                    summary += inlay.text().cursor(0).summary::<TextSummary>(prefix_end);
1200                }
1201                None => {}
1202            }
1203        }
1204
1205        summary
1206    }
1207
1208    #[ztracing::instrument(skip_all)]
1209    pub fn row_infos(&self, row: u32) -> InlayBufferRows<'_> {
1210        let mut cursor = self.transforms.cursor::<Dimensions<InlayPoint, Point>>(());
1211        let inlay_point = InlayPoint::new(row, 0);
1212        cursor.seek(&inlay_point, Bias::Left);
1213
1214        let max_buffer_row = self.buffer.max_row();
1215        let mut buffer_point = cursor.start().1;
1216        let buffer_row = if row == 0 {
1217            MultiBufferRow(0)
1218        } else {
1219            match cursor.item() {
1220                Some(Transform::Isomorphic(_)) => {
1221                    buffer_point += inlay_point.0 - cursor.start().0.0;
1222                    MultiBufferRow(buffer_point.row)
1223                }
1224                _ => cmp::min(MultiBufferRow(buffer_point.row + 1), max_buffer_row),
1225            }
1226        };
1227
1228        InlayBufferRows {
1229            transforms: cursor,
1230            inlay_row: inlay_point.row(),
1231            buffer_rows: self.buffer.row_infos(buffer_row),
1232            max_buffer_row,
1233        }
1234    }
1235
1236    #[ztracing::instrument(skip_all)]
1237    pub fn line_len(&self, row: u32) -> u32 {
1238        let line_start = self.to_offset(InlayPoint::new(row, 0)).0;
1239        let line_end = if row >= self.max_point().row() {
1240            self.len().0
1241        } else {
1242            self.to_offset(InlayPoint::new(row + 1, 0)).0 - 1
1243        };
1244        (line_end - line_start) as u32
1245    }
1246
1247    #[ztracing::instrument(skip_all)]
1248    pub(crate) fn chunks<'a>(
1249        &'a self,
1250        range: Range<InlayOffset>,
1251        language_aware: LanguageAwareStyling,
1252        highlights: Highlights<'a>,
1253    ) -> InlayChunks<'a> {
1254        let mut cursor = self
1255            .transforms
1256            .cursor::<Dimensions<InlayOffset, MultiBufferOffset>>(());
1257        cursor.seek(&range.start, Bias::Right);
1258
1259        let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end);
1260        let buffer_chunks = CustomHighlightsChunks::new(
1261            buffer_range,
1262            language_aware,
1263            highlights.text_highlights,
1264            highlights.semantic_token_highlights,
1265            &self.buffer,
1266        );
1267
1268        InlayChunks {
1269            transforms: cursor,
1270            buffer_chunks,
1271            inlay_chunks: None,
1272            inlay_chunk: None,
1273            buffer_chunk: None,
1274            output_offset: range.start,
1275            max_output_offset: range.end,
1276            highlight_styles: highlights.styles,
1277            highlights,
1278            snapshot: self,
1279        }
1280    }
1281
1282    #[cfg(test)]
1283    #[ztracing::instrument(skip_all)]
1284    pub fn text(&self) -> String {
1285        self.chunks(
1286            Default::default()..self.len(),
1287            LanguageAwareStyling {
1288                tree_sitter: false,
1289                diagnostics: false,
1290            },
1291            Highlights::default(),
1292        )
1293        .map(|chunk| chunk.chunk.text)
1294        .collect()
1295    }
1296
1297    #[ztracing::instrument(skip_all)]
1298    fn check_invariants(&self) {
1299        #[cfg(any(debug_assertions, feature = "test-support"))]
1300        {
1301            assert_eq!(self.transforms.summary().input, self.buffer.text_summary());
1302            let mut transforms = self.transforms.iter().peekable();
1303            while let Some(transform) = transforms.next() {
1304                let transform_is_isomorphic = matches!(transform, Transform::Isomorphic(_));
1305                if let Some(next_transform) = transforms.peek() {
1306                    let next_transform_is_isomorphic =
1307                        matches!(next_transform, Transform::Isomorphic(_));
1308                    assert!(
1309                        !transform_is_isomorphic || !next_transform_is_isomorphic,
1310                        "two adjacent isomorphic transforms"
1311                    );
1312                }
1313            }
1314        }
1315    }
1316}
1317
1318pub struct InlayPointCursor<'transforms> {
1319    cursor: Cursor<'transforms, 'static, Transform, Dimensions<Point, InlayPoint>>,
1320    transforms: &'transforms SumTree<Transform>,
1321}
1322
1323impl InlayPointCursor<'_> {
1324    #[ztracing::instrument(skip_all)]
1325    pub fn map(&mut self, point: Point, bias: Bias) -> InlayPoint {
1326        let cursor = &mut self.cursor;
1327        if cursor.did_seek() {
1328            cursor.seek_forward(&point, Bias::Left);
1329        } else {
1330            cursor.seek(&point, Bias::Left);
1331        }
1332        loop {
1333            match cursor.item() {
1334                Some(Transform::Isomorphic(_)) => {
1335                    if point == cursor.end().0 {
1336                        while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
1337                            if bias == Bias::Left && inlay.position.bias() == Bias::Right {
1338                                break;
1339                            } else {
1340                                cursor.next();
1341                            }
1342                        }
1343                        return cursor.end().1;
1344                    } else {
1345                        let overshoot = point - cursor.start().0;
1346                        return InlayPoint(cursor.start().1.0 + overshoot);
1347                    }
1348                }
1349                Some(Transform::Inlay(inlay)) => {
1350                    if inlay.position.bias() == Bias::Left || bias == Bias::Right {
1351                        cursor.next();
1352                    } else {
1353                        return cursor.start().1;
1354                    }
1355                }
1356                None => {
1357                    return InlayPoint(self.transforms.summary().output.lines);
1358                }
1359            }
1360        }
1361    }
1362}
1363
1364/// Forward-only cursor that maps buffer-offset ranges to the inlay-point ranges
1365/// covering only actual buffer text (excluding inlay text), reusing its tree
1366/// position across calls.
1367///
1368/// This is the streaming equivalent of
1369/// [`InlaySnapshot::buffer_offset_to_inlay_ranges`] composed with
1370/// [`InlaySnapshot::to_point`]. Because the cursor only seeks forward, callers
1371/// must provide ranges with non-decreasing offsets.
1372pub struct BufferOffsetToInlayPointCursor<'a> {
1373    snapshot: &'a InlaySnapshot,
1374    cursor: Cursor<'a, 'static, Transform, Dimensions<MultiBufferOffset, InlayPoint>>,
1375}
1376
1377impl BufferOffsetToInlayPointCursor<'_> {
1378    /// Resets the cursor to the start so it can seek backward again.
1379    pub fn reset(&mut self) {
1380        self.cursor.reset();
1381    }
1382
1383    pub fn map(&mut self, range: Range<MultiBufferOffset>) -> SmallVec<[Range<InlayPoint>; 1]> {
1384        let buffer = &self.snapshot.buffer;
1385        let cursor = &mut self.cursor;
1386        if cursor.did_seek() {
1387            cursor.seek_forward(&range.start, Bias::Right);
1388        } else {
1389            cursor.seek(&range.start, Bias::Right);
1390        }
1391
1392        let mut result = SmallVec::new();
1393        loop {
1394            match cursor.item() {
1395                Some(Transform::Isomorphic(_)) => {
1396                    let seg_buffer_start = cursor.start().0;
1397                    let seg_buffer_end = cursor.end().0;
1398                    let seg_inlay_point_start = cursor.start().1;
1399
1400                    let overlap_start = cmp::max(range.start, seg_buffer_start);
1401                    let overlap_end = cmp::min(range.end, seg_buffer_end);
1402
1403                    if overlap_start < overlap_end {
1404                        let seg_point_start = buffer.offset_to_point(seg_buffer_start);
1405                        let start = InlayPoint(
1406                            seg_inlay_point_start.0
1407                                + (buffer.offset_to_point(overlap_start) - seg_point_start),
1408                        );
1409                        let end = InlayPoint(
1410                            seg_inlay_point_start.0
1411                                + (buffer.offset_to_point(overlap_end) - seg_point_start),
1412                        );
1413                        result.push(start..end);
1414                    }
1415
1416                    // Leave the cursor on the transform containing `range.end`
1417                    // rather than advancing past it, so a subsequent call with a
1418                    // larger (but possibly same-transform) start does not seek
1419                    // backward.
1420                    if seg_buffer_end >= range.end {
1421                        break;
1422                    }
1423                    cursor.next();
1424                }
1425                Some(Transform::Inlay(_)) => cursor.next(),
1426                None => break,
1427            }
1428        }
1429        result
1430    }
1431}
1432
1433fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: MBTextSummary) {
1434    if summary.len == MultiBufferOffset(0) {
1435        return;
1436    }
1437
1438    let mut summary = Some(summary);
1439    sum_tree.update_last(
1440        |transform| {
1441            if let Transform::Isomorphic(transform) = transform {
1442                *transform += summary.take().unwrap();
1443            }
1444        },
1445        (),
1446    );
1447
1448    if let Some(summary) = summary {
1449        sum_tree.push(Transform::Isomorphic(summary), ());
1450    }
1451}
1452
1453#[cfg(test)]
1454mod tests {
1455    use super::*;
1456    use crate::{
1457        MultiBuffer,
1458        display_map::{HighlightKey, InlayHighlights},
1459        hover_links::InlayHighlight,
1460    };
1461    use collections::HashMap;
1462    use gpui::{App, HighlightStyle};
1463    use multi_buffer::Anchor;
1464    use project::{InlayHint, InlayHintLabel, ResolveState};
1465    use rand::prelude::*;
1466    use settings::SettingsStore;
1467    use std::{cmp::Reverse, env, sync::Arc};
1468    use sum_tree::TreeMap;
1469    use text::{BufferId, Patch, Rope};
1470    use util::RandomCharIter;
1471    use util::post_inc;
1472
1473    #[test]
1474    fn test_inlay_properties_label_padding() {
1475        assert_eq!(
1476            Inlay::hint(
1477                InlayId::Hint(0),
1478                Anchor::Min,
1479                &InlayHint {
1480                    label: InlayHintLabel::String("a".to_string()),
1481                    position: text::Anchor::min_for_buffer(BufferId::new(1).unwrap()),
1482                    padding_left: false,
1483                    padding_right: false,
1484                    tooltip: None,
1485                    kind: None,
1486                    resolve_state: ResolveState::Resolved,
1487                },
1488            )
1489            .text()
1490            .to_string(),
1491            "a",
1492            "Should not pad label if not requested"
1493        );
1494
1495        assert_eq!(
1496            Inlay::hint(
1497                InlayId::Hint(0),
1498                Anchor::Min,
1499                &InlayHint {
1500                    label: InlayHintLabel::String("a".to_string()),
1501                    position: text::Anchor::min_for_buffer(BufferId::new(1).unwrap()),
1502                    padding_left: true,
1503                    padding_right: true,
1504                    tooltip: None,
1505                    kind: None,
1506                    resolve_state: ResolveState::Resolved,
1507                },
1508            )
1509            .text()
1510            .to_string(),
1511            " a ",
1512            "Should pad label for every side requested"
1513        );
1514
1515        assert_eq!(
1516            Inlay::hint(
1517                InlayId::Hint(0),
1518                Anchor::Min,
1519                &InlayHint {
1520                    label: InlayHintLabel::String(" a ".to_string()),
1521                    position: text::Anchor::min_for_buffer(BufferId::new(1).unwrap()),
1522                    padding_left: false,
1523                    padding_right: false,
1524                    tooltip: None,
1525                    kind: None,
1526                    resolve_state: ResolveState::Resolved,
1527                },
1528            )
1529            .text()
1530            .to_string(),
1531            " a ",
1532            "Should not change already padded label"
1533        );
1534
1535        assert_eq!(
1536            Inlay::hint(
1537                InlayId::Hint(0),
1538                Anchor::Min,
1539                &InlayHint {
1540                    label: InlayHintLabel::String(" a ".to_string()),
1541                    position: text::Anchor::min_for_buffer(BufferId::new(1).unwrap()),
1542                    padding_left: true,
1543                    padding_right: true,
1544                    tooltip: None,
1545                    kind: None,
1546                    resolve_state: ResolveState::Resolved,
1547                },
1548            )
1549            .text()
1550            .to_string(),
1551            " a ",
1552            "Should not change already padded label"
1553        );
1554    }
1555
1556    #[gpui::test]
1557    fn test_inlay_hint_padding_with_multibyte_chars() {
1558        assert_eq!(
1559            Inlay::hint(
1560                InlayId::Hint(0),
1561                Anchor::Min,
1562                &InlayHint {
1563                    label: InlayHintLabel::String("🎨".to_string()),
1564                    position: text::Anchor::min_for_buffer(BufferId::new(1).unwrap()),
1565                    padding_left: true,
1566                    padding_right: true,
1567                    tooltip: None,
1568                    kind: None,
1569                    resolve_state: ResolveState::Resolved,
1570                },
1571            )
1572            .text()
1573            .to_string(),
1574            " 🎨 ",
1575            "Should pad single emoji correctly"
1576        );
1577    }
1578
1579    #[gpui::test]
1580    fn test_basic_inlays(cx: &mut App) {
1581        let buffer = MultiBuffer::build_simple("abcdefghi", cx);
1582        let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
1583        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1584        assert_eq!(inlay_snapshot.text(), "abcdefghi");
1585        let mut next_inlay_id = 0;
1586
1587        let (inlay_snapshot, _) = inlay_map.splice(
1588            &[],
1589            vec![Inlay::mock_hint(
1590                post_inc(&mut next_inlay_id),
1591                buffer
1592                    .read(cx)
1593                    .snapshot(cx)
1594                    .anchor_after(MultiBufferOffset(3)),
1595                "|123|",
1596            )],
1597        );
1598        assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
1599        assert_eq!(
1600            inlay_snapshot.to_inlay_point(Point::new(0, 0)),
1601            InlayPoint::new(0, 0)
1602        );
1603        assert_eq!(
1604            inlay_snapshot.to_inlay_point(Point::new(0, 1)),
1605            InlayPoint::new(0, 1)
1606        );
1607        assert_eq!(
1608            inlay_snapshot.to_inlay_point(Point::new(0, 2)),
1609            InlayPoint::new(0, 2)
1610        );
1611        assert_eq!(
1612            inlay_snapshot.to_inlay_point(Point::new(0, 3)),
1613            InlayPoint::new(0, 3)
1614        );
1615        assert_eq!(
1616            inlay_snapshot.to_inlay_point(Point::new(0, 4)),
1617            InlayPoint::new(0, 9)
1618        );
1619        assert_eq!(
1620            inlay_snapshot.to_inlay_point(Point::new(0, 5)),
1621            InlayPoint::new(0, 10)
1622        );
1623        assert_eq!(
1624            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1625            InlayPoint::new(0, 0)
1626        );
1627        assert_eq!(
1628            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1629            InlayPoint::new(0, 0)
1630        );
1631        assert_eq!(
1632            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1633            InlayPoint::new(0, 3)
1634        );
1635        assert_eq!(
1636            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1637            InlayPoint::new(0, 3)
1638        );
1639        assert_eq!(
1640            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1641            InlayPoint::new(0, 3)
1642        );
1643        assert_eq!(
1644            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1645            InlayPoint::new(0, 9)
1646        );
1647
1648        // Edits before or after the inlay should not affect it.
1649        buffer.update(cx, |buffer, cx| {
1650            buffer.edit(
1651                [
1652                    (MultiBufferOffset(2)..MultiBufferOffset(3), "x"),
1653                    (MultiBufferOffset(3)..MultiBufferOffset(3), "y"),
1654                    (MultiBufferOffset(4)..MultiBufferOffset(4), "z"),
1655                ],
1656                None,
1657                cx,
1658            )
1659        });
1660        let (inlay_snapshot, _) = inlay_map.sync(
1661            buffer.read(cx).snapshot(cx),
1662            buffer_edits.consume().into_inner(),
1663        );
1664        assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
1665
1666        // An edit surrounding the inlay should invalidate it.
1667        buffer.update(cx, |buffer, cx| {
1668            buffer.edit(
1669                [(MultiBufferOffset(4)..MultiBufferOffset(5), "D")],
1670                None,
1671                cx,
1672            )
1673        });
1674        let (inlay_snapshot, _) = inlay_map.sync(
1675            buffer.read(cx).snapshot(cx),
1676            buffer_edits.consume().into_inner(),
1677        );
1678        assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
1679
1680        let (inlay_snapshot, _) = inlay_map.splice(
1681            &[],
1682            vec![
1683                Inlay::mock_hint(
1684                    post_inc(&mut next_inlay_id),
1685                    buffer
1686                        .read(cx)
1687                        .snapshot(cx)
1688                        .anchor_before(MultiBufferOffset(3)),
1689                    "|123|",
1690                ),
1691                Inlay::edit_prediction(
1692                    post_inc(&mut next_inlay_id),
1693                    buffer
1694                        .read(cx)
1695                        .snapshot(cx)
1696                        .anchor_after(MultiBufferOffset(3)),
1697                    "|456|",
1698                ),
1699            ],
1700        );
1701        assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
1702
1703        // Edits ending where the inlay starts should not move it if it has a left bias.
1704        buffer.update(cx, |buffer, cx| {
1705            buffer.edit(
1706                [(MultiBufferOffset(3)..MultiBufferOffset(3), "JKL")],
1707                None,
1708                cx,
1709            )
1710        });
1711        let (inlay_snapshot, _) = inlay_map.sync(
1712            buffer.read(cx).snapshot(cx),
1713            buffer_edits.consume().into_inner(),
1714        );
1715        assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
1716
1717        assert_eq!(
1718            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1719            InlayPoint::new(0, 0)
1720        );
1721        assert_eq!(
1722            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1723            InlayPoint::new(0, 0)
1724        );
1725
1726        assert_eq!(
1727            inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Left),
1728            InlayPoint::new(0, 1)
1729        );
1730        assert_eq!(
1731            inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Right),
1732            InlayPoint::new(0, 1)
1733        );
1734
1735        assert_eq!(
1736            inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Left),
1737            InlayPoint::new(0, 2)
1738        );
1739        assert_eq!(
1740            inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Right),
1741            InlayPoint::new(0, 2)
1742        );
1743
1744        assert_eq!(
1745            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1746            InlayPoint::new(0, 2)
1747        );
1748        assert_eq!(
1749            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1750            InlayPoint::new(0, 8)
1751        );
1752
1753        assert_eq!(
1754            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1755            InlayPoint::new(0, 2)
1756        );
1757        assert_eq!(
1758            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1759            InlayPoint::new(0, 8)
1760        );
1761
1762        assert_eq!(
1763            inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Left),
1764            InlayPoint::new(0, 2)
1765        );
1766        assert_eq!(
1767            inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Right),
1768            InlayPoint::new(0, 8)
1769        );
1770
1771        assert_eq!(
1772            inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Left),
1773            InlayPoint::new(0, 2)
1774        );
1775        assert_eq!(
1776            inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Right),
1777            InlayPoint::new(0, 8)
1778        );
1779
1780        assert_eq!(
1781            inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Left),
1782            InlayPoint::new(0, 2)
1783        );
1784        assert_eq!(
1785            inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Right),
1786            InlayPoint::new(0, 8)
1787        );
1788
1789        assert_eq!(
1790            inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Left),
1791            InlayPoint::new(0, 8)
1792        );
1793        assert_eq!(
1794            inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Right),
1795            InlayPoint::new(0, 8)
1796        );
1797
1798        assert_eq!(
1799            inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Left),
1800            InlayPoint::new(0, 9)
1801        );
1802        assert_eq!(
1803            inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Right),
1804            InlayPoint::new(0, 9)
1805        );
1806
1807        assert_eq!(
1808            inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Left),
1809            InlayPoint::new(0, 10)
1810        );
1811        assert_eq!(
1812            inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Right),
1813            InlayPoint::new(0, 10)
1814        );
1815
1816        assert_eq!(
1817            inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Left),
1818            InlayPoint::new(0, 11)
1819        );
1820        assert_eq!(
1821            inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Right),
1822            InlayPoint::new(0, 11)
1823        );
1824
1825        assert_eq!(
1826            inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Left),
1827            InlayPoint::new(0, 11)
1828        );
1829        assert_eq!(
1830            inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Right),
1831            InlayPoint::new(0, 17)
1832        );
1833
1834        assert_eq!(
1835            inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Left),
1836            InlayPoint::new(0, 11)
1837        );
1838        assert_eq!(
1839            inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Right),
1840            InlayPoint::new(0, 17)
1841        );
1842
1843        assert_eq!(
1844            inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Left),
1845            InlayPoint::new(0, 11)
1846        );
1847        assert_eq!(
1848            inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Right),
1849            InlayPoint::new(0, 17)
1850        );
1851
1852        assert_eq!(
1853            inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Left),
1854            InlayPoint::new(0, 11)
1855        );
1856        assert_eq!(
1857            inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Right),
1858            InlayPoint::new(0, 17)
1859        );
1860
1861        assert_eq!(
1862            inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Left),
1863            InlayPoint::new(0, 11)
1864        );
1865        assert_eq!(
1866            inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Right),
1867            InlayPoint::new(0, 17)
1868        );
1869
1870        assert_eq!(
1871            inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Left),
1872            InlayPoint::new(0, 17)
1873        );
1874        assert_eq!(
1875            inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Right),
1876            InlayPoint::new(0, 17)
1877        );
1878
1879        assert_eq!(
1880            inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Left),
1881            InlayPoint::new(0, 18)
1882        );
1883        assert_eq!(
1884            inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Right),
1885            InlayPoint::new(0, 18)
1886        );
1887
1888        // The inlays can be manually removed.
1889        let (inlay_snapshot, _) = inlay_map.splice(
1890            &inlay_map
1891                .inlays
1892                .iter()
1893                .map(|inlay| inlay.id)
1894                .collect::<Vec<InlayId>>(),
1895            Vec::new(),
1896        );
1897        assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi");
1898    }
1899
1900    #[gpui::test]
1901    fn test_inlay_buffer_rows(cx: &mut App) {
1902        let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx);
1903        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1904        assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi");
1905        let mut next_inlay_id = 0;
1906
1907        let (inlay_snapshot, _) = inlay_map.splice(
1908            &[],
1909            vec![
1910                Inlay::mock_hint(
1911                    post_inc(&mut next_inlay_id),
1912                    buffer
1913                        .read(cx)
1914                        .snapshot(cx)
1915                        .anchor_before(MultiBufferOffset(0)),
1916                    "|123|\n",
1917                ),
1918                Inlay::mock_hint(
1919                    post_inc(&mut next_inlay_id),
1920                    buffer
1921                        .read(cx)
1922                        .snapshot(cx)
1923                        .anchor_before(MultiBufferOffset(4)),
1924                    "|456|",
1925                ),
1926                Inlay::edit_prediction(
1927                    post_inc(&mut next_inlay_id),
1928                    buffer
1929                        .read(cx)
1930                        .snapshot(cx)
1931                        .anchor_before(MultiBufferOffset(7)),
1932                    "\n|567|\n",
1933                ),
1934            ],
1935        );
1936        assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
1937        assert_eq!(
1938            inlay_snapshot
1939                .row_infos(0)
1940                .map(|info| info.buffer_row)
1941                .collect::<Vec<_>>(),
1942            vec![Some(0), None, Some(1), None, None, Some(2)]
1943        );
1944    }
1945
1946    #[gpui::test(iterations = 100)]
1947    fn test_random_inlays(cx: &mut App, mut rng: StdRng) {
1948        init_test(cx);
1949
1950        let operations = env::var("OPERATIONS")
1951            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1952            .unwrap_or(10);
1953
1954        let len = rng.random_range(0..30);
1955        let buffer = if rng.random() {
1956            let text = util::RandomCharIter::new(&mut rng)
1957                .take(len)
1958                .collect::<String>();
1959            MultiBuffer::build_simple(&text, cx)
1960        } else {
1961            MultiBuffer::build_random(&mut rng, cx)
1962        };
1963        let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1964        let mut next_inlay_id = 0;
1965        log::info!("buffer text: {:?}", buffer_snapshot.text());
1966        let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1967        for _ in 0..operations {
1968            let mut inlay_edits = Patch::default();
1969
1970            let mut prev_inlay_text = inlay_snapshot.text();
1971            let mut buffer_edits = Vec::new();
1972            match rng.random_range(0..=100) {
1973                0..=50 => {
1974                    let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1975                    log::info!("mutated text: {:?}", snapshot.text());
1976                    inlay_edits = Patch::new(edits);
1977                }
1978                _ => buffer.update(cx, |buffer, cx| {
1979                    let subscription = buffer.subscribe();
1980                    let edit_count = rng.random_range(1..=5);
1981                    buffer.randomly_mutate(&mut rng, edit_count, cx);
1982                    buffer_snapshot = buffer.snapshot(cx);
1983                    let edits = subscription.consume().into_inner();
1984                    log::info!("editing {:?}", edits);
1985                    buffer_edits.extend(edits);
1986                }),
1987            };
1988
1989            let (new_inlay_snapshot, new_inlay_edits) =
1990                inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
1991            inlay_snapshot = new_inlay_snapshot;
1992            inlay_edits = inlay_edits.compose(new_inlay_edits);
1993
1994            log::info!("buffer text: {:?}", buffer_snapshot.text());
1995            log::info!("inlay text: {:?}", inlay_snapshot.text());
1996
1997            let inlays = inlay_map
1998                .inlays
1999                .iter()
2000                .filter(|inlay| inlay.position.is_valid(&buffer_snapshot))
2001                .map(|inlay| {
2002                    let offset = inlay.position.to_offset(&buffer_snapshot);
2003                    (offset, inlay.clone())
2004                })
2005                .collect::<Vec<_>>();
2006            let mut expected_text = Rope::from(&buffer_snapshot.text());
2007            for (offset, inlay) in inlays.iter().rev() {
2008                expected_text.replace(offset.0..offset.0, &inlay.text().to_string());
2009            }
2010            assert_eq!(inlay_snapshot.text(), expected_text.to_string());
2011
2012            let expected_buffer_rows = inlay_snapshot.row_infos(0).collect::<Vec<_>>();
2013            assert_eq!(
2014                expected_buffer_rows.len() as u32,
2015                expected_text.max_point().row + 1
2016            );
2017            for row_start in 0..expected_buffer_rows.len() {
2018                assert_eq!(
2019                    inlay_snapshot
2020                        .row_infos(row_start as u32)
2021                        .collect::<Vec<_>>(),
2022                    &expected_buffer_rows[row_start..],
2023                    "incorrect buffer rows starting at {}",
2024                    row_start
2025                );
2026            }
2027
2028            let mut text_highlights = HashMap::default();
2029            let text_highlight_count = rng.random_range(0_usize..10);
2030            let mut text_highlight_ranges = (0..text_highlight_count)
2031                .map(|_| buffer_snapshot.random_byte_range(MultiBufferOffset(0), &mut rng))
2032                .collect::<Vec<_>>();
2033            text_highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
2034            log::info!("highlighting text ranges {text_highlight_ranges:?}");
2035            text_highlights.insert(
2036                HighlightKey::ColorizeBracket(0),
2037                Arc::new((
2038                    HighlightStyle::default(),
2039                    text_highlight_ranges
2040                        .into_iter()
2041                        .map(|range| {
2042                            buffer_snapshot.anchor_before(range.start)
2043                                ..buffer_snapshot.anchor_after(range.end)
2044                        })
2045                        .collect(),
2046                )),
2047            );
2048            let text_highlights = Arc::new(text_highlights);
2049
2050            let mut inlay_highlights = InlayHighlights::default();
2051            if !inlays.is_empty() {
2052                let inlay_highlight_count = rng.random_range(0..inlays.len());
2053                let mut inlay_indices = BTreeSet::default();
2054                while inlay_indices.len() < inlay_highlight_count {
2055                    inlay_indices.insert(rng.random_range(0..inlays.len()));
2056                }
2057                let new_highlights = TreeMap::from_ordered_entries(
2058                    inlay_indices
2059                        .into_iter()
2060                        .filter_map(|i| {
2061                            let (_, inlay) = &inlays[i];
2062                            let inlay_text_len = inlay.text().len();
2063                            match inlay_text_len {
2064                                0 => None,
2065                                1 => Some(InlayHighlight {
2066                                    inlay: inlay.id,
2067                                    inlay_position: inlay.position,
2068                                    range: 0..1,
2069                                }),
2070                                n => {
2071                                    let inlay_text = inlay.text().to_string();
2072                                    let mut highlight_end = rng.random_range(1..n);
2073                                    let mut highlight_start = rng.random_range(0..highlight_end);
2074                                    while !inlay_text.is_char_boundary(highlight_end) {
2075                                        highlight_end += 1;
2076                                    }
2077                                    while !inlay_text.is_char_boundary(highlight_start) {
2078                                        highlight_start -= 1;
2079                                    }
2080                                    Some(InlayHighlight {
2081                                        inlay: inlay.id,
2082                                        inlay_position: inlay.position,
2083                                        range: highlight_start..highlight_end,
2084                                    })
2085                                }
2086                            }
2087                        })
2088                        .map(|highlight| (highlight.inlay, (HighlightStyle::default(), highlight))),
2089                );
2090                log::info!("highlighting inlay ranges {new_highlights:?}");
2091                inlay_highlights.insert(HighlightKey::Editor, new_highlights);
2092            }
2093
2094            for _ in 0..5 {
2095                let mut end = rng.random_range(0..=inlay_snapshot.len().0.0);
2096                end = expected_text.clip_offset(end, Bias::Right);
2097                let mut start = rng.random_range(0..=end);
2098                start = expected_text.clip_offset(start, Bias::Right);
2099
2100                let range =
2101                    InlayOffset(MultiBufferOffset(start))..InlayOffset(MultiBufferOffset(end));
2102                log::info!("calling inlay_snapshot.chunks({range:?})");
2103                let actual_text = inlay_snapshot
2104                    .chunks(
2105                        range,
2106                        LanguageAwareStyling {
2107                            tree_sitter: false,
2108                            diagnostics: false,
2109                        },
2110                        Highlights {
2111                            text_highlights: Some(&text_highlights),
2112                            inlay_highlights: Some(&inlay_highlights),
2113                            ..Highlights::default()
2114                        },
2115                    )
2116                    .map(|chunk| chunk.chunk.text)
2117                    .collect::<String>();
2118                assert_eq!(
2119                    actual_text,
2120                    expected_text.slice(start..end).to_string(),
2121                    "incorrect text in range {:?}",
2122                    start..end
2123                );
2124
2125                assert_eq!(
2126                    inlay_snapshot.text_summary_for_range(
2127                        InlayOffset(MultiBufferOffset(start))..InlayOffset(MultiBufferOffset(end))
2128                    ),
2129                    MBTextSummary::from(expected_text.slice(start..end).summary())
2130                );
2131            }
2132
2133            for edit in inlay_edits {
2134                prev_inlay_text.replace_range(
2135                    edit.new.start.0.0..edit.new.start.0.0 + edit.old_len(),
2136                    &inlay_snapshot.text()[edit.new.start.0.0..edit.new.end.0.0],
2137                );
2138            }
2139            assert_eq!(prev_inlay_text, inlay_snapshot.text());
2140
2141            assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
2142            assert_eq!(expected_text.len(), inlay_snapshot.len().0.0);
2143
2144            let mut buffer_point = Point::default();
2145            let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
2146            let mut buffer_chars = buffer_snapshot.chars_at(MultiBufferOffset(0));
2147            loop {
2148                // Ensure conversion from buffer coordinates to inlay coordinates
2149                // is consistent.
2150                let buffer_offset = buffer_snapshot.point_to_offset(buffer_point);
2151                assert_eq!(
2152                    inlay_snapshot.to_point(inlay_snapshot.to_inlay_offset(buffer_offset)),
2153                    inlay_point
2154                );
2155
2156                // No matter which bias we clip an inlay point with, it doesn't move
2157                // because it was constructed from a buffer point.
2158                assert_eq!(
2159                    inlay_snapshot.clip_point(inlay_point, Bias::Left),
2160                    inlay_point,
2161                    "invalid inlay point for buffer point {:?} when clipped left",
2162                    buffer_point
2163                );
2164                assert_eq!(
2165                    inlay_snapshot.clip_point(inlay_point, Bias::Right),
2166                    inlay_point,
2167                    "invalid inlay point for buffer point {:?} when clipped right",
2168                    buffer_point
2169                );
2170
2171                if let Some(ch) = buffer_chars.next() {
2172                    if ch == '\n' {
2173                        buffer_point += Point::new(1, 0);
2174                    } else {
2175                        buffer_point += Point::new(0, ch.len_utf8() as u32);
2176                    }
2177
2178                    // Ensure that moving forward in the buffer always moves the inlay point forward as well.
2179                    let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
2180                    assert!(new_inlay_point > inlay_point);
2181                    inlay_point = new_inlay_point;
2182                } else {
2183                    break;
2184                }
2185            }
2186
2187            let mut inlay_point = InlayPoint::default();
2188            let mut inlay_offset = InlayOffset::default();
2189            for ch in expected_text.chars() {
2190                assert_eq!(
2191                    inlay_snapshot.to_offset(inlay_point),
2192                    inlay_offset,
2193                    "invalid to_offset({:?})",
2194                    inlay_point
2195                );
2196                assert_eq!(
2197                    inlay_snapshot.to_point(inlay_offset),
2198                    inlay_point,
2199                    "invalid to_point({:?})",
2200                    inlay_offset
2201                );
2202
2203                let mut bytes = [0; 4];
2204                for byte in ch.encode_utf8(&mut bytes).as_bytes() {
2205                    inlay_offset.0 += 1;
2206                    if *byte == b'\n' {
2207                        inlay_point.0 += Point::new(1, 0);
2208                    } else {
2209                        inlay_point.0 += Point::new(0, 1);
2210                    }
2211
2212                    let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left);
2213                    let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right);
2214                    assert!(
2215                        clipped_left_point <= clipped_right_point,
2216                        "inlay point {:?} when clipped left is greater than when clipped right ({:?} > {:?})",
2217                        inlay_point,
2218                        clipped_left_point,
2219                        clipped_right_point
2220                    );
2221
2222                    // Ensure the clipped points are at valid text locations.
2223                    assert_eq!(
2224                        clipped_left_point.0,
2225                        expected_text.clip_point(clipped_left_point.0, Bias::Left)
2226                    );
2227                    assert_eq!(
2228                        clipped_right_point.0,
2229                        expected_text.clip_point(clipped_right_point.0, Bias::Right)
2230                    );
2231
2232                    // Ensure the clipped points never overshoot the end of the map.
2233                    assert!(clipped_left_point <= inlay_snapshot.max_point());
2234                    assert!(clipped_right_point <= inlay_snapshot.max_point());
2235
2236                    // Ensure the clipped points are at valid buffer locations.
2237                    assert_eq!(
2238                        inlay_snapshot
2239                            .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_left_point)),
2240                        clipped_left_point,
2241                        "to_buffer_point({:?}) = {:?}",
2242                        clipped_left_point,
2243                        inlay_snapshot.to_buffer_point(clipped_left_point),
2244                    );
2245                    assert_eq!(
2246                        inlay_snapshot
2247                            .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_right_point)),
2248                        clipped_right_point,
2249                        "to_buffer_point({:?}) = {:?}",
2250                        clipped_right_point,
2251                        inlay_snapshot.to_buffer_point(clipped_right_point),
2252                    );
2253                }
2254            }
2255        }
2256    }
2257
2258    #[gpui::test(iterations = 100)]
2259    fn test_random_chunk_bitmaps(cx: &mut gpui::App, mut rng: StdRng) {
2260        init_test(cx);
2261
2262        // Generate random buffer using existing test infrastructure
2263        let text_len = rng.random_range(0..10000);
2264        let buffer = if rng.random() {
2265            let text = RandomCharIter::new(&mut rng)
2266                .take(text_len)
2267                .collect::<String>();
2268            MultiBuffer::build_simple(&text, cx)
2269        } else {
2270            MultiBuffer::build_random(&mut rng, cx)
2271        };
2272
2273        let buffer_snapshot = buffer.read(cx).snapshot(cx);
2274        let (mut inlay_map, _) = InlayMap::new(buffer_snapshot.clone());
2275
2276        // Perform random mutations to add inlays
2277        let mut next_inlay_id = 0;
2278        let mutation_count = rng.random_range(1..10);
2279        for _ in 0..mutation_count {
2280            inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
2281        }
2282
2283        let (snapshot, _) = inlay_map.sync(buffer_snapshot, vec![]);
2284
2285        // Get all chunks and verify their bitmaps
2286        let chunks = snapshot.chunks(
2287            InlayOffset(MultiBufferOffset(0))..snapshot.len(),
2288            LanguageAwareStyling {
2289                tree_sitter: false,
2290                diagnostics: false,
2291            },
2292            Highlights::default(),
2293        );
2294
2295        for chunk in chunks.into_iter().map(|inlay_chunk| inlay_chunk.chunk) {
2296            let chunk_text = chunk.text;
2297            let chars_bitmap = chunk.chars;
2298            let tabs_bitmap = chunk.tabs;
2299
2300            // Check empty chunks have empty bitmaps
2301            if chunk_text.is_empty() {
2302                assert_eq!(
2303                    chars_bitmap, 0,
2304                    "Empty chunk should have empty chars bitmap"
2305                );
2306                assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
2307                continue;
2308            }
2309
2310            // Verify that chunk text doesn't exceed 128 bytes
2311            assert!(
2312                chunk_text.len() <= 128,
2313                "Chunk text length {} exceeds 128 bytes",
2314                chunk_text.len()
2315            );
2316
2317            // Verify chars bitmap
2318            let char_indices = chunk_text
2319                .char_indices()
2320                .map(|(i, _)| i)
2321                .collect::<Vec<_>>();
2322
2323            for byte_idx in 0..chunk_text.len() {
2324                let should_have_bit = char_indices.contains(&byte_idx);
2325                let has_bit = chars_bitmap & (1 << byte_idx) != 0;
2326
2327                if has_bit != should_have_bit {
2328                    eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
2329                    eprintln!("Char indices: {:?}", char_indices);
2330                    eprintln!("Chars bitmap: {:#b}", chars_bitmap);
2331                    assert_eq!(
2332                        has_bit, should_have_bit,
2333                        "Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
2334                        byte_idx, chunk_text, should_have_bit, has_bit
2335                    );
2336                }
2337            }
2338
2339            // Verify tabs bitmap
2340            for (byte_idx, byte) in chunk_text.bytes().enumerate() {
2341                let is_tab = byte == b'\t';
2342                let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
2343
2344                if has_bit != is_tab {
2345                    eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
2346                    eprintln!("Tabs bitmap: {:#b}", tabs_bitmap);
2347                    assert_eq!(
2348                        has_bit, is_tab,
2349                        "Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
2350                        byte_idx, chunk_text, byte as char, is_tab, has_bit
2351                    );
2352                }
2353            }
2354        }
2355    }
2356
2357    fn init_test(cx: &mut App) {
2358        let store = SettingsStore::test(cx);
2359        cx.set_global(store);
2360        theme_settings::init(theme::LoadThemes::JustBase, cx);
2361    }
2362
2363    /// Helper to create test highlights for an inlay
2364    fn create_inlay_highlights(
2365        inlay_id: InlayId,
2366        highlight_range: Range<usize>,
2367        position: Anchor,
2368    ) -> TreeMap<HighlightKey, TreeMap<InlayId, (HighlightStyle, InlayHighlight)>> {
2369        let mut inlay_highlights = TreeMap::default();
2370        let mut type_highlights = TreeMap::default();
2371        type_highlights.insert(
2372            inlay_id,
2373            (
2374                HighlightStyle::default(),
2375                InlayHighlight {
2376                    inlay: inlay_id,
2377                    range: highlight_range,
2378                    inlay_position: position,
2379                },
2380            ),
2381        );
2382        inlay_highlights.insert(HighlightKey::Editor, type_highlights);
2383        inlay_highlights
2384    }
2385
2386    #[gpui::test]
2387    fn test_inlay_utf8_boundary_panic_fix(cx: &mut App) {
2388        init_test(cx);
2389
2390        // This test verifies that we handle UTF-8 character boundaries correctly
2391        // when splitting inlay text for highlighting. Previously, this would panic
2392        // when trying to split at byte 13, which is in the middle of the '…' character.
2393        //
2394        // See https://github.com/zed-industries/zed/issues/33641
2395        let buffer = MultiBuffer::build_simple("fn main() {}\n", cx);
2396        let (mut inlay_map, _) = InlayMap::new(buffer.read(cx).snapshot(cx));
2397
2398        // Create an inlay with text that contains a multi-byte character
2399        // The string "SortingDirec…" contains an ellipsis character '…' which is 3 bytes (E2 80 A6)
2400        let inlay_text = "SortingDirec…";
2401        let position = buffer.read(cx).snapshot(cx).anchor_before(Point::new(0, 5));
2402
2403        let inlay = Inlay {
2404            id: InlayId::Hint(0),
2405            position,
2406            content: InlayContent::Text(text::Rope::from(inlay_text)),
2407        };
2408
2409        let (inlay_snapshot, _) = inlay_map.splice(&[], vec![inlay]);
2410
2411        // Create highlights that request a split at byte 13, which is in the middle
2412        // of the '…' character (bytes 12..15). We include the full character.
2413        let inlay_highlights = create_inlay_highlights(InlayId::Hint(0), 0..13, position);
2414
2415        let highlights = crate::display_map::Highlights {
2416            text_highlights: None,
2417            inlay_highlights: Some(&inlay_highlights),
2418            semantic_token_highlights: None,
2419            styles: crate::display_map::HighlightStyles::default(),
2420        };
2421
2422        // Collect chunks - this previously would panic
2423        let chunks: Vec<_> = inlay_snapshot
2424            .chunks(
2425                InlayOffset(MultiBufferOffset(0))..inlay_snapshot.len(),
2426                LanguageAwareStyling {
2427                    tree_sitter: false,
2428                    diagnostics: false,
2429                },
2430                highlights,
2431            )
2432            .collect();
2433
2434        // Verify the chunks are correct
2435        let full_text: String = chunks.iter().map(|c| c.chunk.text).collect();
2436        assert_eq!(full_text, "fn maSortingDirec…in() {}\n");
2437
2438        // Verify the highlighted portion includes the complete ellipsis character
2439        let highlighted_chunks: Vec<_> = chunks
2440            .iter()
2441            .filter(|c| c.chunk.highlight_style.is_some() && c.chunk.is_inlay)
2442            .collect();
2443
2444        assert_eq!(highlighted_chunks.len(), 1);
2445        assert_eq!(highlighted_chunks[0].chunk.text, "SortingDirec…");
2446    }
2447
2448    #[gpui::test]
2449    fn test_inlay_utf8_boundaries(cx: &mut App) {
2450        init_test(cx);
2451
2452        struct TestCase {
2453            inlay_text: &'static str,
2454            highlight_range: Range<usize>,
2455            expected_highlighted: &'static str,
2456            description: &'static str,
2457        }
2458
2459        let test_cases = vec![
2460            TestCase {
2461                inlay_text: "Hello👋World",
2462                highlight_range: 0..7,
2463                expected_highlighted: "Hello👋",
2464                description: "Emoji boundary - rounds up to include full emoji",
2465            },
2466            TestCase {
2467                inlay_text: "Test→End",
2468                highlight_range: 0..5,
2469                expected_highlighted: "Test→",
2470                description: "Arrow boundary - rounds up to include full arrow",
2471            },
2472            TestCase {
2473                inlay_text: "café",
2474                highlight_range: 0..4,
2475                expected_highlighted: "café",
2476                description: "Accented char boundary - rounds up to include full é",
2477            },
2478            TestCase {
2479                inlay_text: "🎨🎭🎪",
2480                highlight_range: 0..5,
2481                expected_highlighted: "🎨🎭",
2482                description: "Multiple emojis - partial highlight",
2483            },
2484            TestCase {
2485                inlay_text: "普通话",
2486                highlight_range: 0..4,
2487                expected_highlighted: "普通",
2488                description: "Chinese characters - partial highlight",
2489            },
2490            TestCase {
2491                inlay_text: "Hello",
2492                highlight_range: 0..2,
2493                expected_highlighted: "He",
2494                description: "ASCII only - no adjustment needed",
2495            },
2496            TestCase {
2497                inlay_text: "👋",
2498                highlight_range: 0..1,
2499                expected_highlighted: "👋",
2500                description: "Single emoji - partial byte range includes whole char",
2501            },
2502            TestCase {
2503                inlay_text: "Test",
2504                highlight_range: 0..0,
2505                expected_highlighted: "",
2506                description: "Empty range",
2507            },
2508            TestCase {
2509                inlay_text: "🎨ABC",
2510                highlight_range: 2..5,
2511                expected_highlighted: "A",
2512                description: "Range starting mid-emoji skips the emoji",
2513            },
2514        ];
2515
2516        for test_case in test_cases {
2517            let buffer = MultiBuffer::build_simple("test", cx);
2518            let (mut inlay_map, _) = InlayMap::new(buffer.read(cx).snapshot(cx));
2519            let position = buffer.read(cx).snapshot(cx).anchor_before(Point::new(0, 2));
2520
2521            let inlay = Inlay {
2522                id: InlayId::Hint(0),
2523                position,
2524                content: InlayContent::Text(text::Rope::from(test_case.inlay_text)),
2525            };
2526
2527            let (inlay_snapshot, _) = inlay_map.splice(&[], vec![inlay]);
2528            let inlay_highlights = create_inlay_highlights(
2529                InlayId::Hint(0),
2530                test_case.highlight_range.clone(),
2531                position,
2532            );
2533
2534            let highlights = crate::display_map::Highlights {
2535                text_highlights: None,
2536                inlay_highlights: Some(&inlay_highlights),
2537                semantic_token_highlights: None,
2538                styles: crate::display_map::HighlightStyles::default(),
2539            };
2540
2541            let chunks: Vec<_> = inlay_snapshot
2542                .chunks(
2543                    InlayOffset(MultiBufferOffset(0))..inlay_snapshot.len(),
2544                    LanguageAwareStyling {
2545                        tree_sitter: false,
2546                        diagnostics: false,
2547                    },
2548                    highlights,
2549                )
2550                .collect();
2551
2552            // Verify we got chunks and they total to the expected text
2553            let full_text: String = chunks.iter().map(|c| c.chunk.text).collect();
2554            assert_eq!(
2555                full_text,
2556                format!("te{}st", test_case.inlay_text),
2557                "Full text mismatch for case: {}",
2558                test_case.description
2559            );
2560
2561            // Verify that the highlighted portion matches expectations
2562            let highlighted_text: String = chunks
2563                .iter()
2564                .filter(|c| c.chunk.highlight_style.is_some() && c.chunk.is_inlay)
2565                .map(|c| c.chunk.text)
2566                .collect();
2567            assert_eq!(
2568                highlighted_text, test_case.expected_highlighted,
2569                "Highlighted text mismatch for case: {} (text: '{}', range: {:?})",
2570                test_case.description, test_case.inlay_text, test_case.highlight_range
2571            );
2572        }
2573    }
2574}
2575
Served at tenant.openagents/omega Member data and write actions are omitted.