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