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 buffer_row = if self.inlay_row == 0 {
217 self.suggestion_rows.next().unwrap()
218 } else {
219 match self.transforms.item()? {
220 Transform::Inlay(_) => None,
221 Transform::Isomorphic(_) => self.suggestion_rows.next().unwrap(),
222 }
223 };
224
225 self.inlay_row += 1;
226 self.transforms
227 .seek_forward(&InlayPoint::new(self.inlay_row, 0), Bias::Left, &());
228
229 Some(buffer_row)
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 inlays_by_id: Default::default(),
262 inlays: Default::default(),
263 },
264 snapshot,
265 )
266 }
267
268 pub fn sync(
269 &mut self,
270 suggestion_snapshot: SuggestionSnapshot,
271 mut 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 if suggestion_snapshot
281 .buffer_snapshot()
282 .trailing_excerpt_update_count()
283 != snapshot
284 .suggestion_snapshot
285 .buffer_snapshot()
286 .trailing_excerpt_update_count()
287 {
288 if suggestion_edits.is_empty() {
289 suggestion_edits.push(Edit {
290 old: snapshot.suggestion_snapshot.len()..snapshot.suggestion_snapshot.len(),
291 new: suggestion_snapshot.len()..suggestion_snapshot.len(),
292 });
293 }
294 }
295
296 let mut inlay_edits = Patch::default();
297 let mut new_transforms = SumTree::new();
298 let mut cursor = snapshot
299 .transforms
300 .cursor::<(SuggestionOffset, InlayOffset)>();
301 let mut suggestion_edits_iter = suggestion_edits.iter().peekable();
302 while let Some(suggestion_edit) = suggestion_edits_iter.next() {
303 new_transforms.push_tree(
304 cursor.slice(&suggestion_edit.old.start, Bias::Left, &()),
305 &(),
306 );
307 if let Some(Transform::Isomorphic(transform)) = cursor.item() {
308 if cursor.end(&()).0 == suggestion_edit.old.start {
309 new_transforms.push(Transform::Isomorphic(transform.clone()), &());
310 cursor.next(&());
311 }
312 }
313
314 // Remove all the inlays and transforms contained by the edit.
315 let old_start =
316 cursor.start().1 + InlayOffset(suggestion_edit.old.start.0 - cursor.start().0 .0);
317 cursor.seek(&suggestion_edit.old.end, Bias::Right, &());
318 let old_end =
319 cursor.start().1 + InlayOffset(suggestion_edit.old.end.0 - cursor.start().0 .0);
320
321 // Push the unchanged prefix.
322 let prefix_start = SuggestionOffset(new_transforms.summary().input.len);
323 let prefix_end = suggestion_edit.new.start;
324 push_isomorphic(
325 &mut new_transforms,
326 suggestion_snapshot.text_summary_for_range(
327 suggestion_snapshot.to_point(prefix_start)
328 ..suggestion_snapshot.to_point(prefix_end),
329 ),
330 );
331 let new_start = InlayOffset(new_transforms.summary().output.len);
332
333 let start_point = suggestion_snapshot
334 .to_fold_point(suggestion_snapshot.to_point(suggestion_edit.new.start))
335 .to_buffer_point(&suggestion_snapshot.fold_snapshot);
336 let start_ix = match self.inlays.binary_search_by(|probe| {
337 probe
338 .position
339 .to_point(&suggestion_snapshot.buffer_snapshot())
340 .cmp(&start_point)
341 .then(std::cmp::Ordering::Greater)
342 }) {
343 Ok(ix) | Err(ix) => ix,
344 };
345
346 for inlay in &self.inlays[start_ix..] {
347 let buffer_point = inlay
348 .position
349 .to_point(suggestion_snapshot.buffer_snapshot());
350 let fold_point = suggestion_snapshot
351 .fold_snapshot
352 .to_fold_point(buffer_point, Bias::Left);
353 let suggestion_point = suggestion_snapshot.to_suggestion_point(fold_point);
354 let suggestion_offset = suggestion_snapshot.to_offset(suggestion_point);
355 if suggestion_offset > suggestion_edit.new.end {
356 break;
357 }
358
359 let prefix_start = SuggestionOffset(new_transforms.summary().input.len);
360 let prefix_end = suggestion_offset;
361 push_isomorphic(
362 &mut new_transforms,
363 suggestion_snapshot.text_summary_for_range(
364 suggestion_snapshot.to_point(prefix_start)
365 ..suggestion_snapshot.to_point(prefix_end),
366 ),
367 );
368
369 if inlay
370 .position
371 .is_valid(suggestion_snapshot.buffer_snapshot())
372 {
373 new_transforms.push(Transform::Inlay(inlay.clone()), &());
374 }
375 }
376
377 // Apply the rest of the edit.
378 let transform_start = SuggestionOffset(new_transforms.summary().input.len);
379 push_isomorphic(
380 &mut new_transforms,
381 suggestion_snapshot.text_summary_for_range(
382 suggestion_snapshot.to_point(transform_start)
383 ..suggestion_snapshot.to_point(suggestion_edit.new.end),
384 ),
385 );
386 let new_end = InlayOffset(new_transforms.summary().output.len);
387 inlay_edits.push(Edit {
388 old: old_start..old_end,
389 new: new_start..new_end,
390 });
391
392 // If the next edit doesn't intersect the current isomorphic transform, then
393 // we can push its remainder.
394 if suggestion_edits_iter
395 .peek()
396 .map_or(true, |edit| edit.old.start >= cursor.end(&()).0)
397 {
398 let transform_start = SuggestionOffset(new_transforms.summary().input.len);
399 let transform_end =
400 suggestion_edit.new.end + (cursor.end(&()).0 - suggestion_edit.old.end);
401 push_isomorphic(
402 &mut new_transforms,
403 suggestion_snapshot.text_summary_for_range(
404 suggestion_snapshot.to_point(transform_start)
405 ..suggestion_snapshot.to_point(transform_end),
406 ),
407 );
408 cursor.next(&());
409 }
410 }
411
412 new_transforms.push_tree(cursor.suffix(&()), &());
413 if new_transforms.first().is_none() {
414 new_transforms.push(Transform::Isomorphic(Default::default()), &());
415 }
416 new_snapshot.transforms = new_transforms;
417 new_snapshot.suggestion_snapshot = suggestion_snapshot;
418 new_snapshot.check_invariants();
419 drop(cursor);
420
421 *snapshot = new_snapshot.clone();
422 (new_snapshot, inlay_edits.into_inner())
423 }
424
425 pub fn splice<T: Into<Rope>>(
426 &mut self,
427 to_remove: Vec<InlayId>,
428 to_insert: Vec<(InlayId, InlayProperties<T>)>,
429 ) -> (InlaySnapshot, Vec<InlayEdit>) {
430 let mut snapshot = self.snapshot.lock();
431 snapshot.version += 1;
432
433 let mut edits = BTreeSet::new();
434 for (id, properties) in to_insert {
435 let inlay = Inlay {
436 id,
437 position: properties.position,
438 text: properties.text.into(),
439 };
440 self.inlays_by_id.insert(inlay.id, inlay.clone());
441 match self.inlays.binary_search_by(|probe| {
442 probe
443 .position
444 .cmp(&inlay.position, snapshot.buffer_snapshot())
445 }) {
446 Ok(ix) | Err(ix) => {
447 self.inlays.insert(ix, inlay.clone());
448 }
449 }
450
451 let buffer_point = inlay.position.to_point(snapshot.buffer_snapshot());
452 let fold_point = snapshot
453 .suggestion_snapshot
454 .fold_snapshot
455 .to_fold_point(buffer_point, Bias::Left);
456 let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point);
457 let suggestion_offset = snapshot.suggestion_snapshot.to_offset(suggestion_point);
458 edits.insert(suggestion_offset);
459 }
460
461 self.inlays.retain(|inlay| !to_remove.contains(&inlay.id));
462 for inlay_id in to_remove {
463 if let Some(inlay) = self.inlays_by_id.remove(&inlay_id) {
464 let buffer_point = inlay.position.to_point(snapshot.buffer_snapshot());
465 let fold_point = snapshot
466 .suggestion_snapshot
467 .fold_snapshot
468 .to_fold_point(buffer_point, Bias::Left);
469 let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point);
470 let suggestion_offset = snapshot.suggestion_snapshot.to_offset(suggestion_point);
471 edits.insert(suggestion_offset);
472 }
473 }
474
475 let suggestion_snapshot = snapshot.suggestion_snapshot.clone();
476 let suggestion_edits = edits
477 .into_iter()
478 .map(|offset| Edit {
479 old: offset..offset,
480 new: offset..offset,
481 })
482 .collect();
483 drop(snapshot);
484 self.sync(suggestion_snapshot, suggestion_edits)
485 }
486
487 #[cfg(any(test, feature = "test-support"))]
488 pub fn randomly_mutate(
489 &mut self,
490 next_inlay_id: &mut usize,
491 rng: &mut rand::rngs::StdRng,
492 ) -> (InlaySnapshot, Vec<InlayEdit>) {
493 use rand::prelude::*;
494 use util::post_inc;
495
496 let mut to_remove = Vec::new();
497 let mut to_insert = Vec::new();
498 let snapshot = self.snapshot.lock();
499 for _ in 0..rng.gen_range(1..=5) {
500 if self.inlays.is_empty() || rng.gen() {
501 let buffer_snapshot = snapshot.buffer_snapshot();
502 let position = buffer_snapshot.random_byte_range(0, rng).start;
503 let bias = if rng.gen() { Bias::Left } else { Bias::Right };
504 let len = rng.gen_range(1..=5);
505 let text = util::RandomCharIter::new(&mut *rng)
506 .take(len)
507 .collect::<String>();
508 log::info!(
509 "creating inlay at buffer offset {} with bias {:?} and text {:?}",
510 position,
511 bias,
512 text
513 );
514 to_insert.push((
515 InlayId(post_inc(next_inlay_id)),
516 InlayProperties {
517 position: buffer_snapshot.anchor_at(position, bias),
518 text,
519 },
520 ));
521 } else {
522 to_remove.push(*self.inlays_by_id.keys().choose(rng).unwrap());
523 }
524 }
525 log::info!("removing inlays: {:?}", to_remove);
526
527 drop(snapshot);
528 self.splice(to_remove, to_insert)
529 }
530}
531
532impl InlaySnapshot {
533 pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
534 self.suggestion_snapshot.buffer_snapshot()
535 }
536
537 pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
538 let mut cursor = self
539 .transforms
540 .cursor::<(InlayOffset, (InlayPoint, SuggestionOffset))>();
541 cursor.seek(&offset, Bias::Right, &());
542 let overshoot = offset.0 - cursor.start().0 .0;
543 match cursor.item() {
544 Some(Transform::Isomorphic(_)) => {
545 let suggestion_offset_start = cursor.start().1 .1;
546 let suggestion_offset_end = SuggestionOffset(suggestion_offset_start.0 + overshoot);
547 let suggestion_start = self.suggestion_snapshot.to_point(suggestion_offset_start);
548 let suggestion_end = self.suggestion_snapshot.to_point(suggestion_offset_end);
549 InlayPoint(cursor.start().1 .0 .0 + (suggestion_end.0 - suggestion_start.0))
550 }
551 Some(Transform::Inlay(inlay)) => {
552 let overshoot = inlay.text.offset_to_point(overshoot);
553 InlayPoint(cursor.start().1 .0 .0 + overshoot)
554 }
555 None => self.max_point(),
556 }
557 }
558
559 pub fn len(&self) -> InlayOffset {
560 InlayOffset(self.transforms.summary().output.len)
561 }
562
563 pub fn max_point(&self) -> InlayPoint {
564 InlayPoint(self.transforms.summary().output.lines)
565 }
566
567 pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
568 let mut cursor = self
569 .transforms
570 .cursor::<(InlayPoint, (InlayOffset, SuggestionPoint))>();
571 cursor.seek(&point, Bias::Right, &());
572 let overshoot = point.0 - cursor.start().0 .0;
573 match cursor.item() {
574 Some(Transform::Isomorphic(_)) => {
575 let suggestion_point_start = cursor.start().1 .1;
576 let suggestion_point_end = SuggestionPoint(suggestion_point_start.0 + overshoot);
577 let suggestion_start = self.suggestion_snapshot.to_offset(suggestion_point_start);
578 let suggestion_end = self.suggestion_snapshot.to_offset(suggestion_point_end);
579 InlayOffset(cursor.start().1 .0 .0 + (suggestion_end.0 - suggestion_start.0))
580 }
581 Some(Transform::Inlay(inlay)) => {
582 let overshoot = inlay.text.point_to_offset(overshoot);
583 InlayOffset(cursor.start().1 .0 .0 + overshoot)
584 }
585 None => self.len(),
586 }
587 }
588
589 pub fn chars_at(&self, start: InlayPoint) -> impl '_ + Iterator<Item = char> {
590 self.chunks(self.to_offset(start)..self.len(), false, None, None)
591 .flat_map(|chunk| chunk.text.chars())
592 }
593
594 pub fn to_suggestion_point(&self, point: InlayPoint) -> SuggestionPoint {
595 let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>();
596 cursor.seek(&point, Bias::Right, &());
597 match cursor.item() {
598 Some(Transform::Isomorphic(_)) => {
599 let overshoot = point.0 - cursor.start().0 .0;
600 SuggestionPoint(cursor.start().1 .0 + overshoot)
601 }
602 Some(Transform::Inlay(_)) => cursor.start().1,
603 None => self.suggestion_snapshot.max_point(),
604 }
605 }
606
607 pub fn to_suggestion_offset(&self, offset: InlayOffset) -> SuggestionOffset {
608 let mut cursor = self.transforms.cursor::<(InlayOffset, SuggestionOffset)>();
609 cursor.seek(&offset, Bias::Right, &());
610 match cursor.item() {
611 Some(Transform::Isomorphic(_)) => {
612 let overshoot = offset - cursor.start().0;
613 cursor.start().1 + SuggestionOffset(overshoot.0)
614 }
615 Some(Transform::Inlay(_)) => cursor.start().1,
616 None => self.suggestion_snapshot.len(),
617 }
618 }
619
620 pub fn to_inlay_point(&self, point: SuggestionPoint) -> InlayPoint {
621 let mut cursor = self.transforms.cursor::<(SuggestionPoint, InlayPoint)>();
622 cursor.seek(&point, Bias::Left, &());
623 match cursor.item() {
624 Some(Transform::Isomorphic(_)) => {
625 let overshoot = point.0 - cursor.start().0 .0;
626 InlayPoint(cursor.start().1 .0 + overshoot)
627 }
628 Some(Transform::Inlay(_)) => cursor.start().1,
629 None => self.max_point(),
630 }
631 }
632
633 pub fn clip_point(&self, point: InlayPoint, bias: Bias) -> InlayPoint {
634 let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>();
635 cursor.seek(&point, Bias::Left, &());
636
637 let mut bias = bias;
638 let mut skipped_inlay = false;
639 loop {
640 match cursor.item() {
641 Some(Transform::Isomorphic(transform)) => {
642 let overshoot = if skipped_inlay {
643 match bias {
644 Bias::Left => transform.lines,
645 Bias::Right => {
646 if transform.first_line_chars == 0 {
647 Point::new(1, 0)
648 } else {
649 Point::new(0, 1)
650 }
651 }
652 }
653 } else {
654 point.0 - cursor.start().0 .0
655 };
656 let suggestion_point = SuggestionPoint(cursor.start().1 .0 + overshoot);
657 let clipped_suggestion_point =
658 self.suggestion_snapshot.clip_point(suggestion_point, bias);
659 let clipped_overshoot = clipped_suggestion_point.0 - cursor.start().1 .0;
660 return InlayPoint(cursor.start().0 .0 + clipped_overshoot);
661 }
662 Some(Transform::Inlay(_)) => skipped_inlay = true,
663 None => match bias {
664 Bias::Left => return Default::default(),
665 Bias::Right => bias = Bias::Left,
666 },
667 }
668
669 if bias == Bias::Left {
670 cursor.prev(&());
671 } else {
672 cursor.next(&());
673 }
674 }
675 }
676
677 pub fn text_summary_for_range(&self, range: Range<InlayPoint>) -> TextSummary {
678 let mut summary = TextSummary::default();
679
680 let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>();
681 cursor.seek(&range.start, Bias::Right, &());
682
683 let overshoot = range.start.0 - cursor.start().0 .0;
684 match cursor.item() {
685 Some(Transform::Isomorphic(_)) => {
686 let suggestion_start = cursor.start().1 .0;
687 let suffix_start = SuggestionPoint(suggestion_start + overshoot);
688 let suffix_end = SuggestionPoint(
689 suggestion_start
690 + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0),
691 );
692 summary = self
693 .suggestion_snapshot
694 .text_summary_for_range(suffix_start..suffix_end);
695 cursor.next(&());
696 }
697 Some(Transform::Inlay(inlay)) => {
698 let suffix_start = inlay.text.point_to_offset(overshoot);
699 let suffix_end = inlay.text.point_to_offset(
700 cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0,
701 );
702 summary = inlay.text.cursor(suffix_start).summary(suffix_end);
703 cursor.next(&());
704 }
705 None => {}
706 }
707
708 if range.end > cursor.start().0 {
709 summary += cursor
710 .summary::<_, TransformSummary>(&range.end, Bias::Right, &())
711 .output;
712
713 let overshoot = range.end.0 - cursor.start().0 .0;
714 match cursor.item() {
715 Some(Transform::Isomorphic(_)) => {
716 let prefix_start = cursor.start().1;
717 let prefix_end = SuggestionPoint(prefix_start.0 + overshoot);
718 summary += self
719 .suggestion_snapshot
720 .text_summary_for_range(prefix_start..prefix_end);
721 }
722 Some(Transform::Inlay(inlay)) => {
723 let prefix_end = inlay.text.point_to_offset(overshoot);
724 summary += inlay.text.cursor(0).summary::<TextSummary>(prefix_end);
725 }
726 None => {}
727 }
728 }
729
730 summary
731 }
732
733 pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> {
734 let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>();
735 let inlay_point = InlayPoint::new(row, 0);
736 cursor.seek(&inlay_point, Bias::Left, &());
737
738 let mut suggestion_point = cursor.start().1;
739 let suggestion_row = if row == 0 {
740 0
741 } else {
742 match cursor.item() {
743 Some(Transform::Isomorphic(_)) => {
744 suggestion_point.0 += inlay_point.0 - cursor.start().0 .0;
745 suggestion_point.row()
746 }
747 _ => cmp::min(
748 suggestion_point.row() + 1,
749 self.suggestion_snapshot.max_point().row(),
750 ),
751 }
752 };
753
754 InlayBufferRows {
755 transforms: cursor,
756 inlay_row: inlay_point.row(),
757 suggestion_rows: self.suggestion_snapshot.buffer_rows(suggestion_row),
758 }
759 }
760
761 pub fn line_len(&self, row: u32) -> u32 {
762 let line_start = self.to_offset(InlayPoint::new(row, 0)).0;
763 let line_end = if row >= self.max_point().row() {
764 self.len().0
765 } else {
766 self.to_offset(InlayPoint::new(row + 1, 0)).0 - 1
767 };
768 (line_end - line_start) as u32
769 }
770
771 pub fn chunks<'a>(
772 &'a self,
773 range: Range<InlayOffset>,
774 language_aware: bool,
775 text_highlights: Option<&'a TextHighlights>,
776 suggestion_highlight: Option<HighlightStyle>,
777 ) -> InlayChunks<'a> {
778 let mut cursor = self.transforms.cursor::<(InlayOffset, SuggestionOffset)>();
779 cursor.seek(&range.start, Bias::Right, &());
780
781 let suggestion_range =
782 self.to_suggestion_offset(range.start)..self.to_suggestion_offset(range.end);
783 let suggestion_chunks = self.suggestion_snapshot.chunks(
784 suggestion_range,
785 language_aware,
786 text_highlights,
787 suggestion_highlight,
788 );
789
790 InlayChunks {
791 transforms: cursor,
792 suggestion_chunks,
793 inlay_chunks: None,
794 suggestion_chunk: None,
795 output_offset: range.start,
796 max_output_offset: range.end,
797 highlight_style: suggestion_highlight,
798 }
799 }
800
801 #[cfg(test)]
802 pub fn text(&self) -> String {
803 self.chunks(Default::default()..self.len(), false, None, None)
804 .map(|chunk| chunk.text)
805 .collect()
806 }
807
808 fn check_invariants(&self) {
809 #[cfg(any(debug_assertions, feature = "test-support"))]
810 {
811 assert_eq!(
812 self.transforms.summary().input,
813 self.suggestion_snapshot.text_summary()
814 );
815 }
816 }
817}
818
819fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
820 if summary.len == 0 {
821 return;
822 }
823
824 let mut summary = Some(summary);
825 sum_tree.update_last(
826 |transform| {
827 if let Transform::Isomorphic(transform) = transform {
828 *transform += summary.take().unwrap();
829 }
830 },
831 &(),
832 );
833
834 if let Some(summary) = summary {
835 sum_tree.push(Transform::Isomorphic(summary), &());
836 }
837}
838
839#[cfg(test)]
840mod tests {
841 use super::*;
842 use crate::{
843 display_map::{fold_map::FoldMap, suggestion_map::SuggestionMap},
844 MultiBuffer,
845 };
846 use gpui::AppContext;
847 use rand::prelude::*;
848 use settings::SettingsStore;
849 use std::env;
850 use text::Patch;
851 use util::post_inc;
852
853 #[gpui::test]
854 fn test_basic_inlays(cx: &mut AppContext) {
855 let buffer = MultiBuffer::build_simple("abcdefghi", cx);
856 let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
857 let (fold_map, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
858 let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone());
859 let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone());
860 assert_eq!(inlay_snapshot.text(), "abcdefghi");
861 let mut next_inlay_id = 0;
862
863 let (inlay_snapshot, _) = inlay_map.splice(
864 Vec::new(),
865 vec![(
866 InlayId(post_inc(&mut next_inlay_id)),
867 InlayProperties {
868 position: buffer.read(cx).snapshot(cx).anchor_after(3),
869 text: "|123|",
870 },
871 )],
872 );
873 assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
874 assert_eq!(
875 inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 0)),
876 InlayPoint::new(0, 0)
877 );
878 assert_eq!(
879 inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 1)),
880 InlayPoint::new(0, 1)
881 );
882 assert_eq!(
883 inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 2)),
884 InlayPoint::new(0, 2)
885 );
886 assert_eq!(
887 inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 3)),
888 InlayPoint::new(0, 3)
889 );
890 assert_eq!(
891 inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 4)),
892 InlayPoint::new(0, 9)
893 );
894 assert_eq!(
895 inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 5)),
896 InlayPoint::new(0, 10)
897 );
898 assert_eq!(
899 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
900 InlayPoint::new(0, 0)
901 );
902 assert_eq!(
903 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
904 InlayPoint::new(0, 0)
905 );
906 assert_eq!(
907 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
908 InlayPoint::new(0, 3)
909 );
910 assert_eq!(
911 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
912 InlayPoint::new(0, 3)
913 );
914 assert_eq!(
915 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
916 InlayPoint::new(0, 3)
917 );
918 assert_eq!(
919 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
920 InlayPoint::new(0, 9)
921 );
922
923 // Edits before or after the inlay should not affect it.
924 buffer.update(cx, |buffer, cx| {
925 buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx)
926 });
927 let (fold_snapshot, fold_edits) = fold_map.read(
928 buffer.read(cx).snapshot(cx),
929 buffer_edits.consume().into_inner(),
930 );
931 let (suggestion_snapshot, suggestion_edits) =
932 suggestion_map.sync(fold_snapshot.clone(), fold_edits);
933 let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
934 assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
935
936 // An edit surrounding the inlay should invalidate it.
937 buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx));
938 let (fold_snapshot, fold_edits) = fold_map.read(
939 buffer.read(cx).snapshot(cx),
940 buffer_edits.consume().into_inner(),
941 );
942 let (suggestion_snapshot, suggestion_edits) =
943 suggestion_map.sync(fold_snapshot.clone(), fold_edits);
944 let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
945 assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
946
947 let (inlay_snapshot, _) = inlay_map.splice(
948 Vec::new(),
949 vec![
950 (
951 InlayId(post_inc(&mut next_inlay_id)),
952 InlayProperties {
953 position: buffer.read(cx).snapshot(cx).anchor_before(3),
954 text: "|123|",
955 },
956 ),
957 (
958 InlayId(post_inc(&mut next_inlay_id)),
959 InlayProperties {
960 position: buffer.read(cx).snapshot(cx).anchor_after(3),
961 text: "|456|",
962 },
963 ),
964 ],
965 );
966 assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
967
968 // Edits ending where the inlay starts should not move it if it has a left bias.
969 buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx));
970 let (fold_snapshot, fold_edits) = fold_map.read(
971 buffer.read(cx).snapshot(cx),
972 buffer_edits.consume().into_inner(),
973 );
974 let (suggestion_snapshot, suggestion_edits) =
975 suggestion_map.sync(fold_snapshot.clone(), fold_edits);
976 let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
977 assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
978
979 // The inlays can be manually removed.
980 let (inlay_snapshot, _) = inlay_map
981 .splice::<String>(inlay_map.inlays_by_id.keys().copied().collect(), Vec::new());
982 assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi");
983 }
984
985 #[gpui::test]
986 fn test_buffer_rows(cx: &mut AppContext) {
987 let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx);
988 let (_, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
989 let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone());
990 let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone());
991 assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi");
992
993 let (inlay_snapshot, _) = inlay_map.splice(
994 Vec::new(),
995 vec![
996 (
997 InlayId(0),
998 InlayProperties {
999 position: buffer.read(cx).snapshot(cx).anchor_before(0),
1000 text: "|123|\n",
1001 },
1002 ),
1003 (
1004 InlayId(1),
1005 InlayProperties {
1006 position: buffer.read(cx).snapshot(cx).anchor_before(4),
1007 text: "|456|",
1008 },
1009 ),
1010 (
1011 InlayId(1),
1012 InlayProperties {
1013 position: buffer.read(cx).snapshot(cx).anchor_before(7),
1014 text: "\n|567|\n",
1015 },
1016 ),
1017 ],
1018 );
1019 assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
1020 assert_eq!(
1021 inlay_snapshot.buffer_rows(0).collect::<Vec<_>>(),
1022 vec![Some(0), None, Some(1), None, None, Some(2)]
1023 );
1024 }
1025
1026 #[gpui::test(iterations = 100)]
1027 fn test_random_inlays(cx: &mut AppContext, mut rng: StdRng) {
1028 init_test(cx);
1029
1030 let operations = env::var("OPERATIONS")
1031 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1032 .unwrap_or(10);
1033
1034 let len = rng.gen_range(0..30);
1035 let buffer = if rng.gen() {
1036 let text = util::RandomCharIter::new(&mut rng)
1037 .take(len)
1038 .collect::<String>();
1039 MultiBuffer::build_simple(&text, cx)
1040 } else {
1041 MultiBuffer::build_random(&mut rng, cx)
1042 };
1043 let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1044 log::info!("buffer text: {:?}", buffer_snapshot.text());
1045
1046 let (mut fold_map, mut fold_snapshot) = FoldMap::new(buffer_snapshot.clone());
1047 let (suggestion_map, mut suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone());
1048 let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone());
1049 let mut next_inlay_id = 0;
1050
1051 for _ in 0..operations {
1052 let mut suggestion_edits = Patch::default();
1053 let mut inlay_edits = Patch::default();
1054
1055 let mut prev_inlay_text = inlay_snapshot.text();
1056 let mut buffer_edits = Vec::new();
1057 match rng.gen_range(0..=100) {
1058 0..=29 => {
1059 let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1060 log::info!("mutated text: {:?}", snapshot.text());
1061 inlay_edits = Patch::new(edits);
1062 }
1063 30..=59 => {
1064 for (new_fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) {
1065 fold_snapshot = new_fold_snapshot;
1066 let (_, edits) = suggestion_map.sync(fold_snapshot.clone(), fold_edits);
1067 suggestion_edits = suggestion_edits.compose(edits);
1068 }
1069 }
1070 _ => buffer.update(cx, |buffer, cx| {
1071 let subscription = buffer.subscribe();
1072 let edit_count = rng.gen_range(1..=5);
1073 buffer.randomly_mutate(&mut rng, edit_count, cx);
1074 buffer_snapshot = buffer.snapshot(cx);
1075 let edits = subscription.consume().into_inner();
1076 log::info!("editing {:?}", edits);
1077 buffer_edits.extend(edits);
1078 }),
1079 };
1080
1081 let (new_fold_snapshot, fold_edits) =
1082 fold_map.read(buffer_snapshot.clone(), buffer_edits);
1083 fold_snapshot = new_fold_snapshot;
1084 let (new_suggestion_snapshot, new_suggestion_edits) =
1085 suggestion_map.sync(fold_snapshot.clone(), fold_edits);
1086 suggestion_snapshot = new_suggestion_snapshot;
1087 suggestion_edits = suggestion_edits.compose(new_suggestion_edits);
1088 let (new_inlay_snapshot, new_inlay_edits) =
1089 inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits.into_inner());
1090 inlay_snapshot = new_inlay_snapshot;
1091 inlay_edits = inlay_edits.compose(new_inlay_edits);
1092
1093 log::info!("buffer text: {:?}", buffer_snapshot.text());
1094 log::info!("folds text: {:?}", fold_snapshot.text());
1095 log::info!("suggestions text: {:?}", suggestion_snapshot.text());
1096 log::info!("inlay text: {:?}", inlay_snapshot.text());
1097
1098 let inlays = inlay_map
1099 .inlays
1100 .iter()
1101 .filter(|inlay| inlay.position.is_valid(&buffer_snapshot))
1102 .map(|inlay| {
1103 let buffer_point = inlay.position.to_point(&buffer_snapshot);
1104 let fold_point = fold_snapshot.to_fold_point(buffer_point, Bias::Left);
1105 let suggestion_point = suggestion_snapshot.to_suggestion_point(fold_point);
1106 let suggestion_offset = suggestion_snapshot.to_offset(suggestion_point);
1107 (suggestion_offset, inlay.clone())
1108 })
1109 .collect::<Vec<_>>();
1110 let mut expected_text = Rope::from(suggestion_snapshot.text().as_str());
1111 for (offset, inlay) in inlays.into_iter().rev() {
1112 expected_text.replace(offset.0..offset.0, &inlay.text.to_string());
1113 }
1114 assert_eq!(inlay_snapshot.text(), expected_text.to_string());
1115
1116 let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::<Vec<_>>();
1117 assert_eq!(
1118 expected_buffer_rows.len() as u32,
1119 expected_text.max_point().row + 1
1120 );
1121 for row_start in 0..expected_buffer_rows.len() {
1122 assert_eq!(
1123 inlay_snapshot
1124 .buffer_rows(row_start as u32)
1125 .collect::<Vec<_>>(),
1126 &expected_buffer_rows[row_start..],
1127 "incorrect buffer rows starting at {}",
1128 row_start
1129 );
1130 }
1131
1132 for _ in 0..5 {
1133 let mut end = rng.gen_range(0..=inlay_snapshot.len().0);
1134 end = expected_text.clip_offset(end, Bias::Right);
1135 let mut start = rng.gen_range(0..=end);
1136 start = expected_text.clip_offset(start, Bias::Right);
1137
1138 let actual_text = inlay_snapshot
1139 .chunks(InlayOffset(start)..InlayOffset(end), false, None, None)
1140 .map(|chunk| chunk.text)
1141 .collect::<String>();
1142 assert_eq!(
1143 actual_text,
1144 expected_text.slice(start..end).to_string(),
1145 "incorrect text in range {:?}",
1146 start..end
1147 );
1148
1149 let start_point = InlayPoint(expected_text.offset_to_point(start));
1150 let end_point = InlayPoint(expected_text.offset_to_point(end));
1151 assert_eq!(
1152 inlay_snapshot.text_summary_for_range(start_point..end_point),
1153 expected_text.slice(start..end).summary()
1154 );
1155 }
1156
1157 for edit in inlay_edits {
1158 prev_inlay_text.replace_range(
1159 edit.new.start.0..edit.new.start.0 + edit.old_len().0,
1160 &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0],
1161 );
1162 }
1163 assert_eq!(prev_inlay_text, inlay_snapshot.text());
1164
1165 assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
1166 assert_eq!(expected_text.len(), inlay_snapshot.len().0);
1167
1168 let mut inlay_point = InlayPoint::default();
1169 let mut inlay_offset = InlayOffset::default();
1170 for ch in expected_text.chars() {
1171 assert_eq!(
1172 inlay_snapshot.to_offset(inlay_point),
1173 inlay_offset,
1174 "invalid to_offset({:?})",
1175 inlay_point
1176 );
1177 assert_eq!(
1178 inlay_snapshot.to_point(inlay_offset),
1179 inlay_point,
1180 "invalid to_point({:?})",
1181 inlay_offset
1182 );
1183 assert_eq!(
1184 inlay_snapshot.to_inlay_point(inlay_snapshot.to_suggestion_point(inlay_point)),
1185 inlay_snapshot.clip_point(inlay_point, Bias::Left),
1186 "to_suggestion_point({:?}) = {:?}",
1187 inlay_point,
1188 inlay_snapshot.to_suggestion_point(inlay_point),
1189 );
1190
1191 let mut bytes = [0; 4];
1192 for byte in ch.encode_utf8(&mut bytes).as_bytes() {
1193 inlay_offset.0 += 1;
1194 if *byte == b'\n' {
1195 inlay_point.0 += Point::new(1, 0);
1196 } else {
1197 inlay_point.0 += Point::new(0, 1);
1198 }
1199
1200 let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left);
1201 let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right);
1202 assert!(
1203 clipped_left_point <= clipped_right_point,
1204 "clipped left point {:?} is greater than clipped right point {:?}",
1205 clipped_left_point,
1206 clipped_right_point
1207 );
1208
1209 // Ensure the clipped points are at valid text locations.
1210 assert_eq!(
1211 clipped_left_point.0,
1212 expected_text.clip_point(clipped_left_point.0, Bias::Left)
1213 );
1214 assert_eq!(
1215 clipped_right_point.0,
1216 expected_text.clip_point(clipped_right_point.0, Bias::Right)
1217 );
1218
1219 // Ensure the clipped points never overshoot the end of the map.
1220 assert!(clipped_left_point <= inlay_snapshot.max_point());
1221 assert!(clipped_right_point <= inlay_snapshot.max_point());
1222 }
1223 }
1224 }
1225 }
1226
1227 fn init_test(cx: &mut AppContext) {
1228 cx.set_global(SettingsStore::test(cx));
1229 theme::init((), cx);
1230 }
1231}