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.append(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &());
359 if let Some(Transform::Isomorphic(transform)) = cursor.item() {
360 if cursor.end(&()).0 == buffer_edit.old.start {
361 new_transforms.push(Transform::Isomorphic(transform.clone()), &());
362 cursor.next(&());
363 }
364 }
365
366 // Remove all the inlays and transforms contained by the edit.
367 let old_start =
368 cursor.start().1 + InlayOffset(buffer_edit.old.start - cursor.start().0);
369 cursor.seek(&buffer_edit.old.end, Bias::Right, &());
370 let old_end =
371 cursor.start().1 + InlayOffset(buffer_edit.old.end - cursor.start().0);
372
373 // Push the unchanged prefix.
374 let prefix_start = new_transforms.summary().input.len;
375 let prefix_end = buffer_edit.new.start;
376 push_isomorphic(
377 &mut new_transforms,
378 buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
379 );
380 let new_start = InlayOffset(new_transforms.summary().output.len);
381
382 let start_ix = match self.inlays.binary_search_by(|probe| {
383 probe
384 .position
385 .to_offset(&buffer_snapshot)
386 .cmp(&buffer_edit.new.start)
387 .then(std::cmp::Ordering::Greater)
388 }) {
389 Ok(ix) | Err(ix) => ix,
390 };
391
392 for inlay in &self.inlays[start_ix..] {
393 let buffer_offset = inlay.position.to_offset(&buffer_snapshot);
394 if buffer_offset > buffer_edit.new.end {
395 break;
396 }
397
398 let prefix_start = new_transforms.summary().input.len;
399 let prefix_end = buffer_offset;
400 push_isomorphic(
401 &mut new_transforms,
402 buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
403 );
404
405 if inlay.position.is_valid(&buffer_snapshot) {
406 new_transforms.push(Transform::Inlay(inlay.clone()), &());
407 }
408 }
409
410 // Apply the rest of the edit.
411 let transform_start = new_transforms.summary().input.len;
412 push_isomorphic(
413 &mut new_transforms,
414 buffer_snapshot.text_summary_for_range(transform_start..buffer_edit.new.end),
415 );
416 let new_end = InlayOffset(new_transforms.summary().output.len);
417 inlay_edits.push(Edit {
418 old: old_start..old_end,
419 new: new_start..new_end,
420 });
421
422 // If the next edit doesn't intersect the current isomorphic transform, then
423 // we can push its remainder.
424 if buffer_edits_iter
425 .peek()
426 .map_or(true, |edit| edit.old.start >= cursor.end(&()).0)
427 {
428 let transform_start = new_transforms.summary().input.len;
429 let transform_end =
430 buffer_edit.new.end + (cursor.end(&()).0 - buffer_edit.old.end);
431 push_isomorphic(
432 &mut new_transforms,
433 buffer_snapshot.text_summary_for_range(transform_start..transform_end),
434 );
435 cursor.next(&());
436 }
437 }
438
439 new_transforms.append(cursor.suffix(&()), &());
440 if new_transforms.first().is_none() {
441 new_transforms.push(Transform::Isomorphic(Default::default()), &());
442 }
443
444 drop(cursor);
445 snapshot.transforms = new_transforms;
446 snapshot.version += 1;
447 snapshot.buffer = buffer_snapshot;
448 snapshot.check_invariants();
449
450 (snapshot.clone(), inlay_edits.into_inner())
451 }
452 }
453
454 pub fn splice<T: Into<Rope>>(
455 &mut self,
456 to_remove: Vec<InlayId>,
457 to_insert: Vec<(InlayId, InlayProperties<T>)>,
458 ) -> (InlaySnapshot, Vec<InlayEdit>) {
459 let snapshot = self.snapshot.lock();
460 let mut edits = BTreeSet::new();
461
462 self.inlays.retain(|inlay| !to_remove.contains(&inlay.id));
463 for inlay_id in to_remove {
464 if let Some(inlay) = self.inlays_by_id.remove(&inlay_id) {
465 let offset = inlay.position.to_offset(&snapshot.buffer);
466 edits.insert(offset);
467 }
468 }
469
470 for (existing_id, properties) in to_insert {
471 let inlay = Inlay {
472 id: existing_id,
473 position: properties.position,
474 text: properties.text.into(),
475 };
476
477 // Avoid inserting empty inlays.
478 if inlay.text.is_empty() {
479 continue;
480 }
481
482 self.inlays_by_id.insert(inlay.id, inlay.clone());
483 match self
484 .inlays
485 .binary_search_by(|probe| probe.position.cmp(&inlay.position, &snapshot.buffer))
486 {
487 Ok(ix) | Err(ix) => {
488 self.inlays.insert(ix, inlay.clone());
489 }
490 }
491
492 let offset = inlay.position.to_offset(&snapshot.buffer);
493 edits.insert(offset);
494 }
495
496 let buffer_edits = edits
497 .into_iter()
498 .map(|offset| Edit {
499 old: offset..offset,
500 new: offset..offset,
501 })
502 .collect();
503 let buffer_snapshot = snapshot.buffer.clone();
504 drop(snapshot);
505 let (snapshot, edits) = self.sync(buffer_snapshot, buffer_edits);
506 (snapshot, edits)
507 }
508
509 pub fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
510 self.inlays.iter()
511 }
512
513 #[cfg(test)]
514 pub(crate) fn randomly_mutate(
515 &mut self,
516 next_inlay_id: &mut usize,
517 rng: &mut rand::rngs::StdRng,
518 ) -> (InlaySnapshot, Vec<InlayEdit>) {
519 use rand::prelude::*;
520 use util::post_inc;
521
522 let mut to_remove = Vec::new();
523 let mut to_insert = Vec::new();
524 let snapshot = self.snapshot.lock();
525 for _ in 0..rng.gen_range(1..=5) {
526 if self.inlays.is_empty() || rng.gen() {
527 let position = snapshot.buffer.random_byte_range(0, rng).start;
528 let bias = if rng.gen() { Bias::Left } else { Bias::Right };
529 let len = if rng.gen_bool(0.01) {
530 0
531 } else {
532 rng.gen_range(1..=5)
533 };
534 let text = util::RandomCharIter::new(&mut *rng)
535 .filter(|ch| *ch != '\r')
536 .take(len)
537 .collect::<String>();
538 log::info!(
539 "creating inlay at buffer offset {} with bias {:?} and text {:?}",
540 position,
541 bias,
542 text
543 );
544 to_insert.push((
545 InlayId(post_inc(next_inlay_id)),
546 InlayProperties {
547 position: snapshot.buffer.anchor_at(position, bias),
548 text,
549 },
550 ));
551 } else {
552 to_remove.push(*self.inlays_by_id.keys().choose(rng).unwrap());
553 }
554 }
555 log::info!("removing inlays: {:?}", to_remove);
556
557 drop(snapshot);
558 let (snapshot, edits) = self.splice(to_remove, to_insert);
559 (snapshot, edits)
560 }
561}
562
563impl InlaySnapshot {
564 pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
565 let mut cursor = self
566 .transforms
567 .cursor::<(InlayOffset, (InlayPoint, usize))>();
568 cursor.seek(&offset, Bias::Right, &());
569 let overshoot = offset.0 - cursor.start().0 .0;
570 match cursor.item() {
571 Some(Transform::Isomorphic(_)) => {
572 let buffer_offset_start = cursor.start().1 .1;
573 let buffer_offset_end = buffer_offset_start + overshoot;
574 let buffer_start = self.buffer.offset_to_point(buffer_offset_start);
575 let buffer_end = self.buffer.offset_to_point(buffer_offset_end);
576 InlayPoint(cursor.start().1 .0 .0 + (buffer_end - buffer_start))
577 }
578 Some(Transform::Inlay(inlay)) => {
579 let overshoot = inlay.text.offset_to_point(overshoot);
580 InlayPoint(cursor.start().1 .0 .0 + overshoot)
581 }
582 None => self.max_point(),
583 }
584 }
585
586 pub fn len(&self) -> InlayOffset {
587 InlayOffset(self.transforms.summary().output.len)
588 }
589
590 pub fn max_point(&self) -> InlayPoint {
591 InlayPoint(self.transforms.summary().output.lines)
592 }
593
594 pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
595 let mut cursor = self
596 .transforms
597 .cursor::<(InlayPoint, (InlayOffset, Point))>();
598 cursor.seek(&point, Bias::Right, &());
599 let overshoot = point.0 - cursor.start().0 .0;
600 match cursor.item() {
601 Some(Transform::Isomorphic(_)) => {
602 let buffer_point_start = cursor.start().1 .1;
603 let buffer_point_end = buffer_point_start + overshoot;
604 let buffer_offset_start = self.buffer.point_to_offset(buffer_point_start);
605 let buffer_offset_end = self.buffer.point_to_offset(buffer_point_end);
606 InlayOffset(cursor.start().1 .0 .0 + (buffer_offset_end - buffer_offset_start))
607 }
608 Some(Transform::Inlay(inlay)) => {
609 let overshoot = inlay.text.point_to_offset(overshoot);
610 InlayOffset(cursor.start().1 .0 .0 + overshoot)
611 }
612 None => self.len(),
613 }
614 }
615
616 pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
617 let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
618 cursor.seek(&point, Bias::Right, &());
619 match cursor.item() {
620 Some(Transform::Isomorphic(_)) => {
621 let overshoot = point.0 - cursor.start().0 .0;
622 cursor.start().1 + overshoot
623 }
624 Some(Transform::Inlay(_)) => cursor.start().1,
625 None => self.buffer.max_point(),
626 }
627 }
628
629 pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize {
630 let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
631 cursor.seek(&offset, Bias::Right, &());
632 match cursor.item() {
633 Some(Transform::Isomorphic(_)) => {
634 let overshoot = offset - cursor.start().0;
635 cursor.start().1 + overshoot.0
636 }
637 Some(Transform::Inlay(_)) => cursor.start().1,
638 None => self.buffer.len(),
639 }
640 }
641
642 pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset {
643 let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>();
644 cursor.seek(&offset, Bias::Left, &());
645 match cursor.item() {
646 Some(Transform::Isomorphic(_)) => {
647 let overshoot = offset - cursor.start().0;
648 InlayOffset(cursor.start().1 .0 + overshoot)
649 }
650 Some(Transform::Inlay(_)) => cursor.start().1,
651 None => self.len(),
652 }
653 }
654
655 pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
656 let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>();
657 cursor.seek(&point, Bias::Left, &());
658 match cursor.item() {
659 Some(Transform::Isomorphic(_)) => {
660 let overshoot = point - cursor.start().0;
661 InlayPoint(cursor.start().1 .0 + overshoot)
662 }
663 Some(Transform::Inlay(_)) => cursor.start().1,
664 None => self.max_point(),
665 }
666 }
667
668 pub fn clip_point(&self, point: InlayPoint, bias: Bias) -> InlayPoint {
669 let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
670 cursor.seek(&point, Bias::Left, &());
671
672 let mut bias = bias;
673 let mut skipped_inlay = false;
674 loop {
675 match cursor.item() {
676 Some(Transform::Isomorphic(transform)) => {
677 let overshoot = if skipped_inlay {
678 match bias {
679 Bias::Left => transform.lines,
680 Bias::Right => {
681 if transform.first_line_chars == 0 {
682 Point::new(1, 0)
683 } else {
684 Point::new(0, 1)
685 }
686 }
687 }
688 } else {
689 point.0 - cursor.start().0 .0
690 };
691 let buffer_point = cursor.start().1 + overshoot;
692 let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias);
693 let clipped_overshoot = clipped_buffer_point - cursor.start().1;
694 return InlayPoint(cursor.start().0 .0 + clipped_overshoot);
695 }
696 Some(Transform::Inlay(_)) => skipped_inlay = true,
697 None => match bias {
698 Bias::Left => return Default::default(),
699 Bias::Right => bias = Bias::Left,
700 },
701 }
702
703 if bias == Bias::Left {
704 cursor.prev(&());
705 } else {
706 cursor.next(&());
707 }
708 }
709 }
710
711 pub fn text_summary(&self) -> TextSummary {
712 self.transforms.summary().output.clone()
713 }
714
715 pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> TextSummary {
716 let mut summary = TextSummary::default();
717
718 let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
719 cursor.seek(&range.start, Bias::Right, &());
720
721 let overshoot = range.start.0 - cursor.start().0 .0;
722 match cursor.item() {
723 Some(Transform::Isomorphic(_)) => {
724 let buffer_start = cursor.start().1;
725 let suffix_start = buffer_start + overshoot;
726 let suffix_end =
727 buffer_start + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0);
728 summary = self.buffer.text_summary_for_range(suffix_start..suffix_end);
729 cursor.next(&());
730 }
731 Some(Transform::Inlay(inlay)) => {
732 let suffix_start = overshoot;
733 let suffix_end = cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0;
734 summary = inlay.text.cursor(suffix_start).summary(suffix_end);
735 cursor.next(&());
736 }
737 None => {}
738 }
739
740 if range.end > cursor.start().0 {
741 summary += cursor
742 .summary::<_, TransformSummary>(&range.end, Bias::Right, &())
743 .output;
744
745 let overshoot = range.end.0 - cursor.start().0 .0;
746 match cursor.item() {
747 Some(Transform::Isomorphic(_)) => {
748 let prefix_start = cursor.start().1;
749 let prefix_end = prefix_start + overshoot;
750 summary += self
751 .buffer
752 .text_summary_for_range::<TextSummary, _>(prefix_start..prefix_end);
753 }
754 Some(Transform::Inlay(inlay)) => {
755 let prefix_end = overshoot;
756 summary += inlay.text.cursor(0).summary::<TextSummary>(prefix_end);
757 }
758 None => {}
759 }
760 }
761
762 summary
763 }
764
765 pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> {
766 let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
767 let inlay_point = InlayPoint::new(row, 0);
768 cursor.seek(&inlay_point, Bias::Left, &());
769
770 let max_buffer_row = self.buffer.max_point().row;
771 let mut buffer_point = cursor.start().1;
772 let buffer_row = if row == 0 {
773 0
774 } else {
775 match cursor.item() {
776 Some(Transform::Isomorphic(_)) => {
777 buffer_point += inlay_point.0 - cursor.start().0 .0;
778 buffer_point.row
779 }
780 _ => cmp::min(buffer_point.row + 1, max_buffer_row),
781 }
782 };
783
784 InlayBufferRows {
785 transforms: cursor,
786 inlay_row: inlay_point.row(),
787 buffer_rows: self.buffer.buffer_rows(buffer_row),
788 max_buffer_row,
789 }
790 }
791
792 pub fn line_len(&self, row: u32) -> u32 {
793 let line_start = self.to_offset(InlayPoint::new(row, 0)).0;
794 let line_end = if row >= self.max_point().row() {
795 self.len().0
796 } else {
797 self.to_offset(InlayPoint::new(row + 1, 0)).0 - 1
798 };
799 (line_end - line_start) as u32
800 }
801
802 pub fn chunks<'a>(
803 &'a self,
804 range: Range<InlayOffset>,
805 language_aware: bool,
806 inlay_highlight_style: Option<HighlightStyle>,
807 ) -> InlayChunks<'a> {
808 let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
809 cursor.seek(&range.start, Bias::Right, &());
810
811 let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end);
812 let buffer_chunks = self.buffer.chunks(buffer_range, language_aware);
813
814 InlayChunks {
815 transforms: cursor,
816 buffer_chunks,
817 inlay_chunks: None,
818 buffer_chunk: None,
819 output_offset: range.start,
820 max_output_offset: range.end,
821 highlight_style: inlay_highlight_style,
822 snapshot: self,
823 }
824 }
825
826 #[cfg(test)]
827 pub fn text(&self) -> String {
828 self.chunks(Default::default()..self.len(), false, None)
829 .map(|chunk| chunk.text)
830 .collect()
831 }
832
833 fn check_invariants(&self) {
834 #[cfg(any(debug_assertions, feature = "test-support"))]
835 {
836 assert_eq!(self.transforms.summary().input, self.buffer.text_summary());
837 }
838 }
839}
840
841fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
842 if summary.len == 0 {
843 return;
844 }
845
846 let mut summary = Some(summary);
847 sum_tree.update_last(
848 |transform| {
849 if let Transform::Isomorphic(transform) = transform {
850 *transform += summary.take().unwrap();
851 }
852 },
853 &(),
854 );
855
856 if let Some(summary) = summary {
857 sum_tree.push(Transform::Isomorphic(summary), &());
858 }
859}
860
861#[cfg(test)]
862mod tests {
863 use super::*;
864 use crate::{InlayId, MultiBuffer};
865 use gpui::AppContext;
866 use rand::prelude::*;
867 use settings::SettingsStore;
868 use std::env;
869 use text::Patch;
870 use util::post_inc;
871
872 #[gpui::test]
873 fn test_basic_inlays(cx: &mut AppContext) {
874 let buffer = MultiBuffer::build_simple("abcdefghi", cx);
875 let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
876 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
877 assert_eq!(inlay_snapshot.text(), "abcdefghi");
878 let mut next_inlay_id = 0;
879
880 let (inlay_snapshot, _) = inlay_map.splice(
881 Vec::new(),
882 vec![(
883 InlayId(post_inc(&mut next_inlay_id)),
884 InlayProperties {
885 position: buffer.read(cx).snapshot(cx).anchor_after(3),
886 text: "|123|",
887 },
888 )],
889 );
890 assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
891 assert_eq!(
892 inlay_snapshot.to_inlay_point(Point::new(0, 0)),
893 InlayPoint::new(0, 0)
894 );
895 assert_eq!(
896 inlay_snapshot.to_inlay_point(Point::new(0, 1)),
897 InlayPoint::new(0, 1)
898 );
899 assert_eq!(
900 inlay_snapshot.to_inlay_point(Point::new(0, 2)),
901 InlayPoint::new(0, 2)
902 );
903 assert_eq!(
904 inlay_snapshot.to_inlay_point(Point::new(0, 3)),
905 InlayPoint::new(0, 3)
906 );
907 assert_eq!(
908 inlay_snapshot.to_inlay_point(Point::new(0, 4)),
909 InlayPoint::new(0, 9)
910 );
911 assert_eq!(
912 inlay_snapshot.to_inlay_point(Point::new(0, 5)),
913 InlayPoint::new(0, 10)
914 );
915 assert_eq!(
916 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
917 InlayPoint::new(0, 0)
918 );
919 assert_eq!(
920 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
921 InlayPoint::new(0, 0)
922 );
923 assert_eq!(
924 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
925 InlayPoint::new(0, 3)
926 );
927 assert_eq!(
928 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
929 InlayPoint::new(0, 3)
930 );
931 assert_eq!(
932 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
933 InlayPoint::new(0, 3)
934 );
935 assert_eq!(
936 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
937 InlayPoint::new(0, 9)
938 );
939
940 // Edits before or after the inlay should not affect it.
941 buffer.update(cx, |buffer, cx| {
942 buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx)
943 });
944 let (inlay_snapshot, _) = inlay_map.sync(
945 buffer.read(cx).snapshot(cx),
946 buffer_edits.consume().into_inner(),
947 );
948 assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
949
950 // An edit surrounding the inlay should invalidate it.
951 buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx));
952 let (inlay_snapshot, _) = inlay_map.sync(
953 buffer.read(cx).snapshot(cx),
954 buffer_edits.consume().into_inner(),
955 );
956 assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
957
958 let (inlay_snapshot, _) = inlay_map.splice(
959 Vec::new(),
960 vec![
961 (
962 InlayId(post_inc(&mut next_inlay_id)),
963 InlayProperties {
964 position: buffer.read(cx).snapshot(cx).anchor_before(3),
965 text: "|123|",
966 },
967 ),
968 (
969 InlayId(post_inc(&mut next_inlay_id)),
970 InlayProperties {
971 position: buffer.read(cx).snapshot(cx).anchor_after(3),
972 text: "|456|",
973 },
974 ),
975 ],
976 );
977 assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
978
979 // Edits ending where the inlay starts should not move it if it has a left bias.
980 buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx));
981 let (inlay_snapshot, _) = inlay_map.sync(
982 buffer.read(cx).snapshot(cx),
983 buffer_edits.consume().into_inner(),
984 );
985 assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
986
987 // The inlays can be manually removed.
988 let (inlay_snapshot, _) = inlay_map
989 .splice::<String>(inlay_map.inlays_by_id.keys().copied().collect(), Vec::new());
990 assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi");
991 }
992
993 #[gpui::test]
994 fn test_inlay_buffer_rows(cx: &mut AppContext) {
995 let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx);
996 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
997 assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi");
998 let mut next_inlay_id = 0;
999
1000 let (inlay_snapshot, _) = inlay_map.splice(
1001 Vec::new(),
1002 vec![
1003 (
1004 InlayId(post_inc(&mut next_inlay_id)),
1005 InlayProperties {
1006 position: buffer.read(cx).snapshot(cx).anchor_before(0),
1007 text: "|123|\n",
1008 },
1009 ),
1010 (
1011 InlayId(post_inc(&mut next_inlay_id)),
1012 InlayProperties {
1013 position: buffer.read(cx).snapshot(cx).anchor_before(4),
1014 text: "|456|",
1015 },
1016 ),
1017 (
1018 InlayId(post_inc(&mut next_inlay_id)),
1019 InlayProperties {
1020 position: buffer.read(cx).snapshot(cx).anchor_before(7),
1021 text: "\n|567|\n",
1022 },
1023 ),
1024 ],
1025 );
1026 assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
1027 assert_eq!(
1028 inlay_snapshot.buffer_rows(0).collect::<Vec<_>>(),
1029 vec![Some(0), None, Some(1), None, None, Some(2)]
1030 );
1031 }
1032
1033 #[gpui::test(iterations = 100)]
1034 fn test_random_inlays(cx: &mut AppContext, mut rng: StdRng) {
1035 init_test(cx);
1036
1037 let operations = env::var("OPERATIONS")
1038 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1039 .unwrap_or(10);
1040
1041 let len = rng.gen_range(0..30);
1042 let buffer = if rng.gen() {
1043 let text = util::RandomCharIter::new(&mut rng)
1044 .take(len)
1045 .collect::<String>();
1046 MultiBuffer::build_simple(&text, cx)
1047 } else {
1048 MultiBuffer::build_random(&mut rng, cx)
1049 };
1050 let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1051 let mut next_inlay_id = 0;
1052 log::info!("buffer text: {:?}", buffer_snapshot.text());
1053
1054 let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1055 for _ in 0..operations {
1056 let mut inlay_edits = Patch::default();
1057
1058 let mut prev_inlay_text = inlay_snapshot.text();
1059 let mut buffer_edits = Vec::new();
1060 match rng.gen_range(0..=100) {
1061 0..=50 => {
1062 let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1063 log::info!("mutated text: {:?}", snapshot.text());
1064 inlay_edits = Patch::new(edits);
1065 }
1066 _ => buffer.update(cx, |buffer, cx| {
1067 let subscription = buffer.subscribe();
1068 let edit_count = rng.gen_range(1..=5);
1069 buffer.randomly_mutate(&mut rng, edit_count, cx);
1070 buffer_snapshot = buffer.snapshot(cx);
1071 let edits = subscription.consume().into_inner();
1072 log::info!("editing {:?}", edits);
1073 buffer_edits.extend(edits);
1074 }),
1075 };
1076
1077 let (new_inlay_snapshot, new_inlay_edits) =
1078 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
1079 inlay_snapshot = new_inlay_snapshot;
1080 inlay_edits = inlay_edits.compose(new_inlay_edits);
1081
1082 log::info!("buffer text: {:?}", buffer_snapshot.text());
1083 log::info!("inlay text: {:?}", inlay_snapshot.text());
1084
1085 let inlays = inlay_map
1086 .inlays
1087 .iter()
1088 .filter(|inlay| inlay.position.is_valid(&buffer_snapshot))
1089 .map(|inlay| {
1090 let offset = inlay.position.to_offset(&buffer_snapshot);
1091 (offset, inlay.clone())
1092 })
1093 .collect::<Vec<_>>();
1094 let mut expected_text = Rope::from(buffer_snapshot.text().as_str());
1095 for (offset, inlay) in inlays.into_iter().rev() {
1096 expected_text.replace(offset..offset, &inlay.text.to_string());
1097 }
1098 assert_eq!(inlay_snapshot.text(), expected_text.to_string());
1099
1100 let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::<Vec<_>>();
1101 assert_eq!(
1102 expected_buffer_rows.len() as u32,
1103 expected_text.max_point().row + 1
1104 );
1105 for row_start in 0..expected_buffer_rows.len() {
1106 assert_eq!(
1107 inlay_snapshot
1108 .buffer_rows(row_start as u32)
1109 .collect::<Vec<_>>(),
1110 &expected_buffer_rows[row_start..],
1111 "incorrect buffer rows starting at {}",
1112 row_start
1113 );
1114 }
1115
1116 for _ in 0..5 {
1117 let mut end = rng.gen_range(0..=inlay_snapshot.len().0);
1118 end = expected_text.clip_offset(end, Bias::Right);
1119 let mut start = rng.gen_range(0..=end);
1120 start = expected_text.clip_offset(start, Bias::Right);
1121
1122 let actual_text = inlay_snapshot
1123 .chunks(InlayOffset(start)..InlayOffset(end), false, None)
1124 .map(|chunk| chunk.text)
1125 .collect::<String>();
1126 assert_eq!(
1127 actual_text,
1128 expected_text.slice(start..end).to_string(),
1129 "incorrect text in range {:?}",
1130 start..end
1131 );
1132
1133 assert_eq!(
1134 inlay_snapshot.text_summary_for_range(InlayOffset(start)..InlayOffset(end)),
1135 expected_text.slice(start..end).summary()
1136 );
1137 }
1138
1139 for edit in inlay_edits {
1140 prev_inlay_text.replace_range(
1141 edit.new.start.0..edit.new.start.0 + edit.old_len().0,
1142 &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0],
1143 );
1144 }
1145 assert_eq!(prev_inlay_text, inlay_snapshot.text());
1146
1147 assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
1148 assert_eq!(expected_text.len(), inlay_snapshot.len().0);
1149
1150 let mut inlay_point = InlayPoint::default();
1151 let mut inlay_offset = InlayOffset::default();
1152 for ch in expected_text.chars() {
1153 assert_eq!(
1154 inlay_snapshot.to_offset(inlay_point),
1155 inlay_offset,
1156 "invalid to_offset({:?})",
1157 inlay_point
1158 );
1159 assert_eq!(
1160 inlay_snapshot.to_point(inlay_offset),
1161 inlay_point,
1162 "invalid to_point({:?})",
1163 inlay_offset
1164 );
1165 assert_eq!(
1166 inlay_snapshot.to_inlay_point(inlay_snapshot.to_buffer_point(inlay_point)),
1167 inlay_snapshot.clip_point(inlay_point, Bias::Left),
1168 "to_buffer_point({:?}) = {:?}",
1169 inlay_point,
1170 inlay_snapshot.to_buffer_point(inlay_point),
1171 );
1172
1173 let mut bytes = [0; 4];
1174 for byte in ch.encode_utf8(&mut bytes).as_bytes() {
1175 inlay_offset.0 += 1;
1176 if *byte == b'\n' {
1177 inlay_point.0 += Point::new(1, 0);
1178 } else {
1179 inlay_point.0 += Point::new(0, 1);
1180 }
1181
1182 let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left);
1183 let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right);
1184 assert!(
1185 clipped_left_point <= clipped_right_point,
1186 "clipped left point {:?} is greater than clipped right point {:?}",
1187 clipped_left_point,
1188 clipped_right_point
1189 );
1190
1191 // Ensure the clipped points are at valid text locations.
1192 assert_eq!(
1193 clipped_left_point.0,
1194 expected_text.clip_point(clipped_left_point.0, Bias::Left)
1195 );
1196 assert_eq!(
1197 clipped_right_point.0,
1198 expected_text.clip_point(clipped_right_point.0, Bias::Right)
1199 );
1200
1201 // Ensure the clipped points never overshoot the end of the map.
1202 assert!(clipped_left_point <= inlay_snapshot.max_point());
1203 assert!(clipped_right_point <= inlay_snapshot.max_point());
1204 }
1205 }
1206 }
1207 }
1208
1209 fn init_test(cx: &mut AppContext) {
1210 cx.set_global(SettingsStore::test(cx));
1211 theme::init((), cx);
1212 }
1213}