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