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