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