1use crate::{
2 multi_buffer::{MultiBufferChunks, MultiBufferRows},
3 Anchor, InlayId, MultiBufferSnapshot, ToOffset,
4};
5use collections::{BTreeMap, BTreeSet, HashMap};
6use gpui::fonts::HighlightStyle;
7use language::{Chunk, Edit, Point, Rope, TextSummary};
8use std::{
9 any::TypeId,
10 cmp,
11 iter::Peekable,
12 ops::{Add, AddAssign, Range, Sub, SubAssign},
13 vec,
14};
15use sum_tree::{Bias, Cursor, SumTree};
16use text::Patch;
17
18use super::TextHighlights;
19
20pub struct InlayMap {
21 snapshot: InlaySnapshot,
22 inlays_by_id: HashMap<InlayId, Inlay>,
23 inlays: Vec<Inlay>,
24}
25
26#[derive(Clone)]
27pub struct InlaySnapshot {
28 pub buffer: MultiBufferSnapshot,
29 transforms: SumTree<Transform>,
30 pub version: usize,
31}
32
33#[derive(Clone, Debug)]
34enum Transform {
35 Isomorphic(TextSummary),
36 Inlay(Inlay),
37}
38
39#[derive(Debug, Clone)]
40pub struct Inlay {
41 pub id: InlayId,
42 pub position: Anchor,
43 pub text: text::Rope,
44}
45
46#[derive(Debug, Clone)]
47pub struct InlayProperties<T> {
48 pub position: Anchor,
49 pub text: T,
50}
51
52impl sum_tree::Item for Transform {
53 type Summary = TransformSummary;
54
55 fn summary(&self) -> Self::Summary {
56 match self {
57 Transform::Isomorphic(summary) => TransformSummary {
58 input: summary.clone(),
59 output: summary.clone(),
60 },
61 Transform::Inlay(inlay) => TransformSummary {
62 input: TextSummary::default(),
63 output: inlay.text.summary(),
64 },
65 }
66 }
67}
68
69#[derive(Clone, Debug, Default)]
70struct TransformSummary {
71 input: TextSummary,
72 output: TextSummary,
73}
74
75impl sum_tree::Summary for TransformSummary {
76 type Context = ();
77
78 fn add_summary(&mut self, other: &Self, _: &()) {
79 self.input += &other.input;
80 self.output += &other.output;
81 }
82}
83
84pub type InlayEdit = Edit<InlayOffset>;
85
86#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
87pub struct InlayOffset(pub usize);
88
89impl Add for InlayOffset {
90 type Output = Self;
91
92 fn add(self, rhs: Self) -> Self::Output {
93 Self(self.0 + rhs.0)
94 }
95}
96
97impl Sub for InlayOffset {
98 type Output = Self;
99
100 fn sub(self, rhs: Self) -> Self::Output {
101 Self(self.0 - rhs.0)
102 }
103}
104
105impl AddAssign for InlayOffset {
106 fn add_assign(&mut self, rhs: Self) {
107 self.0 += rhs.0;
108 }
109}
110
111impl SubAssign for InlayOffset {
112 fn sub_assign(&mut self, rhs: Self) {
113 self.0 -= rhs.0;
114 }
115}
116
117impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
118 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
119 self.0 += &summary.output.len;
120 }
121}
122
123#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
124pub struct InlayPoint(pub Point);
125
126impl Add for InlayPoint {
127 type Output = Self;
128
129 fn add(self, rhs: Self) -> Self::Output {
130 Self(self.0 + rhs.0)
131 }
132}
133
134impl Sub for InlayPoint {
135 type Output = Self;
136
137 fn sub(self, rhs: Self) -> Self::Output {
138 Self(self.0 - rhs.0)
139 }
140}
141
142impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
143 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
144 self.0 += &summary.output.lines;
145 }
146}
147
148impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
149 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
150 *self += &summary.input.len;
151 }
152}
153
154impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
155 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
156 *self += &summary.input.lines;
157 }
158}
159
160#[derive(Clone)]
161pub struct InlayBufferRows<'a> {
162 transforms: Cursor<'a, Transform, (InlayPoint, Point)>,
163 buffer_rows: MultiBufferRows<'a>,
164 inlay_row: u32,
165 max_buffer_row: u32,
166}
167
168#[derive(Copy, Clone, Eq, PartialEq)]
169struct HighlightEndpoint {
170 offset: InlayOffset,
171 is_start: bool,
172 tag: Option<TypeId>,
173 style: HighlightStyle,
174}
175
176impl PartialOrd for HighlightEndpoint {
177 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
178 Some(self.cmp(other))
179 }
180}
181
182impl Ord for HighlightEndpoint {
183 fn cmp(&self, other: &Self) -> cmp::Ordering {
184 self.offset
185 .cmp(&other.offset)
186 .then_with(|| other.is_start.cmp(&self.is_start))
187 }
188}
189
190pub struct InlayChunks<'a> {
191 transforms: Cursor<'a, Transform, (InlayOffset, usize)>,
192 buffer_chunks: MultiBufferChunks<'a>,
193 buffer_chunk: Option<Chunk<'a>>,
194 inlay_chunks: Option<text::Chunks<'a>>,
195 output_offset: InlayOffset,
196 max_output_offset: InlayOffset,
197 highlight_style: Option<HighlightStyle>,
198 highlight_endpoints: Peekable<vec::IntoIter<HighlightEndpoint>>,
199 active_highlights: BTreeMap<Option<TypeId>, HighlightStyle>,
200 snapshot: &'a InlaySnapshot,
201}
202
203impl<'a> InlayChunks<'a> {
204 pub fn seek(&mut self, offset: InlayOffset) {
205 self.transforms.seek(&offset, Bias::Right, &());
206
207 let buffer_offset = self.snapshot.to_buffer_offset(offset);
208 self.buffer_chunks.seek(buffer_offset);
209 self.inlay_chunks = None;
210 self.buffer_chunk = None;
211 self.output_offset = offset;
212 }
213
214 pub fn offset(&self) -> InlayOffset {
215 self.output_offset
216 }
217}
218
219impl<'a> Iterator for InlayChunks<'a> {
220 type Item = Chunk<'a>;
221
222 fn next(&mut self) -> Option<Self::Item> {
223 if self.output_offset == self.max_output_offset {
224 return None;
225 }
226
227 let mut next_highlight_endpoint = InlayOffset(usize::MAX);
228 while let Some(endpoint) = self.highlight_endpoints.peek().copied() {
229 if endpoint.offset <= self.output_offset {
230 if endpoint.is_start {
231 self.active_highlights.insert(endpoint.tag, endpoint.style);
232 } else {
233 self.active_highlights.remove(&endpoint.tag);
234 }
235 self.highlight_endpoints.next();
236 } else {
237 next_highlight_endpoint = endpoint.offset;
238 break;
239 }
240 }
241
242 let chunk = match self.transforms.item()? {
243 Transform::Isomorphic(_) => {
244 let chunk = self
245 .buffer_chunk
246 .get_or_insert_with(|| self.buffer_chunks.next().unwrap());
247 if chunk.text.is_empty() {
248 *chunk = self.buffer_chunks.next().unwrap();
249 }
250
251 let (prefix, suffix) = chunk.text.split_at(
252 chunk
253 .text
254 .len()
255 .min(self.transforms.end(&()).0 .0 - self.output_offset.0)
256 .min(next_highlight_endpoint.0 - self.output_offset.0),
257 );
258
259 chunk.text = suffix;
260 self.output_offset.0 += prefix.len();
261 let mut prefix = Chunk {
262 text: prefix,
263 ..chunk.clone()
264 };
265 if !self.active_highlights.is_empty() {
266 let mut highlight_style = HighlightStyle::default();
267 for active_highlight in self.active_highlights.values() {
268 highlight_style.highlight(*active_highlight);
269 }
270 prefix.highlight_style = Some(highlight_style);
271 }
272 prefix
273 }
274 Transform::Inlay(inlay) => {
275 let inlay_chunks = self.inlay_chunks.get_or_insert_with(|| {
276 let start = self.output_offset - self.transforms.start().0;
277 let end = cmp::min(self.max_output_offset, self.transforms.end(&()).0)
278 - self.transforms.start().0;
279 inlay.text.chunks_in_range(start.0..end.0)
280 });
281
282 let chunk = inlay_chunks.next().unwrap();
283 self.output_offset.0 += chunk.len();
284 Chunk {
285 text: chunk,
286 highlight_style: self.highlight_style,
287 ..Default::default()
288 }
289 }
290 };
291
292 if self.output_offset == self.transforms.end(&()).0 {
293 self.inlay_chunks = None;
294 self.transforms.next(&());
295 }
296
297 Some(chunk)
298 }
299}
300
301impl<'a> InlayBufferRows<'a> {
302 pub fn seek(&mut self, row: u32) {
303 let inlay_point = InlayPoint::new(row, 0);
304 self.transforms.seek(&inlay_point, Bias::Left, &());
305
306 let mut buffer_point = self.transforms.start().1;
307 let buffer_row = if row == 0 {
308 0
309 } else {
310 match self.transforms.item() {
311 Some(Transform::Isomorphic(_)) => {
312 buffer_point += inlay_point.0 - self.transforms.start().0 .0;
313 buffer_point.row
314 }
315 _ => cmp::min(buffer_point.row + 1, self.max_buffer_row),
316 }
317 };
318 self.inlay_row = inlay_point.row();
319 self.buffer_rows.seek(buffer_row);
320 }
321}
322
323impl<'a> Iterator for InlayBufferRows<'a> {
324 type Item = Option<u32>;
325
326 fn next(&mut self) -> Option<Self::Item> {
327 let buffer_row = if self.inlay_row == 0 {
328 self.buffer_rows.next().unwrap()
329 } else {
330 match self.transforms.item()? {
331 Transform::Inlay(_) => None,
332 Transform::Isomorphic(_) => self.buffer_rows.next().unwrap(),
333 }
334 };
335
336 self.inlay_row += 1;
337 self.transforms
338 .seek_forward(&InlayPoint::new(self.inlay_row, 0), Bias::Left, &());
339
340 Some(buffer_row)
341 }
342}
343
344impl InlayPoint {
345 pub fn new(row: u32, column: u32) -> Self {
346 Self(Point::new(row, column))
347 }
348
349 pub fn row(self) -> u32 {
350 self.0.row
351 }
352}
353
354impl InlayMap {
355 pub fn new(buffer: MultiBufferSnapshot) -> (Self, InlaySnapshot) {
356 let version = 0;
357 let snapshot = InlaySnapshot {
358 buffer: buffer.clone(),
359 transforms: SumTree::from_iter(Some(Transform::Isomorphic(buffer.text_summary())), &()),
360 version,
361 };
362
363 (
364 Self {
365 snapshot: snapshot.clone(),
366 inlays_by_id: HashMap::default(),
367 inlays: Vec::new(),
368 },
369 snapshot,
370 )
371 }
372
373 pub fn sync(
374 &mut self,
375 buffer_snapshot: MultiBufferSnapshot,
376 mut buffer_edits: Vec<text::Edit<usize>>,
377 ) -> (InlaySnapshot, Vec<InlayEdit>) {
378 let mut snapshot = &mut self.snapshot;
379
380 if buffer_edits.is_empty() {
381 if snapshot.buffer.trailing_excerpt_update_count()
382 != buffer_snapshot.trailing_excerpt_update_count()
383 {
384 buffer_edits.push(Edit {
385 old: snapshot.buffer.len()..snapshot.buffer.len(),
386 new: buffer_snapshot.len()..buffer_snapshot.len(),
387 });
388 }
389 }
390
391 if buffer_edits.is_empty() {
392 if snapshot.buffer.edit_count() != buffer_snapshot.edit_count()
393 || snapshot.buffer.parse_count() != buffer_snapshot.parse_count()
394 || snapshot.buffer.diagnostics_update_count()
395 != buffer_snapshot.diagnostics_update_count()
396 || snapshot.buffer.git_diff_update_count()
397 != buffer_snapshot.git_diff_update_count()
398 || snapshot.buffer.trailing_excerpt_update_count()
399 != buffer_snapshot.trailing_excerpt_update_count()
400 {
401 snapshot.version += 1;
402 }
403
404 snapshot.buffer = buffer_snapshot;
405 (snapshot.clone(), Vec::new())
406 } else {
407 let mut inlay_edits = Patch::default();
408 let mut new_transforms = SumTree::new();
409 let mut cursor = snapshot.transforms.cursor::<(usize, InlayOffset)>();
410 let mut buffer_edits_iter = buffer_edits.iter().peekable();
411 while let Some(buffer_edit) = buffer_edits_iter.next() {
412 new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &());
413 if let Some(Transform::Isomorphic(transform)) = cursor.item() {
414 if cursor.end(&()).0 == buffer_edit.old.start {
415 push_isomorphic(&mut new_transforms, transform.clone());
416 cursor.next(&());
417 }
418 }
419
420 // Remove all the inlays and transforms contained by the edit.
421 let old_start =
422 cursor.start().1 + InlayOffset(buffer_edit.old.start - cursor.start().0);
423 cursor.seek(&buffer_edit.old.end, Bias::Right, &());
424 let old_end =
425 cursor.start().1 + InlayOffset(buffer_edit.old.end - cursor.start().0);
426
427 // Push the unchanged prefix.
428 let prefix_start = new_transforms.summary().input.len;
429 let prefix_end = buffer_edit.new.start;
430 push_isomorphic(
431 &mut new_transforms,
432 buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
433 );
434 let new_start = InlayOffset(new_transforms.summary().output.len);
435
436 let start_ix = match self.inlays.binary_search_by(|probe| {
437 probe
438 .position
439 .to_offset(&buffer_snapshot)
440 .cmp(&buffer_edit.new.start)
441 .then(std::cmp::Ordering::Greater)
442 }) {
443 Ok(ix) | Err(ix) => ix,
444 };
445
446 for inlay in &self.inlays[start_ix..] {
447 let buffer_offset = inlay.position.to_offset(&buffer_snapshot);
448 if buffer_offset > buffer_edit.new.end {
449 break;
450 }
451
452 let prefix_start = new_transforms.summary().input.len;
453 let prefix_end = buffer_offset;
454 push_isomorphic(
455 &mut new_transforms,
456 buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
457 );
458
459 if inlay.position.is_valid(&buffer_snapshot) {
460 new_transforms.push(Transform::Inlay(inlay.clone()), &());
461 }
462 }
463
464 // Apply the rest of the edit.
465 let transform_start = new_transforms.summary().input.len;
466 push_isomorphic(
467 &mut new_transforms,
468 buffer_snapshot.text_summary_for_range(transform_start..buffer_edit.new.end),
469 );
470 let new_end = InlayOffset(new_transforms.summary().output.len);
471 inlay_edits.push(Edit {
472 old: old_start..old_end,
473 new: new_start..new_end,
474 });
475
476 // If the next edit doesn't intersect the current isomorphic transform, then
477 // we can push its remainder.
478 if buffer_edits_iter
479 .peek()
480 .map_or(true, |edit| edit.old.start >= cursor.end(&()).0)
481 {
482 let transform_start = new_transforms.summary().input.len;
483 let transform_end =
484 buffer_edit.new.end + (cursor.end(&()).0 - buffer_edit.old.end);
485 push_isomorphic(
486 &mut new_transforms,
487 buffer_snapshot.text_summary_for_range(transform_start..transform_end),
488 );
489 cursor.next(&());
490 }
491 }
492
493 new_transforms.append(cursor.suffix(&()), &());
494 if new_transforms.is_empty() {
495 new_transforms.push(Transform::Isomorphic(Default::default()), &());
496 }
497
498 drop(cursor);
499 snapshot.transforms = new_transforms;
500 snapshot.version += 1;
501 snapshot.buffer = buffer_snapshot;
502 snapshot.check_invariants();
503
504 (snapshot.clone(), inlay_edits.into_inner())
505 }
506 }
507
508 pub fn splice<T: Into<Rope>>(
509 &mut self,
510 to_remove: Vec<InlayId>,
511 to_insert: Vec<(InlayId, InlayProperties<T>)>,
512 ) -> (InlaySnapshot, Vec<InlayEdit>) {
513 let snapshot = &mut self.snapshot;
514 let mut edits = BTreeSet::new();
515
516 self.inlays.retain(|inlay| !to_remove.contains(&inlay.id));
517 for inlay_id in to_remove {
518 if let Some(inlay) = self.inlays_by_id.remove(&inlay_id) {
519 let offset = inlay.position.to_offset(&snapshot.buffer);
520 edits.insert(offset);
521 }
522 }
523
524 for (existing_id, properties) in to_insert {
525 let inlay = Inlay {
526 id: existing_id,
527 position: properties.position,
528 text: properties.text.into(),
529 };
530
531 // Avoid inserting empty inlays.
532 if inlay.text.is_empty() {
533 continue;
534 }
535
536 self.inlays_by_id.insert(inlay.id, inlay.clone());
537 match self
538 .inlays
539 .binary_search_by(|probe| probe.position.cmp(&inlay.position, &snapshot.buffer))
540 {
541 Ok(ix) | Err(ix) => {
542 self.inlays.insert(ix, inlay.clone());
543 }
544 }
545
546 let offset = inlay.position.to_offset(&snapshot.buffer);
547 edits.insert(offset);
548 }
549
550 let buffer_edits = edits
551 .into_iter()
552 .map(|offset| Edit {
553 old: offset..offset,
554 new: offset..offset,
555 })
556 .collect();
557 let buffer_snapshot = snapshot.buffer.clone();
558 drop(snapshot);
559 let (snapshot, edits) = self.sync(buffer_snapshot, buffer_edits);
560 (snapshot, edits)
561 }
562
563 pub fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
564 self.inlays.iter()
565 }
566
567 #[cfg(test)]
568 pub(crate) fn randomly_mutate(
569 &mut self,
570 next_inlay_id: &mut usize,
571 rng: &mut rand::rngs::StdRng,
572 ) -> (InlaySnapshot, Vec<InlayEdit>) {
573 use rand::prelude::*;
574 use util::post_inc;
575
576 let mut to_remove = Vec::new();
577 let mut to_insert = Vec::new();
578 let snapshot = &mut self.snapshot;
579 for _ in 0..rng.gen_range(1..=5) {
580 if self.inlays.is_empty() || rng.gen() {
581 let position = snapshot.buffer.random_byte_range(0, rng).start;
582 let bias = if rng.gen() { Bias::Left } else { Bias::Right };
583 let len = if rng.gen_bool(0.01) {
584 0
585 } else {
586 rng.gen_range(1..=5)
587 };
588 let text = util::RandomCharIter::new(&mut *rng)
589 .filter(|ch| *ch != '\r')
590 .take(len)
591 .collect::<String>();
592 log::info!(
593 "creating inlay at buffer offset {} with bias {:?} and text {:?}",
594 position,
595 bias,
596 text
597 );
598 to_insert.push((
599 InlayId(post_inc(next_inlay_id)),
600 InlayProperties {
601 position: snapshot.buffer.anchor_at(position, bias),
602 text,
603 },
604 ));
605 } else {
606 to_remove.push(*self.inlays_by_id.keys().choose(rng).unwrap());
607 }
608 }
609 log::info!("removing inlays: {:?}", to_remove);
610
611 drop(snapshot);
612 let (snapshot, edits) = self.splice(to_remove, to_insert);
613 (snapshot, edits)
614 }
615}
616
617impl InlaySnapshot {
618 pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
619 let mut cursor = self
620 .transforms
621 .cursor::<(InlayOffset, (InlayPoint, usize))>();
622 cursor.seek(&offset, Bias::Right, &());
623 let overshoot = offset.0 - cursor.start().0 .0;
624 match cursor.item() {
625 Some(Transform::Isomorphic(_)) => {
626 let buffer_offset_start = cursor.start().1 .1;
627 let buffer_offset_end = buffer_offset_start + overshoot;
628 let buffer_start = self.buffer.offset_to_point(buffer_offset_start);
629 let buffer_end = self.buffer.offset_to_point(buffer_offset_end);
630 InlayPoint(cursor.start().1 .0 .0 + (buffer_end - buffer_start))
631 }
632 Some(Transform::Inlay(inlay)) => {
633 let overshoot = inlay.text.offset_to_point(overshoot);
634 InlayPoint(cursor.start().1 .0 .0 + overshoot)
635 }
636 None => self.max_point(),
637 }
638 }
639
640 pub fn len(&self) -> InlayOffset {
641 InlayOffset(self.transforms.summary().output.len)
642 }
643
644 pub fn max_point(&self) -> InlayPoint {
645 InlayPoint(self.transforms.summary().output.lines)
646 }
647
648 pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
649 let mut cursor = self
650 .transforms
651 .cursor::<(InlayPoint, (InlayOffset, Point))>();
652 cursor.seek(&point, Bias::Right, &());
653 let overshoot = point.0 - cursor.start().0 .0;
654 match cursor.item() {
655 Some(Transform::Isomorphic(_)) => {
656 let buffer_point_start = cursor.start().1 .1;
657 let buffer_point_end = buffer_point_start + overshoot;
658 let buffer_offset_start = self.buffer.point_to_offset(buffer_point_start);
659 let buffer_offset_end = self.buffer.point_to_offset(buffer_point_end);
660 InlayOffset(cursor.start().1 .0 .0 + (buffer_offset_end - buffer_offset_start))
661 }
662 Some(Transform::Inlay(inlay)) => {
663 let overshoot = inlay.text.point_to_offset(overshoot);
664 InlayOffset(cursor.start().1 .0 .0 + overshoot)
665 }
666 None => self.len(),
667 }
668 }
669
670 pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
671 let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
672 cursor.seek(&point, Bias::Right, &());
673 match cursor.item() {
674 Some(Transform::Isomorphic(_)) => {
675 let overshoot = point.0 - cursor.start().0 .0;
676 cursor.start().1 + overshoot
677 }
678 Some(Transform::Inlay(_)) => cursor.start().1,
679 None => self.buffer.max_point(),
680 }
681 }
682
683 pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize {
684 let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
685 cursor.seek(&offset, Bias::Right, &());
686 match cursor.item() {
687 Some(Transform::Isomorphic(_)) => {
688 let overshoot = offset - cursor.start().0;
689 cursor.start().1 + overshoot.0
690 }
691 Some(Transform::Inlay(_)) => cursor.start().1,
692 None => self.buffer.len(),
693 }
694 }
695
696 pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset {
697 let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>();
698 cursor.seek(&offset, Bias::Left, &());
699 match cursor.item() {
700 Some(Transform::Isomorphic(_)) => {
701 let overshoot = offset - cursor.start().0;
702 InlayOffset(cursor.start().1 .0 + overshoot)
703 }
704 Some(Transform::Inlay(_)) => cursor.start().1,
705 None => self.len(),
706 }
707 }
708
709 pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
710 let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>();
711 cursor.seek(&point, Bias::Left, &());
712 loop {
713 match cursor.item() {
714 Some(Transform::Isomorphic(_)) => {
715 if point == cursor.end(&()).0 {
716 while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
717 if inlay.position.bias() == Bias::Right {
718 break;
719 } else {
720 cursor.next(&());
721 }
722 }
723 return cursor.end(&()).1;
724 } else {
725 let overshoot = point - cursor.start().0;
726 return InlayPoint(cursor.start().1 .0 + overshoot);
727 }
728 }
729 Some(Transform::Inlay(inlay)) => {
730 if inlay.position.bias() == Bias::Left {
731 cursor.next(&());
732 } else {
733 return cursor.start().1;
734 }
735 }
736 None => {
737 return self.max_point();
738 }
739 }
740 }
741 }
742
743 pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint {
744 let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
745 cursor.seek(&point, Bias::Left, &());
746 loop {
747 match cursor.item() {
748 Some(Transform::Isomorphic(transform)) => {
749 if cursor.start().0 == point {
750 if let Some(Transform::Inlay(inlay)) = cursor.prev_item() {
751 if inlay.position.bias() == Bias::Left {
752 return point;
753 } else if bias == Bias::Left {
754 cursor.prev(&());
755 } else if transform.first_line_chars == 0 {
756 point.0 += Point::new(1, 0);
757 } else {
758 point.0 += Point::new(0, 1);
759 }
760 } else {
761 return point;
762 }
763 } else if cursor.end(&()).0 == point {
764 if let Some(Transform::Inlay(inlay)) = cursor.next_item() {
765 if inlay.position.bias() == Bias::Right {
766 return point;
767 } else if bias == Bias::Right {
768 cursor.next(&());
769 } else if point.0.column == 0 {
770 point.0.row -= 1;
771 point.0.column = self.line_len(point.0.row);
772 } else {
773 point.0.column -= 1;
774 }
775 } else {
776 return point;
777 }
778 } else {
779 let overshoot = point.0 - cursor.start().0 .0;
780 let buffer_point = cursor.start().1 + overshoot;
781 let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias);
782 let clipped_overshoot = clipped_buffer_point - cursor.start().1;
783 let clipped_point = InlayPoint(cursor.start().0 .0 + clipped_overshoot);
784 if clipped_point == point {
785 return clipped_point;
786 } else {
787 point = clipped_point;
788 }
789 }
790 }
791 Some(Transform::Inlay(inlay)) => {
792 if point == cursor.start().0 && inlay.position.bias() == Bias::Right {
793 match cursor.prev_item() {
794 Some(Transform::Inlay(inlay)) => {
795 if inlay.position.bias() == Bias::Left {
796 return point;
797 }
798 }
799 _ => return point,
800 }
801 } else if point == cursor.end(&()).0 && inlay.position.bias() == Bias::Left {
802 match cursor.next_item() {
803 Some(Transform::Inlay(inlay)) => {
804 if inlay.position.bias() == Bias::Right {
805 return point;
806 }
807 }
808 _ => return point,
809 }
810 }
811
812 if bias == Bias::Left {
813 point = cursor.start().0;
814 cursor.prev(&());
815 } else {
816 cursor.next(&());
817 point = cursor.start().0;
818 }
819 }
820 None => {
821 bias = bias.invert();
822 if bias == Bias::Left {
823 point = cursor.start().0;
824 cursor.prev(&());
825 } else {
826 cursor.next(&());
827 point = cursor.start().0;
828 }
829 }
830 }
831 }
832 }
833
834 pub fn text_summary(&self) -> TextSummary {
835 self.transforms.summary().output.clone()
836 }
837
838 pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> TextSummary {
839 let mut summary = TextSummary::default();
840
841 let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
842 cursor.seek(&range.start, Bias::Right, &());
843
844 let overshoot = range.start.0 - cursor.start().0 .0;
845 match cursor.item() {
846 Some(Transform::Isomorphic(_)) => {
847 let buffer_start = cursor.start().1;
848 let suffix_start = buffer_start + overshoot;
849 let suffix_end =
850 buffer_start + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0);
851 summary = self.buffer.text_summary_for_range(suffix_start..suffix_end);
852 cursor.next(&());
853 }
854 Some(Transform::Inlay(inlay)) => {
855 let suffix_start = overshoot;
856 let suffix_end = cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0;
857 summary = inlay.text.cursor(suffix_start).summary(suffix_end);
858 cursor.next(&());
859 }
860 None => {}
861 }
862
863 if range.end > cursor.start().0 {
864 summary += cursor
865 .summary::<_, TransformSummary>(&range.end, Bias::Right, &())
866 .output;
867
868 let overshoot = range.end.0 - cursor.start().0 .0;
869 match cursor.item() {
870 Some(Transform::Isomorphic(_)) => {
871 let prefix_start = cursor.start().1;
872 let prefix_end = prefix_start + overshoot;
873 summary += self
874 .buffer
875 .text_summary_for_range::<TextSummary, _>(prefix_start..prefix_end);
876 }
877 Some(Transform::Inlay(inlay)) => {
878 let prefix_end = overshoot;
879 summary += inlay.text.cursor(0).summary::<TextSummary>(prefix_end);
880 }
881 None => {}
882 }
883 }
884
885 summary
886 }
887
888 pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> {
889 let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
890 let inlay_point = InlayPoint::new(row, 0);
891 cursor.seek(&inlay_point, Bias::Left, &());
892
893 let max_buffer_row = self.buffer.max_point().row;
894 let mut buffer_point = cursor.start().1;
895 let buffer_row = if row == 0 {
896 0
897 } else {
898 match cursor.item() {
899 Some(Transform::Isomorphic(_)) => {
900 buffer_point += inlay_point.0 - cursor.start().0 .0;
901 buffer_point.row
902 }
903 _ => cmp::min(buffer_point.row + 1, max_buffer_row),
904 }
905 };
906
907 InlayBufferRows {
908 transforms: cursor,
909 inlay_row: inlay_point.row(),
910 buffer_rows: self.buffer.buffer_rows(buffer_row),
911 max_buffer_row,
912 }
913 }
914
915 pub fn line_len(&self, row: u32) -> u32 {
916 let line_start = self.to_offset(InlayPoint::new(row, 0)).0;
917 let line_end = if row >= self.max_point().row() {
918 self.len().0
919 } else {
920 self.to_offset(InlayPoint::new(row + 1, 0)).0 - 1
921 };
922 (line_end - line_start) as u32
923 }
924
925 pub fn chunks<'a>(
926 &'a self,
927 range: Range<InlayOffset>,
928 language_aware: bool,
929 text_highlights: Option<&'a TextHighlights>,
930 inlay_highlight_style: Option<HighlightStyle>,
931 ) -> InlayChunks<'a> {
932 let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
933 cursor.seek(&range.start, Bias::Right, &());
934
935 let mut highlight_endpoints = Vec::new();
936 if let Some(text_highlights) = text_highlights {
937 if !text_highlights.is_empty() {
938 while cursor.start().0 < range.end {
939 if true {
940 let transform_start = self.buffer.anchor_after(
941 self.to_buffer_offset(cmp::max(range.start, cursor.start().0)),
942 );
943
944 let transform_end = {
945 let overshoot = InlayOffset(range.end.0 - cursor.start().0 .0);
946 self.buffer.anchor_before(self.to_buffer_offset(cmp::min(
947 cursor.end(&()).0,
948 cursor.start().0 + overshoot,
949 )))
950 };
951
952 for (tag, highlights) in text_highlights.iter() {
953 let style = highlights.0;
954 let ranges = &highlights.1;
955
956 let start_ix = match ranges.binary_search_by(|probe| {
957 let cmp = probe.end.cmp(&transform_start, &self.buffer);
958 if cmp.is_gt() {
959 cmp::Ordering::Greater
960 } else {
961 cmp::Ordering::Less
962 }
963 }) {
964 Ok(i) | Err(i) => i,
965 };
966 for range in &ranges[start_ix..] {
967 if range.start.cmp(&transform_end, &self.buffer).is_ge() {
968 break;
969 }
970
971 highlight_endpoints.push(HighlightEndpoint {
972 offset: self
973 .to_inlay_offset(range.start.to_offset(&self.buffer)),
974 is_start: true,
975 tag: *tag,
976 style,
977 });
978 highlight_endpoints.push(HighlightEndpoint {
979 offset: self.to_inlay_offset(range.end.to_offset(&self.buffer)),
980 is_start: false,
981 tag: *tag,
982 style,
983 });
984 }
985 }
986 }
987
988 cursor.next(&());
989 }
990 highlight_endpoints.sort();
991 cursor.seek(&range.start, Bias::Right, &());
992 }
993 }
994
995 let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end);
996 let buffer_chunks = self.buffer.chunks(buffer_range, language_aware);
997
998 InlayChunks {
999 transforms: cursor,
1000 buffer_chunks,
1001 inlay_chunks: None,
1002 buffer_chunk: None,
1003 output_offset: range.start,
1004 max_output_offset: range.end,
1005 highlight_style: inlay_highlight_style,
1006 highlight_endpoints: highlight_endpoints.into_iter().peekable(),
1007 active_highlights: Default::default(),
1008 snapshot: self,
1009 }
1010 }
1011
1012 #[cfg(test)]
1013 pub fn text(&self) -> String {
1014 self.chunks(Default::default()..self.len(), false, None, None)
1015 .map(|chunk| chunk.text)
1016 .collect()
1017 }
1018
1019 fn check_invariants(&self) {
1020 #[cfg(any(debug_assertions, feature = "test-support"))]
1021 {
1022 assert_eq!(self.transforms.summary().input, self.buffer.text_summary());
1023 let mut transforms = self.transforms.iter().peekable();
1024 while let Some(transform) = transforms.next() {
1025 let transform_is_isomorphic = matches!(transform, Transform::Isomorphic(_));
1026 if let Some(next_transform) = transforms.peek() {
1027 let next_transform_is_isomorphic =
1028 matches!(next_transform, Transform::Isomorphic(_));
1029 assert!(
1030 !transform_is_isomorphic || !next_transform_is_isomorphic,
1031 "two adjacent isomorphic transforms"
1032 );
1033 }
1034 }
1035 }
1036 }
1037}
1038
1039fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
1040 if summary.len == 0 {
1041 return;
1042 }
1043
1044 let mut summary = Some(summary);
1045 sum_tree.update_last(
1046 |transform| {
1047 if let Transform::Isomorphic(transform) = transform {
1048 *transform += summary.take().unwrap();
1049 }
1050 },
1051 &(),
1052 );
1053
1054 if let Some(summary) = summary {
1055 sum_tree.push(Transform::Isomorphic(summary), &());
1056 }
1057}
1058
1059#[cfg(test)]
1060mod tests {
1061 use super::*;
1062 use crate::{InlayId, MultiBuffer};
1063 use gpui::AppContext;
1064 use rand::prelude::*;
1065 use settings::SettingsStore;
1066 use std::env;
1067 use text::Patch;
1068 use util::post_inc;
1069
1070 #[gpui::test]
1071 fn test_basic_inlays(cx: &mut AppContext) {
1072 let buffer = MultiBuffer::build_simple("abcdefghi", cx);
1073 let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
1074 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1075 assert_eq!(inlay_snapshot.text(), "abcdefghi");
1076 let mut next_inlay_id = 0;
1077
1078 let (inlay_snapshot, _) = inlay_map.splice(
1079 Vec::new(),
1080 vec![(
1081 InlayId(post_inc(&mut next_inlay_id)),
1082 InlayProperties {
1083 position: buffer.read(cx).snapshot(cx).anchor_after(3),
1084 text: "|123|",
1085 },
1086 )],
1087 );
1088 assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
1089 assert_eq!(
1090 inlay_snapshot.to_inlay_point(Point::new(0, 0)),
1091 InlayPoint::new(0, 0)
1092 );
1093 assert_eq!(
1094 inlay_snapshot.to_inlay_point(Point::new(0, 1)),
1095 InlayPoint::new(0, 1)
1096 );
1097 assert_eq!(
1098 inlay_snapshot.to_inlay_point(Point::new(0, 2)),
1099 InlayPoint::new(0, 2)
1100 );
1101 assert_eq!(
1102 inlay_snapshot.to_inlay_point(Point::new(0, 3)),
1103 InlayPoint::new(0, 3)
1104 );
1105 assert_eq!(
1106 inlay_snapshot.to_inlay_point(Point::new(0, 4)),
1107 InlayPoint::new(0, 9)
1108 );
1109 assert_eq!(
1110 inlay_snapshot.to_inlay_point(Point::new(0, 5)),
1111 InlayPoint::new(0, 10)
1112 );
1113 assert_eq!(
1114 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1115 InlayPoint::new(0, 0)
1116 );
1117 assert_eq!(
1118 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1119 InlayPoint::new(0, 0)
1120 );
1121 assert_eq!(
1122 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1123 InlayPoint::new(0, 3)
1124 );
1125 assert_eq!(
1126 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1127 InlayPoint::new(0, 3)
1128 );
1129 assert_eq!(
1130 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1131 InlayPoint::new(0, 3)
1132 );
1133 assert_eq!(
1134 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1135 InlayPoint::new(0, 9)
1136 );
1137
1138 // Edits before or after the inlay should not affect it.
1139 buffer.update(cx, |buffer, cx| {
1140 buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx)
1141 });
1142 let (inlay_snapshot, _) = inlay_map.sync(
1143 buffer.read(cx).snapshot(cx),
1144 buffer_edits.consume().into_inner(),
1145 );
1146 assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
1147
1148 // An edit surrounding the inlay should invalidate it.
1149 buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx));
1150 let (inlay_snapshot, _) = inlay_map.sync(
1151 buffer.read(cx).snapshot(cx),
1152 buffer_edits.consume().into_inner(),
1153 );
1154 assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
1155
1156 let (inlay_snapshot, _) = inlay_map.splice(
1157 Vec::new(),
1158 vec![
1159 (
1160 InlayId(post_inc(&mut next_inlay_id)),
1161 InlayProperties {
1162 position: buffer.read(cx).snapshot(cx).anchor_before(3),
1163 text: "|123|",
1164 },
1165 ),
1166 (
1167 InlayId(post_inc(&mut next_inlay_id)),
1168 InlayProperties {
1169 position: buffer.read(cx).snapshot(cx).anchor_after(3),
1170 text: "|456|",
1171 },
1172 ),
1173 ],
1174 );
1175 assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
1176
1177 // Edits ending where the inlay starts should not move it if it has a left bias.
1178 buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx));
1179 let (inlay_snapshot, _) = inlay_map.sync(
1180 buffer.read(cx).snapshot(cx),
1181 buffer_edits.consume().into_inner(),
1182 );
1183 assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
1184
1185 assert_eq!(
1186 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1187 InlayPoint::new(0, 0)
1188 );
1189 assert_eq!(
1190 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1191 InlayPoint::new(0, 0)
1192 );
1193
1194 assert_eq!(
1195 inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Left),
1196 InlayPoint::new(0, 1)
1197 );
1198 assert_eq!(
1199 inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Right),
1200 InlayPoint::new(0, 1)
1201 );
1202
1203 assert_eq!(
1204 inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Left),
1205 InlayPoint::new(0, 2)
1206 );
1207 assert_eq!(
1208 inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Right),
1209 InlayPoint::new(0, 2)
1210 );
1211
1212 assert_eq!(
1213 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1214 InlayPoint::new(0, 2)
1215 );
1216 assert_eq!(
1217 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1218 InlayPoint::new(0, 8)
1219 );
1220
1221 assert_eq!(
1222 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1223 InlayPoint::new(0, 2)
1224 );
1225 assert_eq!(
1226 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1227 InlayPoint::new(0, 8)
1228 );
1229
1230 assert_eq!(
1231 inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Left),
1232 InlayPoint::new(0, 2)
1233 );
1234 assert_eq!(
1235 inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Right),
1236 InlayPoint::new(0, 8)
1237 );
1238
1239 assert_eq!(
1240 inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Left),
1241 InlayPoint::new(0, 2)
1242 );
1243 assert_eq!(
1244 inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Right),
1245 InlayPoint::new(0, 8)
1246 );
1247
1248 assert_eq!(
1249 inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Left),
1250 InlayPoint::new(0, 2)
1251 );
1252 assert_eq!(
1253 inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Right),
1254 InlayPoint::new(0, 8)
1255 );
1256
1257 assert_eq!(
1258 inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Left),
1259 InlayPoint::new(0, 8)
1260 );
1261 assert_eq!(
1262 inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Right),
1263 InlayPoint::new(0, 8)
1264 );
1265
1266 assert_eq!(
1267 inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Left),
1268 InlayPoint::new(0, 9)
1269 );
1270 assert_eq!(
1271 inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Right),
1272 InlayPoint::new(0, 9)
1273 );
1274
1275 assert_eq!(
1276 inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Left),
1277 InlayPoint::new(0, 10)
1278 );
1279 assert_eq!(
1280 inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Right),
1281 InlayPoint::new(0, 10)
1282 );
1283
1284 assert_eq!(
1285 inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Left),
1286 InlayPoint::new(0, 11)
1287 );
1288 assert_eq!(
1289 inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Right),
1290 InlayPoint::new(0, 11)
1291 );
1292
1293 assert_eq!(
1294 inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Left),
1295 InlayPoint::new(0, 11)
1296 );
1297 assert_eq!(
1298 inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Right),
1299 InlayPoint::new(0, 17)
1300 );
1301
1302 assert_eq!(
1303 inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Left),
1304 InlayPoint::new(0, 11)
1305 );
1306 assert_eq!(
1307 inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Right),
1308 InlayPoint::new(0, 17)
1309 );
1310
1311 assert_eq!(
1312 inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Left),
1313 InlayPoint::new(0, 11)
1314 );
1315 assert_eq!(
1316 inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Right),
1317 InlayPoint::new(0, 17)
1318 );
1319
1320 assert_eq!(
1321 inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Left),
1322 InlayPoint::new(0, 11)
1323 );
1324 assert_eq!(
1325 inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Right),
1326 InlayPoint::new(0, 17)
1327 );
1328
1329 assert_eq!(
1330 inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Left),
1331 InlayPoint::new(0, 11)
1332 );
1333 assert_eq!(
1334 inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Right),
1335 InlayPoint::new(0, 17)
1336 );
1337
1338 assert_eq!(
1339 inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Left),
1340 InlayPoint::new(0, 17)
1341 );
1342 assert_eq!(
1343 inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Right),
1344 InlayPoint::new(0, 17)
1345 );
1346
1347 assert_eq!(
1348 inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Left),
1349 InlayPoint::new(0, 18)
1350 );
1351 assert_eq!(
1352 inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Right),
1353 InlayPoint::new(0, 18)
1354 );
1355
1356 // The inlays can be manually removed.
1357 let (inlay_snapshot, _) = inlay_map
1358 .splice::<String>(inlay_map.inlays_by_id.keys().copied().collect(), Vec::new());
1359 assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi");
1360 }
1361
1362 #[gpui::test]
1363 fn test_inlay_buffer_rows(cx: &mut AppContext) {
1364 let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx);
1365 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1366 assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi");
1367 let mut next_inlay_id = 0;
1368
1369 let (inlay_snapshot, _) = inlay_map.splice(
1370 Vec::new(),
1371 vec![
1372 (
1373 InlayId(post_inc(&mut next_inlay_id)),
1374 InlayProperties {
1375 position: buffer.read(cx).snapshot(cx).anchor_before(0),
1376 text: "|123|\n",
1377 },
1378 ),
1379 (
1380 InlayId(post_inc(&mut next_inlay_id)),
1381 InlayProperties {
1382 position: buffer.read(cx).snapshot(cx).anchor_before(4),
1383 text: "|456|",
1384 },
1385 ),
1386 (
1387 InlayId(post_inc(&mut next_inlay_id)),
1388 InlayProperties {
1389 position: buffer.read(cx).snapshot(cx).anchor_before(7),
1390 text: "\n|567|\n",
1391 },
1392 ),
1393 ],
1394 );
1395 assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
1396 assert_eq!(
1397 inlay_snapshot.buffer_rows(0).collect::<Vec<_>>(),
1398 vec![Some(0), None, Some(1), None, None, Some(2)]
1399 );
1400 }
1401
1402 #[gpui::test(iterations = 100)]
1403 fn test_random_inlays(cx: &mut AppContext, mut rng: StdRng) {
1404 init_test(cx);
1405
1406 let operations = env::var("OPERATIONS")
1407 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1408 .unwrap_or(10);
1409
1410 let len = rng.gen_range(0..30);
1411 let buffer = if rng.gen() {
1412 let text = util::RandomCharIter::new(&mut rng)
1413 .take(len)
1414 .collect::<String>();
1415 MultiBuffer::build_simple(&text, cx)
1416 } else {
1417 MultiBuffer::build_random(&mut rng, cx)
1418 };
1419 let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1420 let mut next_inlay_id = 0;
1421 log::info!("buffer text: {:?}", buffer_snapshot.text());
1422
1423 let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1424 for _ in 0..operations {
1425 let mut inlay_edits = Patch::default();
1426
1427 let mut prev_inlay_text = inlay_snapshot.text();
1428 let mut buffer_edits = Vec::new();
1429 match rng.gen_range(0..=100) {
1430 0..=50 => {
1431 let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1432 log::info!("mutated text: {:?}", snapshot.text());
1433 inlay_edits = Patch::new(edits);
1434 }
1435 _ => buffer.update(cx, |buffer, cx| {
1436 let subscription = buffer.subscribe();
1437 let edit_count = rng.gen_range(1..=5);
1438 buffer.randomly_mutate(&mut rng, edit_count, cx);
1439 buffer_snapshot = buffer.snapshot(cx);
1440 let edits = subscription.consume().into_inner();
1441 log::info!("editing {:?}", edits);
1442 buffer_edits.extend(edits);
1443 }),
1444 };
1445
1446 let (new_inlay_snapshot, new_inlay_edits) =
1447 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
1448 inlay_snapshot = new_inlay_snapshot;
1449 inlay_edits = inlay_edits.compose(new_inlay_edits);
1450
1451 log::info!("buffer text: {:?}", buffer_snapshot.text());
1452 log::info!("inlay text: {:?}", inlay_snapshot.text());
1453
1454 let inlays = inlay_map
1455 .inlays
1456 .iter()
1457 .filter(|inlay| inlay.position.is_valid(&buffer_snapshot))
1458 .map(|inlay| {
1459 let offset = inlay.position.to_offset(&buffer_snapshot);
1460 (offset, inlay.clone())
1461 })
1462 .collect::<Vec<_>>();
1463 let mut expected_text = Rope::from(buffer_snapshot.text().as_str());
1464 for (offset, inlay) in inlays.into_iter().rev() {
1465 expected_text.replace(offset..offset, &inlay.text.to_string());
1466 }
1467 assert_eq!(inlay_snapshot.text(), expected_text.to_string());
1468
1469 let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::<Vec<_>>();
1470 assert_eq!(
1471 expected_buffer_rows.len() as u32,
1472 expected_text.max_point().row + 1
1473 );
1474 for row_start in 0..expected_buffer_rows.len() {
1475 assert_eq!(
1476 inlay_snapshot
1477 .buffer_rows(row_start as u32)
1478 .collect::<Vec<_>>(),
1479 &expected_buffer_rows[row_start..],
1480 "incorrect buffer rows starting at {}",
1481 row_start
1482 );
1483 }
1484
1485 for _ in 0..5 {
1486 let mut end = rng.gen_range(0..=inlay_snapshot.len().0);
1487 end = expected_text.clip_offset(end, Bias::Right);
1488 let mut start = rng.gen_range(0..=end);
1489 start = expected_text.clip_offset(start, Bias::Right);
1490
1491 let actual_text = inlay_snapshot
1492 .chunks(InlayOffset(start)..InlayOffset(end), false, None, None)
1493 .map(|chunk| chunk.text)
1494 .collect::<String>();
1495 assert_eq!(
1496 actual_text,
1497 expected_text.slice(start..end).to_string(),
1498 "incorrect text in range {:?}",
1499 start..end
1500 );
1501
1502 assert_eq!(
1503 inlay_snapshot.text_summary_for_range(InlayOffset(start)..InlayOffset(end)),
1504 expected_text.slice(start..end).summary()
1505 );
1506 }
1507
1508 for edit in inlay_edits {
1509 prev_inlay_text.replace_range(
1510 edit.new.start.0..edit.new.start.0 + edit.old_len().0,
1511 &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0],
1512 );
1513 }
1514 assert_eq!(prev_inlay_text, inlay_snapshot.text());
1515
1516 assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
1517 assert_eq!(expected_text.len(), inlay_snapshot.len().0);
1518
1519 let mut buffer_point = Point::default();
1520 let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
1521 let mut buffer_chars = buffer_snapshot.chars_at(0);
1522 loop {
1523 // No matter which bias we clip an inlay point with, it doesn't move
1524 // because it was constructed from a buffer point.
1525 assert_eq!(
1526 inlay_snapshot.clip_point(inlay_point, Bias::Left),
1527 inlay_point,
1528 "invalid inlay point for buffer point {:?} when clipped left",
1529 buffer_point
1530 );
1531 assert_eq!(
1532 inlay_snapshot.clip_point(inlay_point, Bias::Right),
1533 inlay_point,
1534 "invalid inlay point for buffer point {:?} when clipped right",
1535 buffer_point
1536 );
1537
1538 if let Some(ch) = buffer_chars.next() {
1539 if ch == '\n' {
1540 buffer_point += Point::new(1, 0);
1541 } else {
1542 buffer_point += Point::new(0, ch.len_utf8() as u32);
1543 }
1544
1545 // Ensure that moving forward in the buffer always moves the inlay point forward as well.
1546 let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
1547 assert!(new_inlay_point > inlay_point);
1548 inlay_point = new_inlay_point;
1549 } else {
1550 break;
1551 }
1552 }
1553
1554 let mut inlay_point = InlayPoint::default();
1555 let mut inlay_offset = InlayOffset::default();
1556 for ch in expected_text.chars() {
1557 assert_eq!(
1558 inlay_snapshot.to_offset(inlay_point),
1559 inlay_offset,
1560 "invalid to_offset({:?})",
1561 inlay_point
1562 );
1563 assert_eq!(
1564 inlay_snapshot.to_point(inlay_offset),
1565 inlay_point,
1566 "invalid to_point({:?})",
1567 inlay_offset
1568 );
1569
1570 let mut bytes = [0; 4];
1571 for byte in ch.encode_utf8(&mut bytes).as_bytes() {
1572 inlay_offset.0 += 1;
1573 if *byte == b'\n' {
1574 inlay_point.0 += Point::new(1, 0);
1575 } else {
1576 inlay_point.0 += Point::new(0, 1);
1577 }
1578
1579 let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left);
1580 let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right);
1581 assert!(
1582 clipped_left_point <= clipped_right_point,
1583 "inlay point {:?} when clipped left is greater than when clipped right ({:?} > {:?})",
1584 inlay_point,
1585 clipped_left_point,
1586 clipped_right_point
1587 );
1588
1589 // Ensure the clipped points are at valid text locations.
1590 assert_eq!(
1591 clipped_left_point.0,
1592 expected_text.clip_point(clipped_left_point.0, Bias::Left)
1593 );
1594 assert_eq!(
1595 clipped_right_point.0,
1596 expected_text.clip_point(clipped_right_point.0, Bias::Right)
1597 );
1598
1599 // Ensure the clipped points never overshoot the end of the map.
1600 assert!(clipped_left_point <= inlay_snapshot.max_point());
1601 assert!(clipped_right_point <= inlay_snapshot.max_point());
1602
1603 // Ensure the clipped points are at valid buffer locations.
1604 assert_eq!(
1605 inlay_snapshot
1606 .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_left_point)),
1607 clipped_left_point,
1608 "to_buffer_point({:?}) = {:?}",
1609 clipped_left_point,
1610 inlay_snapshot.to_buffer_point(clipped_left_point),
1611 );
1612 assert_eq!(
1613 inlay_snapshot
1614 .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_right_point)),
1615 clipped_right_point,
1616 "to_buffer_point({:?}) = {:?}",
1617 clipped_right_point,
1618 inlay_snapshot.to_buffer_point(clipped_right_point),
1619 );
1620 }
1621 }
1622 }
1623 }
1624
1625 fn init_test(cx: &mut AppContext) {
1626 cx.set_global(SettingsStore::test(cx));
1627 theme::init((), cx);
1628 }
1629}