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