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}