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