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 text::Patch;
27use util::post_inc;
28
29#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
30pub struct InlayId(usize);
31
32pub struct InlayMap {
33 snapshot: Mutex<InlaySnapshot>,
34 next_inlay_id: usize,
35 pub(super) inlays: HashMap<InlayId, Inlay>,
36}
37
38#[derive(Clone)]
39pub struct InlaySnapshot {
40 // TODO kb merge these two together?
41 pub suggestion_snapshot: SuggestionSnapshot,
42 transforms: SumTree<Transform>,
43 pub version: usize,
44}
45
46#[derive(Clone, Debug)]
47enum Transform {
48 Isomorphic(TextSummary),
49 Inlay(Inlay),
50}
51
52impl Transform {
53 fn is_inlay(&self) -> bool {
54 matches!(self, Self::Inlay(_))
55 }
56}
57
58impl sum_tree::Item for Transform {
59 type Summary = TransformSummary;
60
61 fn summary(&self) -> Self::Summary {
62 match self {
63 Transform::Isomorphic(summary) => TransformSummary {
64 input: summary.clone(),
65 output: summary.clone(),
66 },
67 Transform::Inlay(inlay) => TransformSummary {
68 input: TextSummary::default(),
69 output: inlay.properties.text.summary(),
70 },
71 }
72 }
73}
74
75#[derive(Clone, Debug, Default)]
76struct TransformSummary {
77 input: TextSummary,
78 output: TextSummary,
79}
80
81impl sum_tree::Summary for TransformSummary {
82 type Context = ();
83
84 fn add_summary(&mut self, other: &Self, _: &()) {
85 self.input += &other.input;
86 self.output += &other.output;
87 }
88}
89
90pub type InlayEdit = Edit<InlayOffset>;
91
92#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
93pub struct InlayOffset(pub usize);
94
95impl Add for InlayOffset {
96 type Output = Self;
97
98 fn add(self, rhs: Self) -> Self::Output {
99 Self(self.0 + rhs.0)
100 }
101}
102
103impl Sub for InlayOffset {
104 type Output = Self;
105
106 fn sub(self, rhs: Self) -> Self::Output {
107 Self(self.0 - rhs.0)
108 }
109}
110
111impl AddAssign for InlayOffset {
112 fn add_assign(&mut self, rhs: Self) {
113 self.0 += rhs.0;
114 }
115}
116
117impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
118 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
119 self.0 += &summary.output.len;
120 }
121}
122
123impl<'a> sum_tree::Dimension<'a, TransformSummary> for SuggestionOffset {
124 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
125 self.0 += &summary.input.len;
126 }
127}
128
129#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
130pub struct InlayPoint(pub Point);
131
132impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
133 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
134 self.0 += &summary.output.lines;
135 }
136}
137
138impl<'a> sum_tree::Dimension<'a, TransformSummary> for SuggestionPoint {
139 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
140 self.0 += &summary.input.lines;
141 }
142}
143
144#[derive(Clone)]
145pub struct InlayBufferRows<'a> {
146 suggestion_rows: SuggestionBufferRows<'a>,
147}
148
149pub struct InlayChunks<'a> {
150 transforms: Cursor<'a, Transform, (InlayOffset, SuggestionOffset)>,
151 suggestion_chunks: SuggestionChunks<'a>,
152 suggestion_chunk: Option<Chunk<'a>>,
153 inlay_chunks: Option<text::Chunks<'a>>,
154 output_offset: InlayOffset,
155 max_output_offset: InlayOffset,
156}
157
158#[derive(Debug, Clone)]
159pub struct Inlay {
160 pub(super) id: InlayId,
161 pub(super) properties: InlayProperties,
162}
163
164#[derive(Debug, Clone)]
165pub struct InlayProperties {
166 pub(super) position: Anchor,
167 pub(super) text: Rope,
168}
169
170impl<'a> Iterator for InlayChunks<'a> {
171 type Item = Chunk<'a>;
172
173 fn next(&mut self) -> Option<Self::Item> {
174 if self.output_offset == self.max_output_offset {
175 return None;
176 }
177
178 let chunk = match self.transforms.item()? {
179 Transform::Isomorphic(transform) => {
180 let chunk = self
181 .suggestion_chunk
182 .get_or_insert_with(|| self.suggestion_chunks.next().unwrap());
183 if chunk.text.is_empty() {
184 *chunk = self.suggestion_chunks.next().unwrap();
185 }
186
187 let (prefix, suffix) = chunk.text.split_at(cmp::min(
188 self.transforms.end(&()).0 .0 - self.output_offset.0,
189 chunk.text.len(),
190 ));
191
192 chunk.text = suffix;
193 self.output_offset.0 += prefix.len();
194 Chunk {
195 text: prefix,
196 ..chunk.clone()
197 }
198 }
199 Transform::Inlay(inlay) => {
200 let inlay_chunks = self.inlay_chunks.get_or_insert_with(|| {
201 let start = self.output_offset - self.transforms.start().0;
202 let end = cmp::min(self.max_output_offset, self.transforms.end(&()).0)
203 - self.transforms.start().0;
204 inlay.properties.text.chunks_in_range(start.0..end.0)
205 });
206
207 let chunk = inlay_chunks.next().unwrap();
208 self.output_offset.0 += chunk.len();
209 Chunk {
210 text: chunk,
211 ..Default::default()
212 }
213 }
214 };
215
216 if self.output_offset == self.transforms.end(&()).0 {
217 self.inlay_chunks = None;
218 self.transforms.next(&());
219 }
220
221 Some(chunk)
222 }
223}
224
225impl<'a> Iterator for InlayBufferRows<'a> {
226 type Item = Option<u32>;
227
228 fn next(&mut self) -> Option<Self::Item> {
229 self.suggestion_rows.next()
230 }
231}
232
233impl InlayPoint {
234 pub fn new(row: u32, column: u32) -> Self {
235 Self(Point::new(row, column))
236 }
237
238 pub fn row(self) -> u32 {
239 self.0.row
240 }
241
242 pub fn column(self) -> u32 {
243 self.0.column
244 }
245}
246
247impl InlayMap {
248 pub fn new(suggestion_snapshot: SuggestionSnapshot) -> (Self, InlaySnapshot) {
249 let snapshot = InlaySnapshot {
250 suggestion_snapshot: suggestion_snapshot.clone(),
251 version: 0,
252 transforms: SumTree::from_item(
253 Transform::Isomorphic(suggestion_snapshot.text_summary()),
254 &(),
255 ),
256 };
257
258 (
259 Self {
260 snapshot: Mutex::new(snapshot.clone()),
261 next_inlay_id: 0,
262 inlays: HashMap::default(),
263 },
264 snapshot,
265 )
266 }
267
268 pub fn sync(
269 &mut self,
270 suggestion_snapshot: SuggestionSnapshot,
271 suggestion_edits: Vec<SuggestionEdit>,
272 ) -> (InlaySnapshot, Vec<InlayEdit>) {
273 let mut snapshot = self.snapshot.lock();
274
275 let mut new_snapshot = snapshot.clone();
276 if new_snapshot.suggestion_snapshot.version != suggestion_snapshot.version {
277 new_snapshot.version += 1;
278 }
279
280 new_snapshot.transforms = SumTree::new();
281 let mut cursor = snapshot.transforms.cursor::<SuggestionOffset>();
282 let mut suggestion_edits_iter = suggestion_edits.iter().peekable();
283
284 while let Some(suggestion_edit) = suggestion_edits_iter.next() {
285 if suggestion_edit.old.start >= *cursor.start() {
286 if suggestion_edit.old.start >= *cursor.start() {
287 new_snapshot.transforms.push_tree(
288 cursor.slice(&suggestion_edit.old.start, Bias::Left, &()),
289 &(),
290 );
291 }
292
293 while suggestion_edit.old.end > cursor.end(&()) {
294 if let Some(Transform::Inlay(inlay)) = cursor.item() {
295 self.inlays.remove(&inlay.id);
296 }
297 cursor.next(&());
298 }
299
300 let transform_start = SuggestionOffset(new_snapshot.transforms.summary().input.len);
301 let mut transform_end = suggestion_edit.new.end;
302 if suggestion_edits_iter
303 .peek()
304 .map_or(true, |edit| edit.old.start >= cursor.end(&()))
305 {
306 transform_end += cursor.end(&()) - suggestion_edit.old.end;
307 cursor.next(&());
308 }
309
310 push_isomorphic(
311 &mut new_snapshot.transforms,
312 suggestion_snapshot.text_summary_for_range(
313 suggestion_snapshot.to_point(transform_start)
314 ..suggestion_snapshot.to_point(transform_end),
315 ),
316 );
317 }
318
319 new_snapshot.transforms.push_tree(cursor.suffix(&()), &());
320 new_snapshot.suggestion_snapshot = suggestion_snapshot;
321 drop(cursor);
322
323 let mut inlay_edits = Vec::new();
324 for suggestion_edit in suggestion_edits {
325 let old = snapshot.to_inlay_offset(suggestion_edit.old.start)
326 ..snapshot.to_inlay_offset(suggestion_edit.old.end);
327 let new = new_snapshot.to_inlay_offset(suggestion_edit.new.start)
328 ..new_snapshot.to_inlay_offset(suggestion_edit.new.end);
329 inlay_edits.push(Edit { old, new })
330 }
331
332 *snapshot = new_snapshot.clone();
333 snapshot.check_invariants();
334 (new_snapshot, inlay_edits)
335 }
336
337 pub fn splice(
338 &mut self,
339 to_remove: HashSet<InlayId>,
340 to_insert: Vec<InlayProperties>,
341 ) -> (InlaySnapshot, Vec<InlayEdit>, Vec<InlayId>) {
342 let mut snapshot = self.snapshot.lock();
343
344 let mut inlays = BTreeMap::new();
345 let mut new_ids = Vec::new();
346 for properties in to_insert {
347 let inlay = Inlay {
348 id: InlayId(post_inc(&mut self.next_inlay_id)),
349 properties,
350 };
351 self.inlays.insert(inlay.id, inlay.clone());
352 new_ids.push(inlay.id);
353
354 let buffer_point = inlay
355 .properties
356 .position
357 .to_point(snapshot.buffer_snapshot());
358 let fold_point = snapshot
359 .suggestion_snapshot
360 .fold_snapshot
361 .to_fold_point(buffer_point, Bias::Left);
362 let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point);
363
364 inlays.insert((suggestion_point, Reverse(inlay.id)), Some(inlay));
365 }
366
367 for inlay_id in to_remove {
368 if let Some(inlay) = self.inlays.remove(&inlay_id) {
369 let buffer_point = inlay
370 .properties
371 .position
372 .to_point(snapshot.buffer_snapshot());
373 let fold_point = snapshot
374 .suggestion_snapshot
375 .fold_snapshot
376 .to_fold_point(buffer_point, Bias::Left);
377 let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point);
378 inlays.insert((suggestion_point, Reverse(inlay.id)), None);
379 }
380 }
381
382 let mut inlay_edits = Patch::default();
383 let mut new_transforms = SumTree::new();
384 let mut cursor = snapshot
385 .transforms
386 .cursor::<(SuggestionPoint, (InlayOffset, InlayPoint))>();
387 let mut inlays = inlays.into_iter().peekable();
388 while let Some(((suggestion_point, inlay_id), inlay)) = inlays.next() {
389 new_transforms.push_tree(cursor.slice(&suggestion_point, Bias::Left, &()), &());
390
391 while let Some(transform) = cursor.item() {
392 match transform {
393 Transform::Isomorphic(_) => {
394 if suggestion_point >= cursor.end(&()).0 {
395 new_transforms.push(transform.clone(), &());
396 cursor.next(&());
397 } else {
398 break;
399 }
400 }
401 Transform::Inlay(inlay) => {
402 if inlay.id > inlay_id.0 {
403 new_transforms.push(transform.clone(), &());
404 cursor.next(&());
405 } else {
406 if inlay.id == inlay_id.0 {
407 let new_start = InlayOffset(new_transforms.summary().output.len);
408 inlay_edits.push(Edit {
409 old: cursor.start().1 .0..cursor.end(&()).1 .0,
410 new: new_start..new_start,
411 });
412 cursor.next(&());
413 }
414 break;
415 }
416 }
417 }
418 }
419
420 if let Some(inlay) = inlay {
421 let prefix_suggestion_start = SuggestionPoint(new_transforms.summary().input.lines);
422 push_isomorphic(
423 &mut new_transforms,
424 snapshot
425 .suggestion_snapshot
426 .text_summary_for_range(prefix_suggestion_start..suggestion_point),
427 );
428
429 let new_start = InlayOffset(new_transforms.summary().output.len);
430 let new_end = InlayOffset(new_start.0 + inlay.properties.text.len());
431 if let Some(Transform::Isomorphic(transform)) = cursor.item() {
432 let old_start = snapshot.to_offset(InlayPoint(
433 cursor.start().1 .1 .0 + (suggestion_point.0 - cursor.start().0 .0),
434 ));
435 inlay_edits.push(Edit {
436 old: old_start..old_start,
437 new: new_start..new_end,
438 });
439
440 new_transforms.push(Transform::Inlay(inlay), &());
441
442 if inlays.peek().map_or(true, |((suggestion_point, _), _)| {
443 *suggestion_point >= cursor.end(&()).0
444 }) {
445 let suffix_suggestion_end = cursor.end(&()).0;
446 push_isomorphic(
447 &mut new_transforms,
448 snapshot
449 .suggestion_snapshot
450 .text_summary_for_range(suggestion_point..suffix_suggestion_end),
451 );
452 cursor.next(&());
453 }
454 } else {
455 let old_start = cursor.start().1 .0;
456 inlay_edits.push(Edit {
457 old: old_start..old_start,
458 new: new_start..new_end,
459 });
460 new_transforms.push(Transform::Inlay(inlay), &());
461 }
462 }
463 }
464
465 new_transforms.push_tree(cursor.suffix(&()), &());
466 drop(cursor);
467 snapshot.transforms = new_transforms;
468 snapshot.version += 1;
469 snapshot.check_invariants();
470
471 (snapshot.clone(), inlay_edits.into_inner(), new_ids)
472 }
473
474 #[cfg(test)]
475 pub fn randomly_mutate(
476 &mut self,
477 rng: &mut rand::rngs::StdRng,
478 ) -> (InlaySnapshot, Vec<InlayEdit>, Vec<InlayId>) {
479 use rand::seq::IteratorRandom;
480
481 let mut to_remove = HashSet::default();
482 let mut to_insert = Vec::default();
483 let snapshot = self.snapshot.lock();
484 for _ in 0..rng.gen_range(1..=5) {
485 if self.inlays.is_empty() || rng.gen() {
486 let buffer_snapshot = snapshot.buffer_snapshot();
487 let position = buffer_snapshot.random_byte_range(0, rng).start;
488 let len = rng.gen_range(1..=5);
489 let text = util::RandomCharIter::new(&mut *rng)
490 .take(len)
491 .collect::<String>();
492 log::info!(
493 "creating inlay at buffer offset {} with text {:?}",
494 position,
495 text
496 );
497
498 to_insert.push(InlayProperties {
499 position: buffer_snapshot.anchor_after(position),
500 text: text.as_str().into(),
501 });
502 } else {
503 to_remove.insert(*self.inlays.keys().choose(rng).unwrap());
504 }
505 }
506
507 drop(snapshot);
508 self.splice(to_remove, to_insert)
509 }
510}
511
512impl InlaySnapshot {
513 pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
514 self.suggestion_snapshot.buffer_snapshot()
515 }
516
517 pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
518 let mut cursor = self
519 .transforms
520 .cursor::<(InlayOffset, (InlayPoint, SuggestionOffset))>();
521 cursor.seek(&offset, Bias::Right, &());
522 let overshoot = offset.0 - cursor.start().0 .0;
523 match cursor.item() {
524 Some(Transform::Isomorphic(transform)) => {
525 let suggestion_offset_start = cursor.start().1 .1;
526 let suggestion_offset_end = SuggestionOffset(suggestion_offset_start.0 + overshoot);
527 let suggestion_start = self.suggestion_snapshot.to_point(suggestion_offset_start);
528 let suggestion_end = self.suggestion_snapshot.to_point(suggestion_offset_end);
529 InlayPoint(cursor.start().1 .0 .0 + (suggestion_end.0 - suggestion_start.0))
530 }
531 Some(Transform::Inlay(inlay)) => {
532 let overshoot = inlay.properties.text.offset_to_point(overshoot);
533 InlayPoint(cursor.start().1 .0 .0 + overshoot)
534 }
535 None => self.max_point(),
536 }
537 }
538
539 pub fn len(&self) -> InlayOffset {
540 InlayOffset(self.transforms.summary().output.len)
541 }
542
543 pub fn max_point(&self) -> InlayPoint {
544 InlayPoint(self.transforms.summary().output.lines)
545 }
546
547 pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
548 let mut cursor = self
549 .transforms
550 .cursor::<(InlayPoint, (InlayOffset, SuggestionPoint))>();
551 cursor.seek(&point, Bias::Right, &());
552 let overshoot = point.0 - cursor.start().0 .0;
553 match cursor.item() {
554 Some(Transform::Isomorphic(transform)) => {
555 let suggestion_point_start = cursor.start().1 .1;
556 let suggestion_point_end = SuggestionPoint(suggestion_point_start.0 + overshoot);
557 let suggestion_start = self.suggestion_snapshot.to_offset(suggestion_point_start);
558 let suggestion_end = self.suggestion_snapshot.to_offset(suggestion_point_end);
559 InlayOffset(cursor.start().1 .0 .0 + (suggestion_end.0 - suggestion_start.0))
560 }
561 Some(Transform::Inlay(inlay)) => {
562 let overshoot = inlay.properties.text.point_to_offset(overshoot);
563 InlayOffset(cursor.start().1 .0 .0 + overshoot)
564 }
565 None => self.len(),
566 }
567 }
568
569 pub fn chars_at(&self, start: InlayPoint) -> impl '_ + Iterator<Item = char> {
570 self.chunks(self.to_offset(start)..self.len(), false, None, None)
571 .flat_map(|chunk| chunk.text.chars())
572 }
573
574 pub fn to_suggestion_point(&self, point: InlayPoint) -> SuggestionPoint {
575 let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>();
576 cursor.seek(&point, Bias::Right, &());
577 let overshoot = point.0 - cursor.start().0 .0;
578 match cursor.item() {
579 Some(Transform::Isomorphic(transform)) => {
580 SuggestionPoint(cursor.start().1 .0 + overshoot)
581 }
582 Some(Transform::Inlay(inlay)) => cursor.start().1,
583 None => self.suggestion_snapshot.max_point(),
584 }
585 }
586
587 pub fn to_suggestion_offset(&self, offset: InlayOffset) -> SuggestionOffset {
588 let mut cursor = self.transforms.cursor::<(InlayOffset, SuggestionOffset)>();
589 cursor.seek(&offset, Bias::Right, &());
590 match cursor.item() {
591 Some(Transform::Isomorphic(transform)) => {
592 let overshoot = offset - cursor.start().0;
593 cursor.start().1 + SuggestionOffset(overshoot.0)
594 }
595 Some(Transform::Inlay(inlay)) => cursor.start().1,
596 None => self.suggestion_snapshot.len(),
597 }
598 }
599
600 pub fn to_inlay_offset(&self, offset: SuggestionOffset) -> InlayOffset {
601 let mut cursor = self.transforms.cursor::<(SuggestionOffset, InlayOffset)>();
602 // TODO kb is the bias right? should we have an external one instead?
603 cursor.seek(&offset, Bias::Right, &());
604 let overshoot = offset.0 - cursor.start().0 .0;
605 match cursor.item() {
606 Some(Transform::Isomorphic(transform)) => InlayOffset(cursor.start().1 .0 + overshoot),
607 Some(Transform::Inlay(inlay)) => cursor.start().1,
608 None => self.len(),
609 }
610 }
611
612 pub fn to_inlay_point(&self, point: SuggestionPoint) -> InlayPoint {
613 let mut cursor = self.transforms.cursor::<(SuggestionPoint, InlayPoint)>();
614 // TODO kb is the bias right? should we have an external one instead?
615 cursor.seek(&point, Bias::Right, &());
616 let overshoot = point.0 - cursor.start().0 .0;
617 match cursor.item() {
618 Some(Transform::Isomorphic(transform)) => InlayPoint(cursor.start().1 .0 + overshoot),
619 Some(Transform::Inlay(inlay)) => cursor.start().1,
620 None => self.max_point(),
621 }
622 }
623
624 pub fn clip_point(&self, point: InlayPoint, bias: Bias) -> InlayPoint {
625 let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>();
626 cursor.seek(&point, bias, &());
627 match cursor.item() {
628 Some(Transform::Isomorphic(_)) => {
629 let overshoot = point.0 - cursor.start().0 .0;
630 let suggestion_point = SuggestionPoint(cursor.start().1 .0 + overshoot);
631 let clipped_suggestion_point =
632 self.suggestion_snapshot.clip_point(suggestion_point, bias);
633 let clipped_overshoot = clipped_suggestion_point.0 - cursor.start().1 .0;
634 return InlayPoint(cursor.start().0 .0 + clipped_overshoot);
635 }
636 Some(Transform::Inlay(_)) => {}
637 None => return self.max_point(),
638 }
639
640 while cursor
641 .item()
642 .map_or(false, |transform| transform.is_inlay())
643 {
644 match bias {
645 Bias::Left => cursor.prev(&()),
646 Bias::Right => cursor.next(&()),
647 }
648 }
649
650 match bias {
651 Bias::Left => cursor.end(&()).0,
652 Bias::Right => cursor.start().0,
653 }
654 }
655
656 pub fn text_summary_for_range(&self, range: Range<InlayPoint>) -> TextSummary {
657 let mut summary = TextSummary::default();
658
659 let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>();
660 cursor.seek(&range.start, Bias::Right, &());
661
662 let overshoot = range.start.0 - cursor.start().0 .0;
663 match cursor.item() {
664 Some(Transform::Isomorphic(transform)) => {
665 let suggestion_start = cursor.start().1 .0;
666 let suffix_start = SuggestionPoint(suggestion_start + overshoot);
667 let suffix_end = SuggestionPoint(
668 suggestion_start
669 + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0),
670 );
671 summary = self
672 .suggestion_snapshot
673 .text_summary_for_range(suffix_start..suffix_end);
674 cursor.next(&());
675 }
676 Some(Transform::Inlay(inlay)) => {
677 let text = &inlay.properties.text;
678 let suffix_start = text.point_to_offset(overshoot);
679 let suffix_end = text.point_to_offset(
680 cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0,
681 );
682 summary = text.cursor(suffix_start).summary(suffix_end);
683 cursor.next(&());
684 }
685 None => {}
686 }
687
688 if range.end > cursor.start().0 {
689 summary += cursor
690 .summary::<_, TransformSummary>(&range.end, Bias::Right, &())
691 .output;
692
693 let overshoot = range.end.0 - cursor.start().0 .0;
694 match cursor.item() {
695 Some(Transform::Isomorphic(transform)) => {
696 let prefix_start = cursor.start().1;
697 let prefix_end = SuggestionPoint(prefix_start.0 + overshoot);
698 summary += self
699 .suggestion_snapshot
700 .text_summary_for_range(prefix_start..prefix_end);
701 }
702 Some(Transform::Inlay(inlay)) => {
703 let text = &inlay.properties.text;
704 let prefix_end = text.point_to_offset(overshoot);
705 summary += text.cursor(0).summary::<TextSummary>(prefix_end);
706 }
707 None => {}
708 }
709 }
710
711 summary
712 }
713
714 // TODO kb copied from suggestion_snapshot
715 pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> {
716 InlayBufferRows {
717 suggestion_rows: self.suggestion_snapshot.buffer_rows(row),
718 }
719 }
720
721 pub fn line_len(&self, row: u32) -> u32 {
722 let line_start = self.to_offset(InlayPoint::new(row, 0)).0;
723 let line_end = if row >= self.max_point().row() {
724 self.len().0
725 } else {
726 self.to_offset(InlayPoint::new(row + 1, 0)).0 - 1
727 };
728 (line_end - line_start) as u32
729 }
730
731 pub fn chunks<'a>(
732 &'a self,
733 range: Range<InlayOffset>,
734 language_aware: bool,
735 text_highlights: Option<&'a TextHighlights>,
736 suggestion_highlight: Option<HighlightStyle>,
737 ) -> InlayChunks<'a> {
738 let mut cursor = self.transforms.cursor::<(InlayOffset, SuggestionOffset)>();
739 cursor.seek(&range.start, Bias::Right, &());
740
741 let suggestion_range =
742 self.to_suggestion_offset(range.start)..self.to_suggestion_offset(range.end);
743 let suggestion_chunks = self.suggestion_snapshot.chunks(
744 suggestion_range,
745 language_aware,
746 text_highlights,
747 suggestion_highlight,
748 );
749
750 InlayChunks {
751 transforms: cursor,
752 suggestion_chunks,
753 inlay_chunks: None,
754 suggestion_chunk: None,
755 output_offset: range.start,
756 max_output_offset: range.end,
757 }
758 }
759
760 #[cfg(test)]
761 pub fn text(&self) -> String {
762 self.chunks(Default::default()..self.len(), false, None, None)
763 .map(|chunk| chunk.text)
764 .collect()
765 }
766
767 fn check_invariants(&self) {
768 #[cfg(any(debug_assertions, feature = "test-support"))]
769 {
770 assert_eq!(
771 self.transforms.summary().input,
772 self.suggestion_snapshot.text_summary()
773 );
774 }
775 }
776}
777
778fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
779 if summary.len == 0 {
780 return;
781 }
782
783 let mut summary = Some(summary);
784 sum_tree.update_last(
785 |transform| {
786 if let Transform::Isomorphic(transform) = transform {
787 *transform += summary.take().unwrap();
788 }
789 },
790 &(),
791 );
792
793 if let Some(summary) = summary {
794 sum_tree.push(Transform::Isomorphic(summary), &());
795 }
796}
797
798#[cfg(test)]
799mod tests {
800 use super::*;
801 use crate::{
802 display_map::{fold_map::FoldMap, suggestion_map::SuggestionMap},
803 MultiBuffer,
804 };
805 use gpui::AppContext;
806 use rand::prelude::*;
807 use settings::SettingsStore;
808 use std::env;
809 use text::Patch;
810
811 #[gpui::test]
812 fn test_basic_inlays(cx: &mut AppContext) {
813 let buffer = MultiBuffer::build_simple("abcdefghi", cx);
814 let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
815 let (mut fold_map, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
816 let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone());
817 let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone());
818 assert_eq!(inlay_snapshot.text(), "abcdefghi");
819
820 let (inlay_snapshot, _, inlay_ids) = inlay_map.splice(
821 HashSet::default(),
822 vec![InlayProperties {
823 position: buffer.read(cx).read(cx).anchor_before(3),
824 text: "|123|".into(),
825 }],
826 );
827 assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
828 assert_eq!(
829 inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 0)),
830 InlayPoint::new(0, 0)
831 );
832 assert_eq!(
833 inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 1)),
834 InlayPoint::new(0, 1)
835 );
836 assert_eq!(
837 inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 2)),
838 InlayPoint::new(0, 2)
839 );
840 assert_eq!(
841 inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 3)),
842 InlayPoint::new(0, 8)
843 );
844 assert_eq!(
845 inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 4)),
846 InlayPoint::new(0, 9)
847 );
848 assert_eq!(
849 inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 5)),
850 InlayPoint::new(0, 10)
851 );
852 assert_eq!(
853 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
854 InlayPoint::new(0, 0)
855 );
856 assert_eq!(
857 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
858 InlayPoint::new(0, 0)
859 );
860 assert_eq!(
861 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
862 InlayPoint::new(0, 3)
863 );
864 assert_eq!(
865 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
866 InlayPoint::new(0, 8)
867 );
868 assert_eq!(
869 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
870 InlayPoint::new(0, 3)
871 );
872 assert_eq!(
873 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
874 InlayPoint::new(0, 8)
875 );
876 assert_eq!(
877 inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Left),
878 InlayPoint::new(0, 9)
879 );
880 assert_eq!(
881 inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Right),
882 InlayPoint::new(0, 9)
883 );
884
885 buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "XYZ")], None, cx));
886 let (fold_snapshot, fold_edits) = fold_map.read(
887 buffer.read(cx).snapshot(cx),
888 buffer_edits.consume().into_inner(),
889 );
890 let (suggestion_snapshot, suggestion_edits) =
891 suggestion_map.sync(fold_snapshot.clone(), fold_edits);
892 let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
893 assert_eq!(inlay_snapshot.text(), "XYZabc|123|defghi");
894
895 //////// case: folding and unfolding the text should hine and then return the hint back
896 let (mut fold_map_writer, _, _) = fold_map.write(
897 buffer.read(cx).snapshot(cx),
898 buffer_edits.consume().into_inner(),
899 );
900 let (fold_snapshot, fold_edits) = fold_map_writer.fold([4..8]);
901 let (suggestion_snapshot, suggestion_edits) =
902 suggestion_map.sync(fold_snapshot.clone(), fold_edits);
903 let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
904 assert_eq!(inlay_snapshot.text(), "XYZa⋯fghi");
905
906 let (fold_snapshot, fold_edits) = fold_map_writer.unfold([4..8], false);
907 let (suggestion_snapshot, suggestion_edits) =
908 suggestion_map.sync(fold_snapshot.clone(), fold_edits);
909 let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
910 assert_eq!(inlay_snapshot.text(), "XYZabc|123|defghi");
911
912 ////////// case: replacing the anchor that got the hint: it should disappear
913 buffer.update(cx, |buffer, cx| buffer.edit([(2..3, "C")], None, cx));
914 let (fold_snapshot, fold_edits) = fold_map.read(
915 buffer.read(cx).snapshot(cx),
916 buffer_edits.consume().into_inner(),
917 );
918 let (suggestion_snapshot, suggestion_edits) =
919 suggestion_map.sync(fold_snapshot.clone(), fold_edits);
920 let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
921 assert_eq!(inlay_snapshot.text(), "XYZabCdefghi");
922 }
923
924 #[gpui::test(iterations = 100)]
925 fn test_random_inlays(cx: &mut AppContext, mut rng: StdRng) {
926 init_test(cx);
927
928 let operations = env::var("OPERATIONS")
929 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
930 .unwrap_or(10);
931
932 let len = rng.gen_range(0..30);
933 let buffer = if rng.gen() {
934 let text = util::RandomCharIter::new(&mut rng)
935 .take(len)
936 .collect::<String>();
937 MultiBuffer::build_simple(&text, cx)
938 } else {
939 MultiBuffer::build_random(&mut rng, cx)
940 };
941 let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
942 log::info!("buffer text: {:?}", buffer_snapshot.text());
943
944 let (mut fold_map, mut fold_snapshot) = FoldMap::new(buffer_snapshot.clone());
945 let (suggestion_map, mut suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone());
946 let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone());
947
948 for _ in 0..operations {
949 let mut suggestion_edits = Patch::default();
950 let mut inlay_edits = Patch::default();
951
952 let mut prev_inlay_text = inlay_snapshot.text();
953 let mut buffer_edits = Vec::new();
954 match rng.gen_range(0..=100) {
955 0..=29 => {
956 let (snapshot, edits, _) = inlay_map.randomly_mutate(&mut rng);
957 inlay_snapshot = snapshot;
958 inlay_edits = Patch::new(edits);
959 }
960 30..=59 => {
961 for (new_fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) {
962 fold_snapshot = new_fold_snapshot;
963 let (_, edits) = suggestion_map.sync(fold_snapshot.clone(), fold_edits);
964 suggestion_edits = suggestion_edits.compose(edits);
965 }
966 }
967 _ => buffer.update(cx, |buffer, cx| {
968 let subscription = buffer.subscribe();
969 let edit_count = rng.gen_range(1..=5);
970 buffer.randomly_mutate(&mut rng, edit_count, cx);
971 buffer_snapshot = buffer.snapshot(cx);
972 let edits = subscription.consume().into_inner();
973 log::info!("editing {:?}", edits);
974 buffer_edits.extend(edits);
975 }),
976 };
977
978 let (new_fold_snapshot, fold_edits) =
979 fold_map.read(buffer_snapshot.clone(), buffer_edits);
980 fold_snapshot = new_fold_snapshot;
981 let (new_suggestion_snapshot, new_suggestion_edits) =
982 suggestion_map.sync(fold_snapshot.clone(), fold_edits);
983 suggestion_snapshot = new_suggestion_snapshot;
984 suggestion_edits = suggestion_edits.compose(new_suggestion_edits);
985 let (new_inlay_snapshot, new_inlay_edits) =
986 inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits.into_inner());
987 inlay_snapshot = new_inlay_snapshot;
988 inlay_edits = inlay_edits.compose(new_inlay_edits);
989
990 log::info!("buffer text: {:?}", buffer_snapshot.text());
991 log::info!("folds text: {:?}", fold_snapshot.text());
992 log::info!("suggestions text: {:?}", suggestion_snapshot.text());
993 log::info!("inlay text: {:?}", inlay_snapshot.text());
994
995 let mut inlays = inlay_map
996 .inlays
997 .values()
998 .map(|inlay| {
999 let buffer_point = inlay.properties.position.to_point(&buffer_snapshot);
1000 let fold_point = fold_snapshot.to_fold_point(buffer_point, Bias::Left);
1001 let suggestion_point = suggestion_snapshot.to_suggestion_point(fold_point);
1002 let suggestion_offset = suggestion_snapshot.to_offset(suggestion_point);
1003 (suggestion_offset, inlay.clone())
1004 })
1005 .collect::<Vec<_>>();
1006 inlays.sort_by_key(|(offset, inlay)| (*offset, Reverse(inlay.id)));
1007 let mut expected_text = Rope::from(suggestion_snapshot.text().as_str());
1008 for (offset, inlay) in inlays.into_iter().rev() {
1009 expected_text.replace(offset.0..offset.0, &inlay.properties.text.to_string());
1010 }
1011 assert_eq!(inlay_snapshot.text(), expected_text.to_string());
1012 continue;
1013
1014 // let mut expected_buffer_rows = suggestion_snapshot.buffer_rows(0).collect::<Vec<_>>();
1015
1016 // for row_start in 0..expected_buffer_rows.len() {
1017 // assert_eq!(
1018 // inlay_snapshot
1019 // .buffer_rows(row_start as u32)
1020 // .collect::<Vec<_>>(),
1021 // &expected_buffer_rows[row_start..],
1022 // "incorrect buffer rows starting at {}",
1023 // row_start
1024 // );
1025 // }
1026
1027 for _ in 0..5 {
1028 let mut end = rng.gen_range(0..=inlay_snapshot.len().0);
1029 end = expected_text.clip_offset(end, Bias::Right);
1030 let mut start = rng.gen_range(0..=end);
1031 start = expected_text.clip_offset(start, Bias::Right);
1032
1033 let actual_text = inlay_snapshot
1034 .chunks(InlayOffset(start)..InlayOffset(end), false, None, None)
1035 .map(|chunk| chunk.text)
1036 .collect::<String>();
1037 assert_eq!(
1038 actual_text,
1039 expected_text.slice(start..end).to_string(),
1040 "incorrect text in range {:?}",
1041 start..end
1042 );
1043
1044 let start_point = InlayPoint(expected_text.offset_to_point(start));
1045 let end_point = InlayPoint(expected_text.offset_to_point(end));
1046 assert_eq!(
1047 inlay_snapshot.text_summary_for_range(start_point..end_point),
1048 expected_text.slice(start..end).summary()
1049 );
1050 }
1051
1052 for edit in inlay_edits {
1053 prev_inlay_text.replace_range(
1054 edit.new.start.0..edit.new.start.0 + edit.old_len().0,
1055 &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0],
1056 );
1057 }
1058 assert_eq!(prev_inlay_text, inlay_snapshot.text());
1059
1060 assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
1061 assert_eq!(expected_text.len(), inlay_snapshot.len().0);
1062
1063 let mut inlay_point = InlayPoint::default();
1064 let mut inlay_offset = InlayOffset::default();
1065 for ch in expected_text.chars() {
1066 assert_eq!(
1067 inlay_snapshot.to_offset(inlay_point),
1068 inlay_offset,
1069 "invalid to_offset({:?})",
1070 inlay_point
1071 );
1072 assert_eq!(
1073 inlay_snapshot.to_point(inlay_offset),
1074 inlay_point,
1075 "invalid to_point({:?})",
1076 inlay_offset
1077 );
1078 assert_eq!(
1079 inlay_snapshot.to_inlay_point(inlay_snapshot.to_suggestion_point(inlay_point)),
1080 inlay_snapshot.clip_point(inlay_point, Bias::Right),
1081 "to_suggestion_point({:?}) = {:?}",
1082 inlay_point,
1083 inlay_snapshot.to_suggestion_point(inlay_point),
1084 );
1085
1086 let mut bytes = [0; 4];
1087 for byte in ch.encode_utf8(&mut bytes).as_bytes() {
1088 inlay_offset.0 += 1;
1089 if *byte == b'\n' {
1090 inlay_point.0 += Point::new(1, 0);
1091 } else {
1092 inlay_point.0 += Point::new(0, 1);
1093 }
1094
1095 let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left);
1096 let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right);
1097 assert!(
1098 clipped_left_point <= clipped_right_point,
1099 "clipped left point {:?} is greater than clipped right point {:?}",
1100 clipped_left_point,
1101 clipped_right_point
1102 );
1103 assert_eq!(
1104 clipped_left_point.0,
1105 expected_text.clip_point(clipped_left_point.0, Bias::Left)
1106 );
1107 assert_eq!(
1108 clipped_right_point.0,
1109 expected_text.clip_point(clipped_right_point.0, Bias::Right)
1110 );
1111 assert!(clipped_left_point <= inlay_snapshot.max_point());
1112 assert!(clipped_right_point <= inlay_snapshot.max_point());
1113 }
1114 }
1115 }
1116 }
1117
1118 fn init_test(cx: &mut AppContext) {
1119 cx.set_global(SettingsStore::test(cx));
1120 theme::init((), cx);
1121 }
1122}