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