inlay_map.rs

  1#![allow(unused)]
  2// TODO kb
  3
  4use std::{
  5    cmp::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, 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    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)]
 46enum Transform {
 47    Isomorphic(TextSummary),
 48    Inlay(Inlay),
 49}
 50
 51impl sum_tree::Item for Transform {
 52    type Summary = TransformSummary;
 53
 54    fn summary(&self) -> Self::Summary {
 55        match self {
 56            Transform::Isomorphic(summary) => TransformSummary {
 57                input: summary.clone(),
 58                output: summary.clone(),
 59            },
 60            Transform::Inlay(inlay) => TransformSummary {
 61                input: TextSummary::default(),
 62                output: inlay.properties.text.summary(),
 63            },
 64        }
 65    }
 66}
 67
 68#[derive(Clone, Debug, Default)]
 69struct TransformSummary {
 70    input: TextSummary,
 71    output: TextSummary,
 72}
 73
 74impl sum_tree::Summary for TransformSummary {
 75    type Context = ();
 76
 77    fn add_summary(&mut self, other: &Self, _: &()) {
 78        self.input += &other.input;
 79        self.output += &other.output;
 80    }
 81}
 82
 83pub type InlayEdit = Edit<InlayOffset>;
 84
 85#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 86pub struct InlayOffset(pub usize);
 87
 88impl Add for InlayOffset {
 89    type Output = Self;
 90
 91    fn add(self, rhs: Self) -> Self::Output {
 92        Self(self.0 + rhs.0)
 93    }
 94}
 95
 96impl Sub for InlayOffset {
 97    type Output = Self;
 98
 99    fn sub(self, rhs: Self) -> Self::Output {
100        Self(self.0 - rhs.0)
101    }
102}
103
104impl AddAssign for InlayOffset {
105    fn add_assign(&mut self, rhs: Self) {
106        self.0 += rhs.0;
107    }
108}
109
110#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
111pub struct InlayPoint(pub Point);
112
113impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
114    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
115        self.0 += &summary.output.lines;
116    }
117}
118
119impl<'a> sum_tree::Dimension<'a, TransformSummary> for SuggestionPoint {
120    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
121        self.0 += &summary.input.lines;
122    }
123}
124
125#[derive(Clone)]
126pub struct InlayBufferRows<'a> {
127    suggestion_rows: SuggestionBufferRows<'a>,
128}
129
130pub struct InlayChunks<'a> {
131    suggestion_chunks: SuggestionChunks<'a>,
132}
133
134#[derive(Debug, Clone)]
135pub struct Inlay {
136    pub(super) id: InlayId,
137    pub(super) properties: InlayProperties,
138}
139
140#[derive(Debug, Clone)]
141pub struct InlayProperties {
142    pub(super) position: Anchor,
143    pub(super) text: Rope,
144}
145
146impl<'a> Iterator for InlayChunks<'a> {
147    type Item = Chunk<'a>;
148
149    fn next(&mut self) -> Option<Self::Item> {
150        self.suggestion_chunks.next()
151    }
152}
153
154impl<'a> Iterator for InlayBufferRows<'a> {
155    type Item = Option<u32>;
156
157    fn next(&mut self) -> Option<Self::Item> {
158        self.suggestion_rows.next()
159    }
160}
161
162impl InlayPoint {
163    pub fn new(row: u32, column: u32) -> Self {
164        Self(Point::new(row, column))
165    }
166
167    pub fn row(self) -> u32 {
168        self.0.row
169    }
170
171    pub fn column(self) -> u32 {
172        self.0.column
173    }
174}
175
176impl InlayMap {
177    pub fn new(suggestion_snapshot: SuggestionSnapshot) -> (Self, InlaySnapshot) {
178        let snapshot = InlaySnapshot {
179            suggestion_snapshot: suggestion_snapshot.clone(),
180            version: 0,
181            transforms: SumTree::new(),
182        };
183
184        (
185            Self {
186                snapshot: Mutex::new(snapshot.clone()),
187                next_inlay_id: 0,
188                inlays: HashMap::default(),
189            },
190            snapshot,
191        )
192    }
193
194    pub fn sync(
195        &self,
196        suggestion_snapshot: SuggestionSnapshot,
197        suggestion_edits: Vec<SuggestionEdit>,
198    ) -> (InlaySnapshot, Vec<InlayEdit>) {
199        let mut snapshot = self.snapshot.lock();
200
201        if snapshot.suggestion_snapshot.version != suggestion_snapshot.version {
202            snapshot.version += 1;
203        }
204
205        let mut inlay_edits = Vec::new();
206
207        dbg!(self.inlays.len());
208
209        for suggestion_edit in suggestion_edits {
210            let old = suggestion_edit.old;
211            let new = suggestion_edit.new;
212            // TODO kb copied from suggestion_map
213            inlay_edits.push(InlayEdit {
214                old: InlayOffset(old.start.0)..InlayOffset(old.end.0),
215                new: InlayOffset(old.start.0)..InlayOffset(new.end.0),
216            })
217        }
218
219        snapshot.suggestion_snapshot = suggestion_snapshot;
220
221        (snapshot.clone(), inlay_edits)
222    }
223
224    pub fn splice(
225        &mut self,
226        to_remove: HashSet<InlayId>,
227        to_insert: Vec<(InlayHintLocation, InlayProperties)>,
228    ) -> (InlaySnapshot, Vec<InlayEdit>, Vec<InlayId>) {
229        let mut snapshot = self.snapshot.lock();
230
231        let mut inlays = BTreeMap::new();
232        let mut new_ids = Vec::new();
233        for (location, properties) in to_insert {
234            let inlay = Inlay {
235                id: InlayId(post_inc(&mut self.next_inlay_id)),
236                properties,
237            };
238            self.inlays.insert(inlay.id, (location, inlay.clone()));
239            new_ids.push(inlay.id);
240
241            let buffer_point = inlay
242                .properties
243                .position
244                .to_point(snapshot.buffer_snapshot());
245            let fold_point = snapshot
246                .suggestion_snapshot
247                .fold_snapshot
248                .to_fold_point(buffer_point, Bias::Left);
249            let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point);
250            let inlay_point = snapshot.to_inlay_point(suggestion_point);
251
252            inlays.insert((inlay_point, Reverse(inlay.id)), Some(inlay));
253        }
254
255        for inlay_id in to_remove {
256            if let Some((_, inlay)) = self.inlays.remove(&inlay_id) {
257                let buffer_point = inlay
258                    .properties
259                    .position
260                    .to_point(snapshot.buffer_snapshot());
261                let fold_point = snapshot
262                    .suggestion_snapshot
263                    .fold_snapshot
264                    .to_fold_point(buffer_point, Bias::Left);
265                let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point);
266                let inlay_point = snapshot.to_inlay_point(suggestion_point);
267                inlays.insert((inlay_point, Reverse(inlay.id)), None);
268            }
269        }
270
271        let mut new_transforms = SumTree::new();
272        let mut cursor = snapshot
273            .transforms
274            .cursor::<(InlayPoint, SuggestionPoint)>();
275        for ((inlay_point, inlay_id), inlay) in inlays {
276            new_transforms.push_tree(cursor.slice(&inlay_point, Bias::Right, &()), &());
277            while let Some(transform) = cursor.item() {
278                match transform {
279                    Transform::Isomorphic(_) => break,
280                    Transform::Inlay(inlay) => {
281                        if inlay.id > inlay_id.0 {
282                            new_transforms.push(transform.clone(), &());
283                            cursor.next(&());
284                        } else {
285                            if inlay.id == inlay_id.0 {
286                                cursor.next(&());
287                            }
288                            break;
289                        }
290                    }
291                }
292            }
293
294            if let Some(inlay) = inlay {
295                if let Some(Transform::Isomorphic(transform)) = cursor.item() {
296                    let prefix = inlay_point.0 - cursor.start().0 .0;
297                    if !prefix.is_zero() {
298                        let prefix_suggestion_start = cursor.start().1;
299                        let prefix_suggestion_end = SuggestionPoint(cursor.start().1 .0 + prefix);
300                        new_transforms.push(
301                            Transform::Isomorphic(
302                                snapshot.suggestion_snapshot.text_summary_for_range(
303                                    prefix_suggestion_start..prefix_suggestion_end,
304                                ),
305                            ),
306                            &(),
307                        );
308                    }
309
310                    new_transforms.push(Transform::Inlay(inlay), &());
311
312                    let suffix_suggestion_start = SuggestionPoint(cursor.start().1 .0 + prefix);
313                    let suffix_suggestion_end = cursor.end(&()).1;
314                    new_transforms.push(
315                        Transform::Isomorphic(snapshot.suggestion_snapshot.text_summary_for_range(
316                            suffix_suggestion_start..suffix_suggestion_end,
317                        )),
318                        &(),
319                    );
320
321                    cursor.next(&());
322                } else {
323                    new_transforms.push(Transform::Inlay(inlay), &());
324                }
325            }
326        }
327
328        new_transforms.push_tree(cursor.suffix(&()), &());
329        drop(cursor);
330        snapshot.transforms = new_transforms;
331        snapshot.version += 1;
332
333        (snapshot.clone(), Vec::new(), new_ids)
334    }
335}
336
337impl InlaySnapshot {
338    pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
339        // TODO kb copied from suggestion_map
340        self.suggestion_snapshot.buffer_snapshot()
341    }
342
343    pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
344        // TODO kb copied from suggestion_map
345        self.to_inlay_point(
346            self.suggestion_snapshot
347                .to_point(super::suggestion_map::SuggestionOffset(offset.0)),
348        )
349    }
350
351    pub fn max_point(&self) -> InlayPoint {
352        // TODO kb copied from suggestion_map
353        self.to_inlay_point(self.suggestion_snapshot.max_point())
354    }
355
356    pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
357        // TODO kb copied from suggestion_map
358        InlayOffset(
359            self.suggestion_snapshot
360                .to_offset(self.to_suggestion_point(point, Bias::Left))
361                .0,
362        )
363    }
364
365    pub fn chars_at(&self, start: InlayPoint) -> impl '_ + Iterator<Item = char> {
366        self.suggestion_snapshot
367            .chars_at(self.to_suggestion_point(start, Bias::Left))
368    }
369
370    // TODO kb what to do with bias?
371    pub fn to_suggestion_point(&self, point: InlayPoint, _: Bias) -> SuggestionPoint {
372        SuggestionPoint(point.0)
373    }
374
375    pub fn to_inlay_point(&self, point: SuggestionPoint) -> InlayPoint {
376        InlayPoint(point.0)
377    }
378
379    pub fn clip_point(&self, point: InlayPoint, bias: Bias) -> InlayPoint {
380        // TODO kb copied from suggestion_map
381        self.to_inlay_point(
382            self.suggestion_snapshot
383                .clip_point(self.to_suggestion_point(point, bias), bias),
384        )
385    }
386
387    pub fn text_summary_for_range(&self, range: Range<InlayPoint>) -> TextSummary {
388        // TODO kb copied from suggestion_map
389        self.suggestion_snapshot.text_summary_for_range(
390            self.to_suggestion_point(range.start, Bias::Left)
391                ..self.to_suggestion_point(range.end, Bias::Left),
392        )
393    }
394
395    pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> {
396        InlayBufferRows {
397            suggestion_rows: self.suggestion_snapshot.buffer_rows(row),
398        }
399    }
400
401    pub fn line_len(&self, row: u32) -> u32 {
402        // TODO kb copied from suggestion_map
403        self.suggestion_snapshot.line_len(row)
404    }
405
406    pub fn chunks<'a>(
407        &'a self,
408        range: Range<InlayOffset>,
409        language_aware: bool,
410        text_highlights: Option<&'a TextHighlights>,
411        suggestion_highlight: Option<HighlightStyle>,
412    ) -> InlayChunks<'a> {
413        // TODO kb copied from suggestion_map
414        InlayChunks {
415            suggestion_chunks: self.suggestion_snapshot.chunks(
416                SuggestionOffset(range.start.0)..SuggestionOffset(range.end.0),
417                language_aware,
418                text_highlights,
419                suggestion_highlight,
420            ),
421        }
422    }
423
424    #[cfg(test)]
425    pub fn text(&self) -> String {
426        // TODO kb copied from suggestion_map
427        self.suggestion_snapshot.text()
428    }
429}
430
431#[cfg(test)]
432mod tests {
433    use super::*;
434    use crate::{
435        display_map::{fold_map::FoldMap, suggestion_map::SuggestionMap},
436        MultiBuffer,
437    };
438    use gpui::AppContext;
439
440    #[gpui::test]
441    fn test_basic_inlays(cx: &mut AppContext) {
442        let buffer = MultiBuffer::build_simple("abcdefghi", cx);
443        let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
444        let (mut fold_map, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
445        let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone());
446        let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone());
447        assert_eq!(inlay_snapshot.text(), "abcdefghi");
448
449        let (inlay_snapshot, _, inlay_ids) = inlay_map.splice(
450            HashSet::default(),
451            vec![(
452                InlayHintLocation {
453                    buffer_id: 0,
454                    excerpt_id: ExcerptId::default(),
455                },
456                InlayProperties {
457                    position: buffer.read(cx).read(cx).anchor_before(3),
458                    text: "|123|".into(),
459                },
460            )],
461        );
462        assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
463
464        buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "XYZ")], None, cx));
465        let (fold_snapshot, fold_edits) = fold_map.read(
466            buffer.read(cx).snapshot(cx),
467            buffer_edits.consume().into_inner(),
468        );
469        let (suggestion_snapshot, suggestion_edits) =
470            suggestion_map.sync(fold_snapshot.clone(), fold_edits);
471        let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
472        assert_eq!(inlay_snapshot.text(), "XYZabc|123|defghi");
473
474        //////// case: folding and unfolding the text should hine and then return the hint back
475        let (mut fold_map_writer, _, _) = fold_map.write(
476            buffer.read(cx).snapshot(cx),
477            buffer_edits.consume().into_inner(),
478        );
479        let (fold_snapshot, fold_edits) = fold_map_writer.fold([4..8]);
480        let (suggestion_snapshot, suggestion_edits) =
481            suggestion_map.sync(fold_snapshot.clone(), fold_edits);
482        let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
483        assert_eq!(inlay_snapshot.text(), "XYZa⋯fghi");
484
485        let (fold_snapshot, fold_edits) = fold_map_writer.unfold([4..8], false);
486        let (suggestion_snapshot, suggestion_edits) =
487            suggestion_map.sync(fold_snapshot.clone(), fold_edits);
488        let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
489        assert_eq!(inlay_snapshot.text(), "XYZabc|123|defghi");
490
491        ////////// case: replacing the anchor that got the hint: it should disappear, then undo and it should reappear again
492        buffer.update(cx, |buffer, cx| buffer.edit([(2..3, "C")], None, cx));
493        let (fold_snapshot, fold_edits) = fold_map.read(
494            buffer.read(cx).snapshot(cx),
495            buffer_edits.consume().into_inner(),
496        );
497        let (suggestion_snapshot, suggestion_edits) =
498            suggestion_map.sync(fold_snapshot.clone(), fold_edits);
499        let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
500        assert_eq!(inlay_snapshot.text(), "XYZabCdefghi");
501
502        buffer.update(cx, |buffer, cx| buffer.undo(cx));
503        let (fold_snapshot, fold_edits) = fold_map.read(
504            buffer.read(cx).snapshot(cx),
505            buffer_edits.consume().into_inner(),
506        );
507        let (suggestion_snapshot, suggestion_edits) =
508            suggestion_map.sync(fold_snapshot.clone(), fold_edits);
509        let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
510        assert_eq!(inlay_snapshot.text(), "XYZabc|123|defghi");
511    }
512}