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 .filter(|ch| *ch != '\r')
476 .take(len)
477 .collect::<String>();
478 log::info!(
479 "creating inlay at buffer offset {} with bias {:?} and text {:?}",
480 position,
481 bias,
482 text
483 );
484 to_insert.push((
485 InlayId(post_inc(next_inlay_id)),
486 InlayProperties {
487 position: buffer_snapshot.anchor_at(position, bias),
488 text,
489 },
490 ));
491 } else {
492 to_remove.push(*self.inlays_by_id.keys().choose(rng).unwrap());
493 }
494 }
495 log::info!("removing inlays: {:?}", to_remove);
496
497 drop(snapshot);
498 self.splice(to_remove, to_insert)
499 }
500}
501
502impl InlaySnapshot {
503 pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
504 self.fold_snapshot.buffer_snapshot()
505 }
506
507 pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
508 let mut cursor = self
509 .transforms
510 .cursor::<(InlayOffset, (InlayPoint, FoldOffset))>();
511 cursor.seek(&offset, Bias::Right, &());
512 let overshoot = offset.0 - cursor.start().0 .0;
513 match cursor.item() {
514 Some(Transform::Isomorphic(_)) => {
515 let fold_offset_start = cursor.start().1 .1;
516 let fold_offset_end = FoldOffset(fold_offset_start.0 + overshoot);
517 let fold_start = fold_offset_start.to_point(&self.fold_snapshot);
518 let fold_end = fold_offset_end.to_point(&self.fold_snapshot);
519 InlayPoint(cursor.start().1 .0 .0 + (fold_end.0 - fold_start.0))
520 }
521 Some(Transform::Inlay(inlay)) => {
522 let overshoot = inlay.text.offset_to_point(overshoot);
523 InlayPoint(cursor.start().1 .0 .0 + overshoot)
524 }
525 None => self.max_point(),
526 }
527 }
528
529 pub fn len(&self) -> InlayOffset {
530 InlayOffset(self.transforms.summary().output.len)
531 }
532
533 pub fn max_point(&self) -> InlayPoint {
534 InlayPoint(self.transforms.summary().output.lines)
535 }
536
537 pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
538 let mut cursor = self
539 .transforms
540 .cursor::<(InlayPoint, (InlayOffset, FoldPoint))>();
541 cursor.seek(&point, Bias::Right, &());
542 let overshoot = point.0 - cursor.start().0 .0;
543 match cursor.item() {
544 Some(Transform::Isomorphic(_)) => {
545 let fold_point_start = cursor.start().1 .1;
546 let fold_point_end = FoldPoint(fold_point_start.0 + overshoot);
547 let fold_start = fold_point_start.to_offset(&self.fold_snapshot);
548 let fold_end = fold_point_end.to_offset(&self.fold_snapshot);
549 InlayOffset(cursor.start().1 .0 .0 + (fold_end.0 - fold_start.0))
550 }
551 Some(Transform::Inlay(inlay)) => {
552 let overshoot = inlay.text.point_to_offset(overshoot);
553 InlayOffset(cursor.start().1 .0 .0 + overshoot)
554 }
555 None => self.len(),
556 }
557 }
558
559 pub fn chars_at(&self, start: InlayPoint) -> impl '_ + Iterator<Item = char> {
560 self.chunks(self.to_offset(start)..self.len(), false, None, None)
561 .flat_map(|chunk| chunk.text.chars())
562 }
563
564 pub fn to_fold_point(&self, point: InlayPoint) -> FoldPoint {
565 let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>();
566 cursor.seek(&point, Bias::Right, &());
567 match cursor.item() {
568 Some(Transform::Isomorphic(_)) => {
569 let overshoot = point.0 - cursor.start().0 .0;
570 FoldPoint(cursor.start().1 .0 + overshoot)
571 }
572 Some(Transform::Inlay(_)) => cursor.start().1,
573 None => self.fold_snapshot.max_point(),
574 }
575 }
576
577 pub fn to_fold_offset(&self, offset: InlayOffset) -> FoldOffset {
578 let mut cursor = self.transforms.cursor::<(InlayOffset, FoldOffset)>();
579 cursor.seek(&offset, Bias::Right, &());
580 match cursor.item() {
581 Some(Transform::Isomorphic(_)) => {
582 let overshoot = offset - cursor.start().0;
583 cursor.start().1 + FoldOffset(overshoot.0)
584 }
585 Some(Transform::Inlay(_)) => cursor.start().1,
586 None => self.fold_snapshot.len(),
587 }
588 }
589
590 pub fn to_inlay_point(&self, point: FoldPoint) -> InlayPoint {
591 let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>();
592 cursor.seek(&point, Bias::Left, &());
593 match cursor.item() {
594 Some(Transform::Isomorphic(_)) => {
595 let overshoot = point.0 - cursor.start().0 .0;
596 InlayPoint(cursor.start().1 .0 + overshoot)
597 }
598 Some(Transform::Inlay(_)) => cursor.start().1,
599 None => self.max_point(),
600 }
601 }
602
603 pub fn clip_point(&self, point: InlayPoint, bias: Bias) -> InlayPoint {
604 let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>();
605 cursor.seek(&point, Bias::Left, &());
606
607 let mut bias = bias;
608 let mut skipped_inlay = false;
609 loop {
610 match cursor.item() {
611 Some(Transform::Isomorphic(transform)) => {
612 let overshoot = if skipped_inlay {
613 match bias {
614 Bias::Left => transform.lines,
615 Bias::Right => {
616 if transform.first_line_chars == 0 {
617 Point::new(1, 0)
618 } else {
619 Point::new(0, 1)
620 }
621 }
622 }
623 } else {
624 point.0 - cursor.start().0 .0
625 };
626 let fold_point = FoldPoint(cursor.start().1 .0 + overshoot);
627 let clipped_fold_point = self.fold_snapshot.clip_point(fold_point, bias);
628 let clipped_overshoot = clipped_fold_point.0 - cursor.start().1 .0;
629 return InlayPoint(cursor.start().0 .0 + clipped_overshoot);
630 }
631 Some(Transform::Inlay(_)) => skipped_inlay = true,
632 None => match bias {
633 Bias::Left => return Default::default(),
634 Bias::Right => bias = Bias::Left,
635 },
636 }
637
638 if bias == Bias::Left {
639 cursor.prev(&());
640 } else {
641 cursor.next(&());
642 }
643 }
644 }
645
646 pub fn text_summary_for_range(&self, range: Range<InlayPoint>) -> TextSummary {
647 let mut summary = TextSummary::default();
648
649 let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>();
650 cursor.seek(&range.start, Bias::Right, &());
651
652 let overshoot = range.start.0 - cursor.start().0 .0;
653 match cursor.item() {
654 Some(Transform::Isomorphic(_)) => {
655 let fold_start = cursor.start().1 .0;
656 let suffix_start = FoldPoint(fold_start + overshoot);
657 let suffix_end = FoldPoint(
658 fold_start + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0),
659 );
660 summary = self
661 .fold_snapshot
662 .text_summary_for_range(suffix_start..suffix_end);
663 cursor.next(&());
664 }
665 Some(Transform::Inlay(inlay)) => {
666 let suffix_start = inlay.text.point_to_offset(overshoot);
667 let suffix_end = inlay.text.point_to_offset(
668 cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0,
669 );
670 summary = inlay.text.cursor(suffix_start).summary(suffix_end);
671 cursor.next(&());
672 }
673 None => {}
674 }
675
676 if range.end > cursor.start().0 {
677 summary += cursor
678 .summary::<_, TransformSummary>(&range.end, Bias::Right, &())
679 .output;
680
681 let overshoot = range.end.0 - cursor.start().0 .0;
682 match cursor.item() {
683 Some(Transform::Isomorphic(_)) => {
684 let prefix_start = cursor.start().1;
685 let prefix_end = FoldPoint(prefix_start.0 + overshoot);
686 summary += self
687 .fold_snapshot
688 .text_summary_for_range(prefix_start..prefix_end);
689 }
690 Some(Transform::Inlay(inlay)) => {
691 let prefix_end = inlay.text.point_to_offset(overshoot);
692 summary += inlay.text.cursor(0).summary::<TextSummary>(prefix_end);
693 }
694 None => {}
695 }
696 }
697
698 summary
699 }
700
701 pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> {
702 let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>();
703 let inlay_point = InlayPoint::new(row, 0);
704 cursor.seek(&inlay_point, Bias::Left, &());
705
706 let mut fold_point = cursor.start().1;
707 let fold_row = if row == 0 {
708 0
709 } else {
710 match cursor.item() {
711 Some(Transform::Isomorphic(_)) => {
712 fold_point.0 += inlay_point.0 - cursor.start().0 .0;
713 fold_point.row()
714 }
715 _ => cmp::min(fold_point.row() + 1, self.fold_snapshot.max_point().row()),
716 }
717 };
718
719 InlayBufferRows {
720 transforms: cursor,
721 inlay_row: inlay_point.row(),
722 fold_rows: self.fold_snapshot.buffer_rows(fold_row),
723 }
724 }
725
726 pub fn line_len(&self, row: u32) -> u32 {
727 let line_start = self.to_offset(InlayPoint::new(row, 0)).0;
728 let line_end = if row >= self.max_point().row() {
729 self.len().0
730 } else {
731 self.to_offset(InlayPoint::new(row + 1, 0)).0 - 1
732 };
733 (line_end - line_start) as u32
734 }
735
736 pub fn chunks<'a>(
737 &'a self,
738 range: Range<InlayOffset>,
739 language_aware: bool,
740 text_highlights: Option<&'a TextHighlights>,
741 inlay_highlight_style: Option<HighlightStyle>,
742 ) -> InlayChunks<'a> {
743 let mut cursor = self.transforms.cursor::<(InlayOffset, FoldOffset)>();
744 cursor.seek(&range.start, Bias::Right, &());
745
746 let fold_range = self.to_fold_offset(range.start)..self.to_fold_offset(range.end);
747 let fold_chunks = self
748 .fold_snapshot
749 .chunks(fold_range, language_aware, text_highlights);
750
751 InlayChunks {
752 transforms: cursor,
753 fold_chunks,
754 inlay_chunks: None,
755 fold_chunk: None,
756 output_offset: range.start,
757 max_output_offset: range.end,
758 highlight_style: inlay_highlight_style,
759 }
760 }
761
762 #[cfg(test)]
763 pub fn text(&self) -> String {
764 self.chunks(Default::default()..self.len(), false, None, None)
765 .map(|chunk| chunk.text)
766 .collect()
767 }
768
769 fn check_invariants(&self) {
770 #[cfg(any(debug_assertions, feature = "test-support"))]
771 {
772 assert_eq!(
773 self.transforms.summary().input,
774 self.fold_snapshot.text_summary()
775 );
776 }
777 }
778}
779
780fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
781 if summary.len == 0 {
782 return;
783 }
784
785 let mut summary = Some(summary);
786 sum_tree.update_last(
787 |transform| {
788 if let Transform::Isomorphic(transform) = transform {
789 *transform += summary.take().unwrap();
790 }
791 },
792 &(),
793 );
794
795 if let Some(summary) = summary {
796 sum_tree.push(Transform::Isomorphic(summary), &());
797 }
798}
799
800#[cfg(test)]
801mod tests {
802 use super::*;
803 use crate::{display_map::fold_map::FoldMap, MultiBuffer};
804 use gpui::AppContext;
805 use rand::prelude::*;
806 use settings::SettingsStore;
807 use std::env;
808 use text::Patch;
809 use util::post_inc;
810
811 #[gpui::test]
812 fn test_basic_inlays(cx: &mut AppContext) {
813 let buffer = MultiBuffer::build_simple("abcdefghi", cx);
814 let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
815 let (fold_map, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
816 let (mut inlay_map, inlay_snapshot) = InlayMap::new(fold_snapshot.clone());
817 assert_eq!(inlay_snapshot.text(), "abcdefghi");
818 let mut next_inlay_id = 0;
819
820 let (inlay_snapshot, _) = inlay_map.splice(
821 Vec::new(),
822 vec![(
823 InlayId(post_inc(&mut next_inlay_id)),
824 InlayProperties {
825 position: buffer.read(cx).snapshot(cx).anchor_after(3),
826 text: "|123|",
827 },
828 )],
829 );
830 assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
831 assert_eq!(
832 inlay_snapshot.to_inlay_point(FoldPoint::new(0, 0)),
833 InlayPoint::new(0, 0)
834 );
835 assert_eq!(
836 inlay_snapshot.to_inlay_point(FoldPoint::new(0, 1)),
837 InlayPoint::new(0, 1)
838 );
839 assert_eq!(
840 inlay_snapshot.to_inlay_point(FoldPoint::new(0, 2)),
841 InlayPoint::new(0, 2)
842 );
843 assert_eq!(
844 inlay_snapshot.to_inlay_point(FoldPoint::new(0, 3)),
845 InlayPoint::new(0, 3)
846 );
847 assert_eq!(
848 inlay_snapshot.to_inlay_point(FoldPoint::new(0, 4)),
849 InlayPoint::new(0, 9)
850 );
851 assert_eq!(
852 inlay_snapshot.to_inlay_point(FoldPoint::new(0, 5)),
853 InlayPoint::new(0, 10)
854 );
855 assert_eq!(
856 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
857 InlayPoint::new(0, 0)
858 );
859 assert_eq!(
860 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
861 InlayPoint::new(0, 0)
862 );
863 assert_eq!(
864 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
865 InlayPoint::new(0, 3)
866 );
867 assert_eq!(
868 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
869 InlayPoint::new(0, 3)
870 );
871 assert_eq!(
872 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
873 InlayPoint::new(0, 3)
874 );
875 assert_eq!(
876 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
877 InlayPoint::new(0, 9)
878 );
879
880 // Edits before or after the inlay should not affect it.
881 buffer.update(cx, |buffer, cx| {
882 buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx)
883 });
884 let (fold_snapshot, fold_edits) = fold_map.read(
885 buffer.read(cx).snapshot(cx),
886 buffer_edits.consume().into_inner(),
887 );
888 let (inlay_snapshot, _) = inlay_map.sync(fold_snapshot.clone(), fold_edits);
889 assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
890
891 // An edit surrounding the inlay should invalidate it.
892 buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx));
893 let (fold_snapshot, fold_edits) = fold_map.read(
894 buffer.read(cx).snapshot(cx),
895 buffer_edits.consume().into_inner(),
896 );
897 let (inlay_snapshot, _) = inlay_map.sync(fold_snapshot.clone(), fold_edits);
898 assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
899
900 let (inlay_snapshot, _) = inlay_map.splice(
901 Vec::new(),
902 vec![
903 (
904 InlayId(post_inc(&mut next_inlay_id)),
905 InlayProperties {
906 position: buffer.read(cx).snapshot(cx).anchor_before(3),
907 text: "|123|",
908 },
909 ),
910 (
911 InlayId(post_inc(&mut next_inlay_id)),
912 InlayProperties {
913 position: buffer.read(cx).snapshot(cx).anchor_after(3),
914 text: "|456|",
915 },
916 ),
917 ],
918 );
919 assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
920
921 // Edits ending where the inlay starts should not move it if it has a left bias.
922 buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx));
923 let (fold_snapshot, fold_edits) = fold_map.read(
924 buffer.read(cx).snapshot(cx),
925 buffer_edits.consume().into_inner(),
926 );
927 let (inlay_snapshot, _) = inlay_map.sync(fold_snapshot.clone(), fold_edits);
928 assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
929
930 // The inlays can be manually removed.
931 let (inlay_snapshot, _) = inlay_map
932 .splice::<String>(inlay_map.inlays_by_id.keys().copied().collect(), Vec::new());
933 assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi");
934 }
935
936 #[gpui::test]
937 fn test_buffer_rows(cx: &mut AppContext) {
938 let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx);
939 let (_, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
940 let (mut inlay_map, inlay_snapshot) = InlayMap::new(fold_snapshot.clone());
941 assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi");
942
943 let (inlay_snapshot, _) = inlay_map.splice(
944 Vec::new(),
945 vec![
946 (
947 InlayId(0),
948 InlayProperties {
949 position: buffer.read(cx).snapshot(cx).anchor_before(0),
950 text: "|123|\n",
951 },
952 ),
953 (
954 InlayId(1),
955 InlayProperties {
956 position: buffer.read(cx).snapshot(cx).anchor_before(4),
957 text: "|456|",
958 },
959 ),
960 (
961 InlayId(1),
962 InlayProperties {
963 position: buffer.read(cx).snapshot(cx).anchor_before(7),
964 text: "\n|567|\n",
965 },
966 ),
967 ],
968 );
969 assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
970 assert_eq!(
971 inlay_snapshot.buffer_rows(0).collect::<Vec<_>>(),
972 vec![Some(0), None, Some(1), None, None, Some(2)]
973 );
974 }
975
976 #[gpui::test(iterations = 100)]
977 fn test_random_inlays(cx: &mut AppContext, mut rng: StdRng) {
978 init_test(cx);
979
980 let operations = env::var("OPERATIONS")
981 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
982 .unwrap_or(10);
983
984 let len = rng.gen_range(0..30);
985 let buffer = if rng.gen() {
986 let text = util::RandomCharIter::new(&mut rng)
987 .take(len)
988 .collect::<String>();
989 MultiBuffer::build_simple(&text, cx)
990 } else {
991 MultiBuffer::build_random(&mut rng, cx)
992 };
993 let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
994 log::info!("buffer text: {:?}", buffer_snapshot.text());
995
996 let (mut fold_map, mut fold_snapshot) = FoldMap::new(buffer_snapshot.clone());
997 let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(fold_snapshot.clone());
998 let mut next_inlay_id = 0;
999
1000 for _ in 0..operations {
1001 let mut fold_edits = Patch::default();
1002 let mut inlay_edits = Patch::default();
1003
1004 let mut prev_inlay_text = inlay_snapshot.text();
1005 let mut buffer_edits = Vec::new();
1006 match rng.gen_range(0..=100) {
1007 0..=29 => {
1008 let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1009 log::info!("mutated text: {:?}", snapshot.text());
1010 inlay_edits = Patch::new(edits);
1011 }
1012 30..=59 => {
1013 for (_, edits) in fold_map.randomly_mutate(&mut rng) {
1014 fold_edits = fold_edits.compose(edits);
1015 }
1016 }
1017 _ => buffer.update(cx, |buffer, cx| {
1018 let subscription = buffer.subscribe();
1019 let edit_count = rng.gen_range(1..=5);
1020 buffer.randomly_mutate(&mut rng, edit_count, cx);
1021 buffer_snapshot = buffer.snapshot(cx);
1022 let edits = subscription.consume().into_inner();
1023 log::info!("editing {:?}", edits);
1024 buffer_edits.extend(edits);
1025 }),
1026 };
1027
1028 let (new_fold_snapshot, new_fold_edits) =
1029 fold_map.read(buffer_snapshot.clone(), buffer_edits);
1030 fold_snapshot = new_fold_snapshot;
1031 fold_edits = fold_edits.compose(new_fold_edits);
1032 let (new_inlay_snapshot, new_inlay_edits) =
1033 inlay_map.sync(fold_snapshot.clone(), fold_edits.into_inner());
1034 inlay_snapshot = new_inlay_snapshot;
1035 inlay_edits = inlay_edits.compose(new_inlay_edits);
1036
1037 log::info!("buffer text: {:?}", buffer_snapshot.text());
1038 log::info!("folds text: {:?}", fold_snapshot.text());
1039 log::info!("inlay text: {:?}", inlay_snapshot.text());
1040
1041 let inlays = inlay_map
1042 .inlays
1043 .iter()
1044 .filter(|inlay| inlay.position.is_valid(&buffer_snapshot))
1045 .map(|inlay| {
1046 let buffer_point = inlay.position.to_point(&buffer_snapshot);
1047 let fold_point = fold_snapshot.to_fold_point(buffer_point, Bias::Left);
1048 let fold_offset = fold_point.to_offset(&fold_snapshot);
1049 (fold_offset, inlay.clone())
1050 })
1051 .collect::<Vec<_>>();
1052 let mut expected_text = Rope::from(fold_snapshot.text().as_str());
1053 for (offset, inlay) in inlays.into_iter().rev() {
1054 expected_text.replace(offset.0..offset.0, &inlay.text.to_string());
1055 }
1056 assert_eq!(inlay_snapshot.text(), expected_text.to_string());
1057
1058 let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::<Vec<_>>();
1059 assert_eq!(
1060 expected_buffer_rows.len() as u32,
1061 expected_text.max_point().row + 1
1062 );
1063 for row_start in 0..expected_buffer_rows.len() {
1064 assert_eq!(
1065 inlay_snapshot
1066 .buffer_rows(row_start as u32)
1067 .collect::<Vec<_>>(),
1068 &expected_buffer_rows[row_start..],
1069 "incorrect buffer rows starting at {}",
1070 row_start
1071 );
1072 }
1073
1074 for _ in 0..5 {
1075 let mut end = rng.gen_range(0..=inlay_snapshot.len().0);
1076 end = expected_text.clip_offset(end, Bias::Right);
1077 let mut start = rng.gen_range(0..=end);
1078 start = expected_text.clip_offset(start, Bias::Right);
1079
1080 let actual_text = inlay_snapshot
1081 .chunks(InlayOffset(start)..InlayOffset(end), false, None, None)
1082 .map(|chunk| chunk.text)
1083 .collect::<String>();
1084 assert_eq!(
1085 actual_text,
1086 expected_text.slice(start..end).to_string(),
1087 "incorrect text in range {:?}",
1088 start..end
1089 );
1090
1091 let start_point = InlayPoint(expected_text.offset_to_point(start));
1092 let end_point = InlayPoint(expected_text.offset_to_point(end));
1093 assert_eq!(
1094 inlay_snapshot.text_summary_for_range(start_point..end_point),
1095 expected_text.slice(start..end).summary()
1096 );
1097 }
1098
1099 for edit in inlay_edits {
1100 prev_inlay_text.replace_range(
1101 edit.new.start.0..edit.new.start.0 + edit.old_len().0,
1102 &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0],
1103 );
1104 }
1105 assert_eq!(prev_inlay_text, inlay_snapshot.text());
1106
1107 assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
1108 assert_eq!(expected_text.len(), inlay_snapshot.len().0);
1109
1110 let mut inlay_point = InlayPoint::default();
1111 let mut inlay_offset = InlayOffset::default();
1112 for ch in expected_text.chars() {
1113 assert_eq!(
1114 inlay_snapshot.to_offset(inlay_point),
1115 inlay_offset,
1116 "invalid to_offset({:?})",
1117 inlay_point
1118 );
1119 assert_eq!(
1120 inlay_snapshot.to_point(inlay_offset),
1121 inlay_point,
1122 "invalid to_point({:?})",
1123 inlay_offset
1124 );
1125 assert_eq!(
1126 inlay_snapshot.to_inlay_point(inlay_snapshot.to_fold_point(inlay_point)),
1127 inlay_snapshot.clip_point(inlay_point, Bias::Left),
1128 "to_fold_point({:?}) = {:?}",
1129 inlay_point,
1130 inlay_snapshot.to_fold_point(inlay_point),
1131 );
1132
1133 let mut bytes = [0; 4];
1134 for byte in ch.encode_utf8(&mut bytes).as_bytes() {
1135 inlay_offset.0 += 1;
1136 if *byte == b'\n' {
1137 inlay_point.0 += Point::new(1, 0);
1138 } else {
1139 inlay_point.0 += Point::new(0, 1);
1140 }
1141
1142 let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left);
1143 let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right);
1144 assert!(
1145 clipped_left_point <= clipped_right_point,
1146 "clipped left point {:?} is greater than clipped right point {:?}",
1147 clipped_left_point,
1148 clipped_right_point
1149 );
1150
1151 // Ensure the clipped points are at valid text locations.
1152 assert_eq!(
1153 clipped_left_point.0,
1154 expected_text.clip_point(clipped_left_point.0, Bias::Left)
1155 );
1156 assert_eq!(
1157 clipped_right_point.0,
1158 expected_text.clip_point(clipped_right_point.0, Bias::Right)
1159 );
1160
1161 // Ensure the clipped points never overshoot the end of the map.
1162 assert!(clipped_left_point <= inlay_snapshot.max_point());
1163 assert!(clipped_right_point <= inlay_snapshot.max_point());
1164 }
1165 }
1166 }
1167 }
1168
1169 fn init_test(cx: &mut AppContext) {
1170 cx.set_global(SettingsStore::test(cx));
1171 theme::init((), cx);
1172 }
1173}