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