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