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