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