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