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