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