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