inlay_map.rs

  1#![allow(unused)]
  2// TODO kb
  3
  4use std::{
  5    cmp::{self, Reverse},
  6    ops::{Add, AddAssign, Range, Sub},
  7    sync::atomic::{self, AtomicUsize},
  8};
  9
 10use crate::{Anchor, ExcerptId, InlayHintLocation, MultiBufferSnapshot, ToOffset, ToPoint};
 11
 12use super::{
 13    suggestion_map::{
 14        SuggestionBufferRows, SuggestionChunks, SuggestionEdit, SuggestionOffset, SuggestionPoint,
 15        SuggestionSnapshot,
 16    },
 17    TextHighlights,
 18};
 19use collections::{BTreeMap, HashMap, HashSet};
 20use gpui::fonts::HighlightStyle;
 21use language::{Chunk, Edit, Point, Rope, TextSummary};
 22use parking_lot::Mutex;
 23use project::InlayHint;
 24use rand::Rng;
 25use sum_tree::{Bias, Cursor, SumTree};
 26use util::post_inc;
 27
 28#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
 29pub struct InlayId(usize);
 30
 31pub struct InlayMap {
 32    snapshot: Mutex<InlaySnapshot>,
 33    next_inlay_id: usize,
 34    pub(super) inlays: HashMap<InlayId, (InlayHintLocation, Inlay)>,
 35}
 36
 37#[derive(Clone)]
 38pub struct InlaySnapshot {
 39    // TODO kb merge these two together?
 40    pub suggestion_snapshot: SuggestionSnapshot,
 41    transforms: SumTree<Transform>,
 42    pub version: usize,
 43}
 44
 45#[derive(Clone, Debug)]
 46enum Transform {
 47    Isomorphic(TextSummary),
 48    Inlay(Inlay),
 49}
 50
 51impl Transform {
 52    fn is_inlay(&self) -> bool {
 53        matches!(self, Self::Inlay(_))
 54    }
 55}
 56
 57impl sum_tree::Item for Transform {
 58    type Summary = TransformSummary;
 59
 60    fn summary(&self) -> Self::Summary {
 61        match self {
 62            Transform::Isomorphic(summary) => TransformSummary {
 63                input: summary.clone(),
 64                output: summary.clone(),
 65            },
 66            Transform::Inlay(inlay) => TransformSummary {
 67                input: TextSummary::default(),
 68                output: inlay.properties.text.summary(),
 69            },
 70        }
 71    }
 72}
 73
 74#[derive(Clone, Debug, Default)]
 75struct TransformSummary {
 76    input: TextSummary,
 77    output: TextSummary,
 78}
 79
 80impl sum_tree::Summary for TransformSummary {
 81    type Context = ();
 82
 83    fn add_summary(&mut self, other: &Self, _: &()) {
 84        self.input += &other.input;
 85        self.output += &other.output;
 86    }
 87}
 88
 89pub type InlayEdit = Edit<InlayOffset>;
 90
 91#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 92pub struct InlayOffset(pub usize);
 93
 94impl Add for InlayOffset {
 95    type Output = Self;
 96
 97    fn add(self, rhs: Self) -> Self::Output {
 98        Self(self.0 + rhs.0)
 99    }
100}
101
102impl Sub for InlayOffset {
103    type Output = Self;
104
105    fn sub(self, rhs: Self) -> Self::Output {
106        Self(self.0 - rhs.0)
107    }
108}
109
110impl AddAssign for InlayOffset {
111    fn add_assign(&mut self, rhs: Self) {
112        self.0 += rhs.0;
113    }
114}
115
116impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
117    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
118        self.0 += &summary.output.len;
119    }
120}
121
122impl<'a> sum_tree::Dimension<'a, TransformSummary> for SuggestionOffset {
123    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
124        self.0 += &summary.input.len;
125    }
126}
127
128#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
129pub struct InlayPoint(pub Point);
130
131impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
132    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
133        self.0 += &summary.output.lines;
134    }
135}
136
137impl<'a> sum_tree::Dimension<'a, TransformSummary> for SuggestionPoint {
138    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
139        self.0 += &summary.input.lines;
140    }
141}
142
143#[derive(Clone)]
144pub struct InlayBufferRows<'a> {
145    suggestion_rows: SuggestionBufferRows<'a>,
146}
147
148pub struct InlayChunks<'a> {
149    transforms: Cursor<'a, Transform, (InlayOffset, SuggestionOffset)>,
150    suggestion_chunks: SuggestionChunks<'a>,
151    suggestion_chunk: Option<Chunk<'a>>,
152    inlay_chunks: Option<text::Chunks<'a>>,
153    output_offset: InlayOffset,
154    max_output_offset: InlayOffset,
155}
156
157#[derive(Debug, Clone)]
158pub struct Inlay {
159    pub(super) id: InlayId,
160    pub(super) properties: InlayProperties,
161}
162
163#[derive(Debug, Clone)]
164pub struct InlayProperties {
165    pub(super) position: Anchor,
166    pub(super) text: Rope,
167}
168
169impl<'a> Iterator for InlayChunks<'a> {
170    type Item = Chunk<'a>;
171
172    fn next(&mut self) -> Option<Self::Item> {
173        if self.output_offset == self.max_output_offset {
174            return None;
175        }
176
177        let chunk = match self.transforms.item()? {
178            Transform::Isomorphic(transform) => {
179                let chunk = self
180                    .suggestion_chunk
181                    .get_or_insert_with(|| self.suggestion_chunks.next().unwrap());
182                if chunk.text.is_empty() {
183                    *chunk = self.suggestion_chunks.next().unwrap();
184                }
185
186                let (prefix, suffix) = chunk
187                    .text
188                    .split_at(cmp::min(transform.len, chunk.text.len()));
189                chunk.text = suffix;
190                self.output_offset.0 += prefix.len();
191                Chunk {
192                    text: prefix,
193                    ..chunk.clone()
194                }
195            }
196            Transform::Inlay(inlay) => {
197                let inlay_chunks = self.inlay_chunks.get_or_insert_with(|| {
198                    let start = self.output_offset - self.transforms.start().0;
199                    let end = cmp::min(self.max_output_offset, self.transforms.end(&()).0)
200                        - self.transforms.start().0;
201                    inlay.properties.text.chunks_in_range(start.0..end.0)
202                });
203
204                let chunk = inlay_chunks.next().unwrap();
205                self.output_offset.0 += chunk.len();
206                Chunk {
207                    text: chunk,
208                    ..Default::default()
209                }
210            }
211        };
212
213        if self.output_offset == self.transforms.end(&()).0 {
214            self.transforms.next(&());
215        }
216
217        Some(chunk)
218    }
219}
220
221impl<'a> Iterator for InlayBufferRows<'a> {
222    type Item = Option<u32>;
223
224    fn next(&mut self) -> Option<Self::Item> {
225        self.suggestion_rows.next()
226    }
227}
228
229impl InlayPoint {
230    pub fn new(row: u32, column: u32) -> Self {
231        Self(Point::new(row, column))
232    }
233
234    pub fn row(self) -> u32 {
235        self.0.row
236    }
237
238    pub fn column(self) -> u32 {
239        self.0.column
240    }
241}
242
243impl InlayMap {
244    pub fn new(suggestion_snapshot: SuggestionSnapshot) -> (Self, InlaySnapshot) {
245        let snapshot = InlaySnapshot {
246            suggestion_snapshot: suggestion_snapshot.clone(),
247            version: 0,
248            transforms: SumTree::from_item(
249                Transform::Isomorphic(suggestion_snapshot.text_summary()),
250                &(),
251            ),
252        };
253
254        (
255            Self {
256                snapshot: Mutex::new(snapshot.clone()),
257                next_inlay_id: 0,
258                inlays: HashMap::default(),
259            },
260            snapshot,
261        )
262    }
263
264    pub fn sync(
265        &self,
266        suggestion_snapshot: SuggestionSnapshot,
267        suggestion_edits: Vec<SuggestionEdit>,
268    ) -> (InlaySnapshot, Vec<InlayEdit>) {
269        let mut snapshot = self.snapshot.lock();
270
271        let mut new_snapshot = snapshot.clone();
272        if new_snapshot.suggestion_snapshot.version != suggestion_snapshot.version {
273            new_snapshot.version += 1;
274        }
275
276        new_snapshot.transforms = SumTree::new();
277        let mut cursor = snapshot.transforms.cursor::<SuggestionOffset>();
278        let mut suggestion_edits = suggestion_edits.iter().peekable();
279
280        while let Some(suggestion_edit) = suggestion_edits.next() {
281            if suggestion_edit.old.start >= *cursor.start() {
282                new_snapshot.transforms.push_tree(
283                    cursor.slice(&suggestion_edit.old.start, Bias::Right, &()),
284                    &(),
285                );
286            }
287
288            if suggestion_edit.old.end > cursor.end(&()) {
289                cursor.seek_forward(&suggestion_edit.old.end, Bias::Right, &());
290            }
291
292            let transform_start = SuggestionOffset(new_snapshot.transforms.summary().input.len);
293            let mut transform_end = suggestion_edit.new.end;
294            if suggestion_edits
295                .peek()
296                .map_or(true, |edit| edit.old.start > cursor.end(&()))
297            {
298                transform_end += cursor.end(&()) - suggestion_edit.old.end;
299                cursor.next(&());
300            }
301            push_isomorphic(
302                &mut new_snapshot.transforms,
303                suggestion_snapshot.text_summary_for_range(
304                    suggestion_snapshot.to_point(transform_start)
305                        ..suggestion_snapshot.to_point(transform_end),
306                ),
307            );
308        }
309
310        new_snapshot.transforms.push_tree(cursor.suffix(&()), &());
311        new_snapshot.suggestion_snapshot = suggestion_snapshot;
312        drop(cursor);
313
314        let mut inlay_edits = Vec::new();
315        for suggestion_edit in suggestion_edits {
316            let old = snapshot.to_inlay_offset(suggestion_edit.old.start)
317                ..snapshot.to_inlay_offset(suggestion_edit.old.end);
318            let new = new_snapshot.to_inlay_offset(suggestion_edit.new.start)
319                ..new_snapshot.to_inlay_offset(suggestion_edit.new.end);
320            inlay_edits.push(Edit { old, new })
321        }
322
323        *snapshot = new_snapshot.clone();
324        (new_snapshot, inlay_edits)
325    }
326
327    pub fn splice(
328        &mut self,
329        to_remove: HashSet<InlayId>,
330        to_insert: Vec<(InlayHintLocation, InlayProperties)>,
331    ) -> (InlaySnapshot, Vec<InlayEdit>, Vec<InlayId>) {
332        let mut snapshot = self.snapshot.lock();
333
334        let mut inlays = BTreeMap::new();
335        let mut new_ids = Vec::new();
336        for (location, properties) in to_insert {
337            let inlay = Inlay {
338                id: InlayId(post_inc(&mut self.next_inlay_id)),
339                properties,
340            };
341            self.inlays.insert(inlay.id, (location, inlay.clone()));
342            new_ids.push(inlay.id);
343
344            let buffer_point = inlay
345                .properties
346                .position
347                .to_point(snapshot.buffer_snapshot());
348            let fold_point = snapshot
349                .suggestion_snapshot
350                .fold_snapshot
351                .to_fold_point(buffer_point, Bias::Left);
352            let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point);
353            let inlay_point = snapshot.to_inlay_point(suggestion_point);
354
355            inlays.insert((inlay_point, Reverse(inlay.id)), Some(inlay));
356        }
357
358        for inlay_id in to_remove {
359            if let Some((_, inlay)) = self.inlays.remove(&inlay_id) {
360                let buffer_point = inlay
361                    .properties
362                    .position
363                    .to_point(snapshot.buffer_snapshot());
364                let fold_point = snapshot
365                    .suggestion_snapshot
366                    .fold_snapshot
367                    .to_fold_point(buffer_point, Bias::Left);
368                let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point);
369                let inlay_point = snapshot.to_inlay_point(suggestion_point);
370                inlays.insert((inlay_point, Reverse(inlay.id)), None);
371            }
372        }
373
374        let mut new_transforms = SumTree::new();
375        let mut cursor = snapshot
376            .transforms
377            .cursor::<(InlayPoint, SuggestionPoint)>();
378        for ((inlay_point, inlay_id), inlay) in inlays {
379            new_transforms.push_tree(cursor.slice(&inlay_point, Bias::Right, &()), &());
380            while let Some(transform) = cursor.item() {
381                match transform {
382                    Transform::Isomorphic(_) => break,
383                    Transform::Inlay(inlay) => {
384                        if inlay.id > inlay_id.0 {
385                            new_transforms.push(transform.clone(), &());
386                            cursor.next(&());
387                        } else {
388                            if inlay.id == inlay_id.0 {
389                                cursor.next(&());
390                            }
391                            break;
392                        }
393                    }
394                }
395            }
396
397            if let Some(inlay) = inlay {
398                if let Some(Transform::Isomorphic(transform)) = cursor.item() {
399                    let prefix = inlay_point.0 - cursor.start().0 .0;
400                    if !prefix.is_zero() {
401                        let prefix_suggestion_start = cursor.start().1;
402                        let prefix_suggestion_end = SuggestionPoint(cursor.start().1 .0 + prefix);
403                        new_transforms.push(
404                            Transform::Isomorphic(
405                                snapshot.suggestion_snapshot.text_summary_for_range(
406                                    prefix_suggestion_start..prefix_suggestion_end,
407                                ),
408                            ),
409                            &(),
410                        );
411                    }
412
413                    new_transforms.push(Transform::Inlay(inlay), &());
414
415                    let suffix_suggestion_start = SuggestionPoint(cursor.start().1 .0 + prefix);
416                    let suffix_suggestion_end = cursor.end(&()).1;
417                    new_transforms.push(
418                        Transform::Isomorphic(snapshot.suggestion_snapshot.text_summary_for_range(
419                            suffix_suggestion_start..suffix_suggestion_end,
420                        )),
421                        &(),
422                    );
423
424                    cursor.next(&());
425                } else {
426                    new_transforms.push(Transform::Inlay(inlay), &());
427                }
428            }
429        }
430
431        new_transforms.push_tree(cursor.suffix(&()), &());
432        drop(cursor);
433        snapshot.transforms = new_transforms;
434        snapshot.version += 1;
435
436        (snapshot.clone(), Vec::new(), new_ids)
437    }
438}
439
440impl InlaySnapshot {
441    pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
442        self.suggestion_snapshot.buffer_snapshot()
443    }
444
445    pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
446        let mut cursor = self
447            .transforms
448            .cursor::<(InlayOffset, (InlayPoint, SuggestionOffset))>();
449        cursor.seek(&offset, Bias::Right, &());
450        let overshoot = offset.0 - cursor.start().0 .0;
451        match cursor.item() {
452            Some(Transform::Isomorphic(transform)) => {
453                let suggestion_offset_start = cursor.start().1 .1;
454                let suggestion_offset_end = SuggestionOffset(suggestion_offset_start.0 + overshoot);
455                let suggestion_start = self.suggestion_snapshot.to_point(suggestion_offset_start);
456                let suggestion_end = self.suggestion_snapshot.to_point(suggestion_offset_end);
457                InlayPoint(cursor.start().1 .0 .0 + (suggestion_end.0 - suggestion_start.0))
458            }
459            Some(Transform::Inlay(inlay)) => {
460                let overshoot = inlay.properties.text.offset_to_point(overshoot);
461                InlayPoint(cursor.start().1 .0 .0 + overshoot)
462            }
463            None => self.max_point(),
464        }
465    }
466
467    pub fn len(&self) -> InlayOffset {
468        InlayOffset(self.transforms.summary().output.len)
469    }
470
471    pub fn max_point(&self) -> InlayPoint {
472        InlayPoint(self.transforms.summary().output.lines)
473    }
474
475    pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
476        let mut cursor = self
477            .transforms
478            .cursor::<(InlayPoint, (InlayOffset, SuggestionPoint))>();
479        cursor.seek(&point, Bias::Right, &());
480        let overshoot = point.0 - cursor.start().0 .0;
481        match cursor.item() {
482            Some(Transform::Isomorphic(transform)) => {
483                let suggestion_point_start = cursor.start().1 .1;
484                let suggestion_point_end = SuggestionPoint(suggestion_point_start.0 + overshoot);
485                let suggestion_start = self.suggestion_snapshot.to_offset(suggestion_point_start);
486                let suggestion_end = self.suggestion_snapshot.to_offset(suggestion_point_end);
487                InlayOffset(cursor.start().1 .0 .0 + (suggestion_end.0 - suggestion_start.0))
488            }
489            Some(Transform::Inlay(inlay)) => {
490                let overshoot = inlay.properties.text.point_to_offset(overshoot);
491                InlayOffset(cursor.start().1 .0 .0 + overshoot)
492            }
493            None => self.len(),
494        }
495    }
496
497    pub fn chars_at(&self, start: InlayPoint) -> impl '_ + Iterator<Item = char> {
498        self.chunks(self.to_offset(start)..self.len(), false, None, None)
499            .flat_map(|chunk| chunk.text.chars())
500    }
501
502    pub fn to_suggestion_point(&self, point: InlayPoint) -> SuggestionPoint {
503        let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>();
504        cursor.seek(&point, Bias::Right, &());
505        let overshoot = point.0 - cursor.start().0 .0;
506        match cursor.item() {
507            Some(Transform::Isomorphic(transform)) => {
508                SuggestionPoint(cursor.start().1 .0 + overshoot)
509            }
510            Some(Transform::Inlay(inlay)) => cursor.start().1,
511            None => self.suggestion_snapshot.max_point(),
512        }
513    }
514
515    pub fn to_suggestion_offset(&self, offset: InlayOffset) -> SuggestionOffset {
516        let mut cursor = self.transforms.cursor::<(InlayOffset, SuggestionOffset)>();
517        cursor.seek(&offset, Bias::Right, &());
518        match cursor.item() {
519            Some(Transform::Isomorphic(transform)) => {
520                let overshoot = offset - cursor.start().0;
521                cursor.start().1 + SuggestionOffset(overshoot.0)
522            }
523            Some(Transform::Inlay(inlay)) => cursor.start().1,
524            None => self.suggestion_snapshot.len(),
525        }
526    }
527
528    pub fn to_inlay_offset(&self, offset: SuggestionOffset) -> InlayOffset {
529        let mut cursor = self.transforms.cursor::<(SuggestionOffset, InlayOffset)>();
530        // TODO kb is the bias right? should we have an external one instead?
531        cursor.seek(&offset, Bias::Right, &());
532        let overshoot = offset.0 - cursor.start().0 .0;
533        match cursor.item() {
534            Some(Transform::Isomorphic(transform)) => InlayOffset(cursor.start().1 .0 + overshoot),
535            Some(Transform::Inlay(inlay)) => cursor.start().1,
536            None => self.len(),
537        }
538    }
539
540    pub fn to_inlay_point(&self, point: SuggestionPoint) -> InlayPoint {
541        let mut cursor = self.transforms.cursor::<(SuggestionPoint, InlayPoint)>();
542        // TODO kb is the bias right? should we have an external one instead?
543        cursor.seek(&point, Bias::Right, &());
544        let overshoot = point.0 - cursor.start().0 .0;
545        match cursor.item() {
546            Some(Transform::Isomorphic(transform)) => InlayPoint(cursor.start().1 .0 + overshoot),
547            Some(Transform::Inlay(inlay)) => cursor.start().1,
548            None => self.max_point(),
549        }
550    }
551
552    pub fn clip_point(&self, point: InlayPoint, bias: Bias) -> InlayPoint {
553        let mut cursor = self.transforms.cursor::<InlayPoint>();
554        cursor.seek(&point, bias, &());
555        match cursor.item() {
556            Some(Transform::Isomorphic(_)) => return point,
557            Some(Transform::Inlay(_)) => {}
558            None => cursor.prev(&()),
559        }
560
561        while cursor
562            .item()
563            .map_or(false, |transform| transform.is_inlay())
564        {
565            match bias {
566                Bias::Left => cursor.prev(&()),
567                Bias::Right => cursor.next(&()),
568            }
569        }
570
571        match bias {
572            Bias::Left => cursor.end(&()),
573            Bias::Right => *cursor.start(),
574        }
575    }
576
577    pub fn text_summary_for_range(&self, range: Range<InlayPoint>) -> TextSummary {
578        let mut summary = TextSummary::default();
579
580        let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>();
581        cursor.seek(&range.start, Bias::Right, &());
582
583        let overshoot = range.start.0 - cursor.start().0 .0;
584        match cursor.item() {
585            Some(Transform::Isomorphic(transform)) => {
586                let suggestion_start = cursor.start().1 .0;
587                let suffix_start = SuggestionPoint(suggestion_start + overshoot);
588                let suffix_end = SuggestionPoint(
589                    suggestion_start
590                        + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0),
591                );
592                summary = self
593                    .suggestion_snapshot
594                    .text_summary_for_range(suffix_start..suffix_end);
595                cursor.next(&());
596            }
597            Some(Transform::Inlay(inlay)) => {
598                let text = &inlay.properties.text;
599                let suffix_start = text.point_to_offset(overshoot);
600                let suffix_end = text.point_to_offset(
601                    cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0,
602                );
603                summary = text.cursor(suffix_start).summary(suffix_end);
604                cursor.next(&());
605            }
606            None => {}
607        }
608
609        if range.end > cursor.start().0 {
610            summary += cursor
611                .summary::<_, TransformSummary>(&range.end, Bias::Right, &())
612                .output;
613
614            let overshoot = range.end.0 - cursor.start().0 .0;
615            match cursor.item() {
616                Some(Transform::Isomorphic(transform)) => {
617                    let prefix_start = cursor.start().1;
618                    let prefix_end = SuggestionPoint(prefix_start.0 + overshoot);
619                    summary += self
620                        .suggestion_snapshot
621                        .text_summary_for_range(prefix_start..prefix_end);
622                }
623                Some(Transform::Inlay(inlay)) => {
624                    let text = &inlay.properties.text;
625                    let prefix_end = text.point_to_offset(overshoot);
626                    summary += text.cursor(0).summary::<TextSummary>(prefix_end);
627                }
628                None => {}
629            }
630        }
631
632        summary
633    }
634
635    pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> {
636        InlayBufferRows {
637            suggestion_rows: self.suggestion_snapshot.buffer_rows(row),
638        }
639    }
640
641    pub fn line_len(&self, row: u32) -> u32 {
642        let line_start = self.to_offset(InlayPoint::new(row, 0)).0;
643        let line_end = if row >= self.max_point().row() {
644            self.len().0
645        } else {
646            self.to_offset(InlayPoint::new(row + 1, 0)).0 - 1
647        };
648        (line_end - line_start) as u32
649    }
650
651    pub fn chunks<'a>(
652        &'a self,
653        range: Range<InlayOffset>,
654        language_aware: bool,
655        text_highlights: Option<&'a TextHighlights>,
656        suggestion_highlight: Option<HighlightStyle>,
657    ) -> InlayChunks<'a> {
658        let mut cursor = self.transforms.cursor::<(InlayOffset, SuggestionOffset)>();
659        cursor.seek(&range.start, Bias::Right, &());
660
661        let suggestion_range =
662            self.to_suggestion_offset(range.start)..self.to_suggestion_offset(range.end);
663        let suggestion_chunks = self.suggestion_snapshot.chunks(
664            suggestion_range,
665            language_aware,
666            text_highlights,
667            suggestion_highlight,
668        );
669
670        InlayChunks {
671            transforms: cursor,
672            suggestion_chunks,
673            inlay_chunks: None,
674            suggestion_chunk: None,
675            output_offset: range.start,
676            max_output_offset: range.end,
677        }
678    }
679
680    #[cfg(test)]
681    pub fn text(&self) -> String {
682        self.chunks(Default::default()..self.len(), false, None, None)
683            .map(|chunk| chunk.text)
684            .collect()
685    }
686}
687
688fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
689    let mut summary = Some(summary);
690    sum_tree.update_last(
691        |transform| {
692            if let Transform::Isomorphic(transform) = transform {
693                *transform += summary.take().unwrap();
694            }
695        },
696        &(),
697    );
698
699    if let Some(summary) = summary {
700        sum_tree.push(Transform::Isomorphic(summary), &());
701    }
702}
703
704#[cfg(test)]
705mod tests {
706    use super::*;
707    use crate::{
708        display_map::{fold_map::FoldMap, suggestion_map::SuggestionMap},
709        MultiBuffer,
710    };
711    use gpui::AppContext;
712
713    #[gpui::test]
714    fn test_basic_inlays(cx: &mut AppContext) {
715        let buffer = MultiBuffer::build_simple("abcdefghi", cx);
716        let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
717        let (mut fold_map, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
718        let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone());
719        let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone());
720        assert_eq!(inlay_snapshot.text(), "abcdefghi");
721
722        let (inlay_snapshot, _, inlay_ids) = inlay_map.splice(
723            HashSet::default(),
724            vec![(
725                InlayHintLocation {
726                    buffer_id: 0,
727                    excerpt_id: ExcerptId::default(),
728                },
729                InlayProperties {
730                    position: buffer.read(cx).read(cx).anchor_before(3),
731                    text: "|123|".into(),
732                },
733            )],
734        );
735        assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
736
737        buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "XYZ")], None, cx));
738        let (fold_snapshot, fold_edits) = fold_map.read(
739            buffer.read(cx).snapshot(cx),
740            buffer_edits.consume().into_inner(),
741        );
742        let (suggestion_snapshot, suggestion_edits) =
743            suggestion_map.sync(fold_snapshot.clone(), fold_edits);
744        let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
745        assert_eq!(inlay_snapshot.text(), "XYZabc|123|defghi");
746
747        //////// case: folding and unfolding the text should hine and then return the hint back
748        let (mut fold_map_writer, _, _) = fold_map.write(
749            buffer.read(cx).snapshot(cx),
750            buffer_edits.consume().into_inner(),
751        );
752        let (fold_snapshot, fold_edits) = fold_map_writer.fold([4..8]);
753        let (suggestion_snapshot, suggestion_edits) =
754            suggestion_map.sync(fold_snapshot.clone(), fold_edits);
755        let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
756        assert_eq!(inlay_snapshot.text(), "XYZa⋯fghi");
757
758        let (fold_snapshot, fold_edits) = fold_map_writer.unfold([4..8], false);
759        let (suggestion_snapshot, suggestion_edits) =
760            suggestion_map.sync(fold_snapshot.clone(), fold_edits);
761        let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
762        assert_eq!(inlay_snapshot.text(), "XYZabc|123|defghi");
763
764        ////////// case: replacing the anchor that got the hint: it should disappear
765        buffer.update(cx, |buffer, cx| buffer.edit([(2..3, "C")], None, cx));
766        let (fold_snapshot, fold_edits) = fold_map.read(
767            buffer.read(cx).snapshot(cx),
768            buffer_edits.consume().into_inner(),
769        );
770        let (suggestion_snapshot, suggestion_edits) =
771            suggestion_map.sync(fold_snapshot.clone(), fold_edits);
772        let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
773        assert_eq!(inlay_snapshot.text(), "XYZabCdefghi");
774    }
775}