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