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(crate) 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 let buffer_offset = inlay.position.to_offset(&buffer_snapshot);
486 if buffer_offset > buffer_edit.new.end {
487 break;
488 }
489
490 let prefix_start = new_transforms.summary().input.len;
491 let prefix_end = buffer_offset;
492 push_isomorphic(
493 &mut new_transforms,
494 buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
495 );
496
497 if inlay.position.is_valid(&buffer_snapshot) {
498 new_transforms.push(Transform::Inlay(inlay.clone()), &());
499 }
500 }
501
502 // Apply the rest of the edit.
503 let transform_start = new_transforms.summary().input.len;
504 push_isomorphic(
505 &mut new_transforms,
506 buffer_snapshot.text_summary_for_range(transform_start..buffer_edit.new.end),
507 );
508 let new_end = InlayOffset(new_transforms.summary().output.len);
509 inlay_edits.push(Edit {
510 old: old_start..old_end,
511 new: new_start..new_end,
512 });
513
514 // If the next edit doesn't intersect the current isomorphic transform, then
515 // we can push its remainder.
516 if buffer_edits_iter
517 .peek()
518 .map_or(true, |edit| edit.old.start >= cursor.end(&()).0)
519 {
520 let transform_start = new_transforms.summary().input.len;
521 let transform_end =
522 buffer_edit.new.end + (cursor.end(&()).0 - buffer_edit.old.end);
523 push_isomorphic(
524 &mut new_transforms,
525 buffer_snapshot.text_summary_for_range(transform_start..transform_end),
526 );
527 cursor.next(&());
528 }
529 }
530
531 new_transforms.append(cursor.suffix(&()), &());
532 if new_transforms.is_empty() {
533 new_transforms.push(Transform::Isomorphic(Default::default()), &());
534 }
535
536 drop(cursor);
537 snapshot.transforms = new_transforms;
538 snapshot.version += 1;
539 snapshot.buffer = buffer_snapshot;
540 snapshot.check_invariants();
541
542 (snapshot.clone(), inlay_edits.into_inner())
543 }
544 }
545
546 pub fn splice(
547 &mut self,
548 to_remove: &[InlayId],
549 to_insert: Vec<Inlay>,
550 ) -> (InlaySnapshot, Vec<InlayEdit>) {
551 let snapshot = &mut self.snapshot;
552 let mut edits = BTreeSet::new();
553
554 self.inlays.retain(|inlay| {
555 let retain = !to_remove.contains(&inlay.id);
556 if !retain {
557 let offset = inlay.position.to_offset(&snapshot.buffer);
558 edits.insert(offset);
559 }
560 retain
561 });
562
563 for inlay_to_insert in to_insert {
564 // Avoid inserting empty inlays.
565 if inlay_to_insert.text.is_empty() {
566 continue;
567 }
568
569 let offset = inlay_to_insert.position.to_offset(&snapshot.buffer);
570 match self.inlays.binary_search_by(|probe| {
571 probe
572 .position
573 .cmp(&inlay_to_insert.position, &snapshot.buffer)
574 .then(std::cmp::Ordering::Less)
575 }) {
576 Ok(ix) | Err(ix) => {
577 self.inlays.insert(ix, inlay_to_insert);
578 }
579 }
580
581 edits.insert(offset);
582 }
583
584 let buffer_edits = edits
585 .into_iter()
586 .map(|offset| Edit {
587 old: offset..offset,
588 new: offset..offset,
589 })
590 .collect();
591 let buffer_snapshot = snapshot.buffer.clone();
592 let (snapshot, edits) = self.sync(buffer_snapshot, buffer_edits);
593 (snapshot, edits)
594 }
595
596 pub fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
597 self.inlays.iter()
598 }
599
600 #[cfg(test)]
601 pub(crate) fn randomly_mutate(
602 &mut self,
603 next_inlay_id: &mut usize,
604 rng: &mut rand::rngs::StdRng,
605 ) -> (InlaySnapshot, Vec<InlayEdit>) {
606 use rand::prelude::*;
607 use util::post_inc;
608
609 let mut to_remove = Vec::new();
610 let mut to_insert = Vec::new();
611 let snapshot = &mut self.snapshot;
612 for i in 0..rng.gen_range(1..=5) {
613 if self.inlays.is_empty() || rng.r#gen() {
614 let position = snapshot.buffer.random_byte_range(0, rng).start;
615 let bias = if rng.r#gen() { Bias::Left } else { Bias::Right };
616 let len = if rng.gen_bool(0.01) {
617 0
618 } else {
619 rng.gen_range(1..=5)
620 };
621 let text = util::RandomCharIter::new(&mut *rng)
622 .filter(|ch| *ch != '\r')
623 .take(len)
624 .collect::<String>();
625
626 let inlay_id = if i % 2 == 0 {
627 InlayId::Hint(post_inc(next_inlay_id))
628 } else {
629 InlayId::InlineCompletion(post_inc(next_inlay_id))
630 };
631 log::info!(
632 "creating inlay {:?} at buffer offset {} with bias {:?} and text {:?}",
633 inlay_id,
634 position,
635 bias,
636 text
637 );
638
639 to_insert.push(Inlay {
640 id: inlay_id,
641 position: snapshot.buffer.anchor_at(position, bias),
642 text: text.into(),
643 });
644 } else {
645 to_remove.push(
646 self.inlays
647 .iter()
648 .choose(rng)
649 .map(|inlay| inlay.id)
650 .unwrap(),
651 );
652 }
653 }
654 log::info!("removing inlays: {:?}", to_remove);
655
656 let (snapshot, edits) = self.splice(&to_remove, to_insert);
657 (snapshot, edits)
658 }
659}
660
661impl InlaySnapshot {
662 pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
663 let mut cursor = self
664 .transforms
665 .cursor::<(InlayOffset, (InlayPoint, usize))>(&());
666 cursor.seek(&offset, Bias::Right, &());
667 let overshoot = offset.0 - cursor.start().0.0;
668 match cursor.item() {
669 Some(Transform::Isomorphic(_)) => {
670 let buffer_offset_start = cursor.start().1.1;
671 let buffer_offset_end = buffer_offset_start + overshoot;
672 let buffer_start = self.buffer.offset_to_point(buffer_offset_start);
673 let buffer_end = self.buffer.offset_to_point(buffer_offset_end);
674 InlayPoint(cursor.start().1.0.0 + (buffer_end - buffer_start))
675 }
676 Some(Transform::Inlay(inlay)) => {
677 let overshoot = inlay.text.offset_to_point(overshoot);
678 InlayPoint(cursor.start().1.0.0 + overshoot)
679 }
680 None => self.max_point(),
681 }
682 }
683
684 pub fn len(&self) -> InlayOffset {
685 InlayOffset(self.transforms.summary().output.len)
686 }
687
688 pub fn max_point(&self) -> InlayPoint {
689 InlayPoint(self.transforms.summary().output.lines)
690 }
691
692 pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
693 let mut cursor = self
694 .transforms
695 .cursor::<(InlayPoint, (InlayOffset, Point))>(&());
696 cursor.seek(&point, Bias::Right, &());
697 let overshoot = point.0 - cursor.start().0.0;
698 match cursor.item() {
699 Some(Transform::Isomorphic(_)) => {
700 let buffer_point_start = cursor.start().1.1;
701 let buffer_point_end = buffer_point_start + overshoot;
702 let buffer_offset_start = self.buffer.point_to_offset(buffer_point_start);
703 let buffer_offset_end = self.buffer.point_to_offset(buffer_point_end);
704 InlayOffset(cursor.start().1.0.0 + (buffer_offset_end - buffer_offset_start))
705 }
706 Some(Transform::Inlay(inlay)) => {
707 let overshoot = inlay.text.point_to_offset(overshoot);
708 InlayOffset(cursor.start().1.0.0 + overshoot)
709 }
710 None => self.len(),
711 }
712 }
713 pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
714 let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
715 cursor.seek(&point, Bias::Right, &());
716 match cursor.item() {
717 Some(Transform::Isomorphic(_)) => {
718 let overshoot = point.0 - cursor.start().0.0;
719 cursor.start().1 + overshoot
720 }
721 Some(Transform::Inlay(_)) => cursor.start().1,
722 None => self.buffer.max_point(),
723 }
724 }
725 pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize {
726 let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
727 cursor.seek(&offset, Bias::Right, &());
728 match cursor.item() {
729 Some(Transform::Isomorphic(_)) => {
730 let overshoot = offset - cursor.start().0;
731 cursor.start().1 + overshoot.0
732 }
733 Some(Transform::Inlay(_)) => cursor.start().1,
734 None => self.buffer.len(),
735 }
736 }
737
738 pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset {
739 let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>(&());
740 cursor.seek(&offset, Bias::Left, &());
741 loop {
742 match cursor.item() {
743 Some(Transform::Isomorphic(_)) => {
744 if offset == cursor.end(&()).0 {
745 while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
746 if inlay.position.bias() == Bias::Right {
747 break;
748 } else {
749 cursor.next(&());
750 }
751 }
752 return cursor.end(&()).1;
753 } else {
754 let overshoot = offset - cursor.start().0;
755 return InlayOffset(cursor.start().1.0 + overshoot);
756 }
757 }
758 Some(Transform::Inlay(inlay)) => {
759 if inlay.position.bias() == Bias::Left {
760 cursor.next(&());
761 } else {
762 return cursor.start().1;
763 }
764 }
765 None => {
766 return self.len();
767 }
768 }
769 }
770 }
771 pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
772 let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>(&());
773 cursor.seek(&point, Bias::Left, &());
774 loop {
775 match cursor.item() {
776 Some(Transform::Isomorphic(_)) => {
777 if point == cursor.end(&()).0 {
778 while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
779 if inlay.position.bias() == Bias::Right {
780 break;
781 } else {
782 cursor.next(&());
783 }
784 }
785 return cursor.end(&()).1;
786 } else {
787 let overshoot = point - cursor.start().0;
788 return InlayPoint(cursor.start().1.0 + overshoot);
789 }
790 }
791 Some(Transform::Inlay(inlay)) => {
792 if inlay.position.bias() == Bias::Left {
793 cursor.next(&());
794 } else {
795 return cursor.start().1;
796 }
797 }
798 None => {
799 return self.max_point();
800 }
801 }
802 }
803 }
804
805 pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint {
806 let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
807 cursor.seek(&point, Bias::Left, &());
808 loop {
809 match cursor.item() {
810 Some(Transform::Isomorphic(transform)) => {
811 if cursor.start().0 == point {
812 if let Some(Transform::Inlay(inlay)) = cursor.prev_item() {
813 if inlay.position.bias() == Bias::Left {
814 return point;
815 } else if bias == Bias::Left {
816 cursor.prev(&());
817 } else if transform.first_line_chars == 0 {
818 point.0 += Point::new(1, 0);
819 } else {
820 point.0 += Point::new(0, 1);
821 }
822 } else {
823 return point;
824 }
825 } else if cursor.end(&()).0 == point {
826 if let Some(Transform::Inlay(inlay)) = cursor.next_item() {
827 if inlay.position.bias() == Bias::Right {
828 return point;
829 } else if bias == Bias::Right {
830 cursor.next(&());
831 } else if point.0.column == 0 {
832 point.0.row -= 1;
833 point.0.column = self.line_len(point.0.row);
834 } else {
835 point.0.column -= 1;
836 }
837 } else {
838 return point;
839 }
840 } else {
841 let overshoot = point.0 - cursor.start().0.0;
842 let buffer_point = cursor.start().1 + overshoot;
843 let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias);
844 let clipped_overshoot = clipped_buffer_point - cursor.start().1;
845 let clipped_point = InlayPoint(cursor.start().0.0 + clipped_overshoot);
846 if clipped_point == point {
847 return clipped_point;
848 } else {
849 point = clipped_point;
850 }
851 }
852 }
853 Some(Transform::Inlay(inlay)) => {
854 if point == cursor.start().0 && inlay.position.bias() == Bias::Right {
855 match cursor.prev_item() {
856 Some(Transform::Inlay(inlay)) => {
857 if inlay.position.bias() == Bias::Left {
858 return point;
859 }
860 }
861 _ => return point,
862 }
863 } else if point == cursor.end(&()).0 && inlay.position.bias() == Bias::Left {
864 match cursor.next_item() {
865 Some(Transform::Inlay(inlay)) => {
866 if inlay.position.bias() == Bias::Right {
867 return point;
868 }
869 }
870 _ => return point,
871 }
872 }
873
874 if bias == Bias::Left {
875 point = cursor.start().0;
876 cursor.prev(&());
877 } else {
878 cursor.next(&());
879 point = cursor.start().0;
880 }
881 }
882 None => {
883 bias = bias.invert();
884 if bias == Bias::Left {
885 point = cursor.start().0;
886 cursor.prev(&());
887 } else {
888 cursor.next(&());
889 point = cursor.start().0;
890 }
891 }
892 }
893 }
894 }
895
896 pub fn text_summary(&self) -> TextSummary {
897 self.transforms.summary().output
898 }
899
900 pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> TextSummary {
901 let mut summary = TextSummary::default();
902
903 let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
904 cursor.seek(&range.start, Bias::Right, &());
905
906 let overshoot = range.start.0 - cursor.start().0.0;
907 match cursor.item() {
908 Some(Transform::Isomorphic(_)) => {
909 let buffer_start = cursor.start().1;
910 let suffix_start = buffer_start + overshoot;
911 let suffix_end =
912 buffer_start + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0.0);
913 summary = self.buffer.text_summary_for_range(suffix_start..suffix_end);
914 cursor.next(&());
915 }
916 Some(Transform::Inlay(inlay)) => {
917 let suffix_start = overshoot;
918 let suffix_end = cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0.0;
919 summary = inlay.text.cursor(suffix_start).summary(suffix_end);
920 cursor.next(&());
921 }
922 None => {}
923 }
924
925 if range.end > cursor.start().0 {
926 summary += cursor
927 .summary::<_, TransformSummary>(&range.end, Bias::Right, &())
928 .output;
929
930 let overshoot = range.end.0 - cursor.start().0.0;
931 match cursor.item() {
932 Some(Transform::Isomorphic(_)) => {
933 let prefix_start = cursor.start().1;
934 let prefix_end = prefix_start + overshoot;
935 summary += self
936 .buffer
937 .text_summary_for_range::<TextSummary, _>(prefix_start..prefix_end);
938 }
939 Some(Transform::Inlay(inlay)) => {
940 let prefix_end = overshoot;
941 summary += inlay.text.cursor(0).summary::<TextSummary>(prefix_end);
942 }
943 None => {}
944 }
945 }
946
947 summary
948 }
949
950 pub fn row_infos(&self, row: u32) -> InlayBufferRows<'_> {
951 let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
952 let inlay_point = InlayPoint::new(row, 0);
953 cursor.seek(&inlay_point, Bias::Left, &());
954
955 let max_buffer_row = self.buffer.max_row();
956 let mut buffer_point = cursor.start().1;
957 let buffer_row = if row == 0 {
958 MultiBufferRow(0)
959 } else {
960 match cursor.item() {
961 Some(Transform::Isomorphic(_)) => {
962 buffer_point += inlay_point.0 - cursor.start().0.0;
963 MultiBufferRow(buffer_point.row)
964 }
965 _ => cmp::min(MultiBufferRow(buffer_point.row + 1), max_buffer_row),
966 }
967 };
968
969 InlayBufferRows {
970 transforms: cursor,
971 inlay_row: inlay_point.row(),
972 buffer_rows: self.buffer.row_infos(buffer_row),
973 max_buffer_row,
974 }
975 }
976
977 pub fn line_len(&self, row: u32) -> u32 {
978 let line_start = self.to_offset(InlayPoint::new(row, 0)).0;
979 let line_end = if row >= self.max_point().row() {
980 self.len().0
981 } else {
982 self.to_offset(InlayPoint::new(row + 1, 0)).0 - 1
983 };
984 (line_end - line_start) as u32
985 }
986
987 pub(crate) fn chunks<'a>(
988 &'a self,
989 range: Range<InlayOffset>,
990 language_aware: bool,
991 highlights: Highlights<'a>,
992 ) -> InlayChunks<'a> {
993 let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
994 cursor.seek(&range.start, Bias::Right, &());
995
996 let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end);
997 let buffer_chunks = CustomHighlightsChunks::new(
998 buffer_range,
999 language_aware,
1000 highlights.text_highlights,
1001 &self.buffer,
1002 );
1003
1004 InlayChunks {
1005 transforms: cursor,
1006 buffer_chunks,
1007 inlay_chunks: None,
1008 inlay_chunk: None,
1009 buffer_chunk: None,
1010 output_offset: range.start,
1011 max_output_offset: range.end,
1012 highlight_styles: highlights.styles,
1013 highlights,
1014 snapshot: self,
1015 }
1016 }
1017
1018 #[cfg(test)]
1019 pub fn text(&self) -> String {
1020 self.chunks(Default::default()..self.len(), false, Highlights::default())
1021 .map(|chunk| chunk.text)
1022 .collect()
1023 }
1024
1025 fn check_invariants(&self) {
1026 #[cfg(any(debug_assertions, feature = "test-support"))]
1027 {
1028 assert_eq!(self.transforms.summary().input, self.buffer.text_summary());
1029 let mut transforms = self.transforms.iter().peekable();
1030 while let Some(transform) = transforms.next() {
1031 let transform_is_isomorphic = matches!(transform, Transform::Isomorphic(_));
1032 if let Some(next_transform) = transforms.peek() {
1033 let next_transform_is_isomorphic =
1034 matches!(next_transform, Transform::Isomorphic(_));
1035 assert!(
1036 !transform_is_isomorphic || !next_transform_is_isomorphic,
1037 "two adjacent isomorphic transforms"
1038 );
1039 }
1040 }
1041 }
1042 }
1043}
1044
1045fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
1046 if summary.len == 0 {
1047 return;
1048 }
1049
1050 let mut summary = Some(summary);
1051 sum_tree.update_last(
1052 |transform| {
1053 if let Transform::Isomorphic(transform) = transform {
1054 *transform += summary.take().unwrap();
1055 }
1056 },
1057 &(),
1058 );
1059
1060 if let Some(summary) = summary {
1061 sum_tree.push(Transform::Isomorphic(summary), &());
1062 }
1063}
1064
1065#[cfg(test)]
1066mod tests {
1067 use super::*;
1068 use crate::{
1069 InlayId, MultiBuffer,
1070 display_map::{InlayHighlights, TextHighlights},
1071 hover_links::InlayHighlight,
1072 };
1073 use gpui::{App, HighlightStyle};
1074 use project::{InlayHint, InlayHintLabel, ResolveState};
1075 use rand::prelude::*;
1076 use settings::SettingsStore;
1077 use std::{any::TypeId, cmp::Reverse, env, sync::Arc};
1078 use sum_tree::TreeMap;
1079 use text::Patch;
1080 use util::post_inc;
1081
1082 #[test]
1083 fn test_inlay_properties_label_padding() {
1084 assert_eq!(
1085 Inlay::hint(
1086 0,
1087 Anchor::min(),
1088 &InlayHint {
1089 label: InlayHintLabel::String("a".to_string()),
1090 position: text::Anchor::default(),
1091 padding_left: false,
1092 padding_right: false,
1093 tooltip: None,
1094 kind: None,
1095 resolve_state: ResolveState::Resolved,
1096 },
1097 )
1098 .text
1099 .to_string(),
1100 "a",
1101 "Should not pad label if not requested"
1102 );
1103
1104 assert_eq!(
1105 Inlay::hint(
1106 0,
1107 Anchor::min(),
1108 &InlayHint {
1109 label: InlayHintLabel::String("a".to_string()),
1110 position: text::Anchor::default(),
1111 padding_left: true,
1112 padding_right: true,
1113 tooltip: None,
1114 kind: None,
1115 resolve_state: ResolveState::Resolved,
1116 },
1117 )
1118 .text
1119 .to_string(),
1120 " a ",
1121 "Should pad label for every side requested"
1122 );
1123
1124 assert_eq!(
1125 Inlay::hint(
1126 0,
1127 Anchor::min(),
1128 &InlayHint {
1129 label: InlayHintLabel::String(" a ".to_string()),
1130 position: text::Anchor::default(),
1131 padding_left: false,
1132 padding_right: false,
1133 tooltip: None,
1134 kind: None,
1135 resolve_state: ResolveState::Resolved,
1136 },
1137 )
1138 .text
1139 .to_string(),
1140 " a ",
1141 "Should not change already padded label"
1142 );
1143
1144 assert_eq!(
1145 Inlay::hint(
1146 0,
1147 Anchor::min(),
1148 &InlayHint {
1149 label: InlayHintLabel::String(" a ".to_string()),
1150 position: text::Anchor::default(),
1151 padding_left: true,
1152 padding_right: true,
1153 tooltip: None,
1154 kind: None,
1155 resolve_state: ResolveState::Resolved,
1156 },
1157 )
1158 .text
1159 .to_string(),
1160 " a ",
1161 "Should not change already padded label"
1162 );
1163 }
1164
1165 #[gpui::test]
1166 fn test_basic_inlays(cx: &mut App) {
1167 let buffer = MultiBuffer::build_simple("abcdefghi", cx);
1168 let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
1169 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1170 assert_eq!(inlay_snapshot.text(), "abcdefghi");
1171 let mut next_inlay_id = 0;
1172
1173 let (inlay_snapshot, _) = inlay_map.splice(
1174 &[],
1175 vec![Inlay {
1176 id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1177 position: buffer.read(cx).snapshot(cx).anchor_after(3),
1178 text: "|123|".into(),
1179 }],
1180 );
1181 assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
1182 assert_eq!(
1183 inlay_snapshot.to_inlay_point(Point::new(0, 0)),
1184 InlayPoint::new(0, 0)
1185 );
1186 assert_eq!(
1187 inlay_snapshot.to_inlay_point(Point::new(0, 1)),
1188 InlayPoint::new(0, 1)
1189 );
1190 assert_eq!(
1191 inlay_snapshot.to_inlay_point(Point::new(0, 2)),
1192 InlayPoint::new(0, 2)
1193 );
1194 assert_eq!(
1195 inlay_snapshot.to_inlay_point(Point::new(0, 3)),
1196 InlayPoint::new(0, 3)
1197 );
1198 assert_eq!(
1199 inlay_snapshot.to_inlay_point(Point::new(0, 4)),
1200 InlayPoint::new(0, 9)
1201 );
1202 assert_eq!(
1203 inlay_snapshot.to_inlay_point(Point::new(0, 5)),
1204 InlayPoint::new(0, 10)
1205 );
1206 assert_eq!(
1207 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1208 InlayPoint::new(0, 0)
1209 );
1210 assert_eq!(
1211 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1212 InlayPoint::new(0, 0)
1213 );
1214 assert_eq!(
1215 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1216 InlayPoint::new(0, 3)
1217 );
1218 assert_eq!(
1219 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1220 InlayPoint::new(0, 3)
1221 );
1222 assert_eq!(
1223 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1224 InlayPoint::new(0, 3)
1225 );
1226 assert_eq!(
1227 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1228 InlayPoint::new(0, 9)
1229 );
1230
1231 // Edits before or after the inlay should not affect it.
1232 buffer.update(cx, |buffer, cx| {
1233 buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx)
1234 });
1235 let (inlay_snapshot, _) = inlay_map.sync(
1236 buffer.read(cx).snapshot(cx),
1237 buffer_edits.consume().into_inner(),
1238 );
1239 assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
1240
1241 // An edit surrounding the inlay should invalidate it.
1242 buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx));
1243 let (inlay_snapshot, _) = inlay_map.sync(
1244 buffer.read(cx).snapshot(cx),
1245 buffer_edits.consume().into_inner(),
1246 );
1247 assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
1248
1249 let (inlay_snapshot, _) = inlay_map.splice(
1250 &[],
1251 vec![
1252 Inlay {
1253 id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1254 position: buffer.read(cx).snapshot(cx).anchor_before(3),
1255 text: "|123|".into(),
1256 },
1257 Inlay {
1258 id: InlayId::InlineCompletion(post_inc(&mut next_inlay_id)),
1259 position: buffer.read(cx).snapshot(cx).anchor_after(3),
1260 text: "|456|".into(),
1261 },
1262 ],
1263 );
1264 assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
1265
1266 // Edits ending where the inlay starts should not move it if it has a left bias.
1267 buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx));
1268 let (inlay_snapshot, _) = inlay_map.sync(
1269 buffer.read(cx).snapshot(cx),
1270 buffer_edits.consume().into_inner(),
1271 );
1272 assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
1273
1274 assert_eq!(
1275 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1276 InlayPoint::new(0, 0)
1277 );
1278 assert_eq!(
1279 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1280 InlayPoint::new(0, 0)
1281 );
1282
1283 assert_eq!(
1284 inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Left),
1285 InlayPoint::new(0, 1)
1286 );
1287 assert_eq!(
1288 inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Right),
1289 InlayPoint::new(0, 1)
1290 );
1291
1292 assert_eq!(
1293 inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Left),
1294 InlayPoint::new(0, 2)
1295 );
1296 assert_eq!(
1297 inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Right),
1298 InlayPoint::new(0, 2)
1299 );
1300
1301 assert_eq!(
1302 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1303 InlayPoint::new(0, 2)
1304 );
1305 assert_eq!(
1306 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1307 InlayPoint::new(0, 8)
1308 );
1309
1310 assert_eq!(
1311 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1312 InlayPoint::new(0, 2)
1313 );
1314 assert_eq!(
1315 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1316 InlayPoint::new(0, 8)
1317 );
1318
1319 assert_eq!(
1320 inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Left),
1321 InlayPoint::new(0, 2)
1322 );
1323 assert_eq!(
1324 inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Right),
1325 InlayPoint::new(0, 8)
1326 );
1327
1328 assert_eq!(
1329 inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Left),
1330 InlayPoint::new(0, 2)
1331 );
1332 assert_eq!(
1333 inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Right),
1334 InlayPoint::new(0, 8)
1335 );
1336
1337 assert_eq!(
1338 inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Left),
1339 InlayPoint::new(0, 2)
1340 );
1341 assert_eq!(
1342 inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Right),
1343 InlayPoint::new(0, 8)
1344 );
1345
1346 assert_eq!(
1347 inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Left),
1348 InlayPoint::new(0, 8)
1349 );
1350 assert_eq!(
1351 inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Right),
1352 InlayPoint::new(0, 8)
1353 );
1354
1355 assert_eq!(
1356 inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Left),
1357 InlayPoint::new(0, 9)
1358 );
1359 assert_eq!(
1360 inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Right),
1361 InlayPoint::new(0, 9)
1362 );
1363
1364 assert_eq!(
1365 inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Left),
1366 InlayPoint::new(0, 10)
1367 );
1368 assert_eq!(
1369 inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Right),
1370 InlayPoint::new(0, 10)
1371 );
1372
1373 assert_eq!(
1374 inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Left),
1375 InlayPoint::new(0, 11)
1376 );
1377 assert_eq!(
1378 inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Right),
1379 InlayPoint::new(0, 11)
1380 );
1381
1382 assert_eq!(
1383 inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Left),
1384 InlayPoint::new(0, 11)
1385 );
1386 assert_eq!(
1387 inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Right),
1388 InlayPoint::new(0, 17)
1389 );
1390
1391 assert_eq!(
1392 inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Left),
1393 InlayPoint::new(0, 11)
1394 );
1395 assert_eq!(
1396 inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Right),
1397 InlayPoint::new(0, 17)
1398 );
1399
1400 assert_eq!(
1401 inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Left),
1402 InlayPoint::new(0, 11)
1403 );
1404 assert_eq!(
1405 inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Right),
1406 InlayPoint::new(0, 17)
1407 );
1408
1409 assert_eq!(
1410 inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Left),
1411 InlayPoint::new(0, 11)
1412 );
1413 assert_eq!(
1414 inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Right),
1415 InlayPoint::new(0, 17)
1416 );
1417
1418 assert_eq!(
1419 inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Left),
1420 InlayPoint::new(0, 11)
1421 );
1422 assert_eq!(
1423 inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Right),
1424 InlayPoint::new(0, 17)
1425 );
1426
1427 assert_eq!(
1428 inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Left),
1429 InlayPoint::new(0, 17)
1430 );
1431 assert_eq!(
1432 inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Right),
1433 InlayPoint::new(0, 17)
1434 );
1435
1436 assert_eq!(
1437 inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Left),
1438 InlayPoint::new(0, 18)
1439 );
1440 assert_eq!(
1441 inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Right),
1442 InlayPoint::new(0, 18)
1443 );
1444
1445 // The inlays can be manually removed.
1446 let (inlay_snapshot, _) = inlay_map.splice(
1447 &inlay_map
1448 .inlays
1449 .iter()
1450 .map(|inlay| inlay.id)
1451 .collect::<Vec<InlayId>>(),
1452 Vec::new(),
1453 );
1454 assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi");
1455 }
1456
1457 #[gpui::test]
1458 fn test_inlay_buffer_rows(cx: &mut App) {
1459 let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx);
1460 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1461 assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi");
1462 let mut next_inlay_id = 0;
1463
1464 let (inlay_snapshot, _) = inlay_map.splice(
1465 &[],
1466 vec![
1467 Inlay {
1468 id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1469 position: buffer.read(cx).snapshot(cx).anchor_before(0),
1470 text: "|123|\n".into(),
1471 },
1472 Inlay {
1473 id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1474 position: buffer.read(cx).snapshot(cx).anchor_before(4),
1475 text: "|456|".into(),
1476 },
1477 Inlay {
1478 id: InlayId::InlineCompletion(post_inc(&mut next_inlay_id)),
1479 position: buffer.read(cx).snapshot(cx).anchor_before(7),
1480 text: "\n|567|\n".into(),
1481 },
1482 ],
1483 );
1484 assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
1485 assert_eq!(
1486 inlay_snapshot
1487 .row_infos(0)
1488 .map(|info| info.buffer_row)
1489 .collect::<Vec<_>>(),
1490 vec![Some(0), None, Some(1), None, None, Some(2)]
1491 );
1492 }
1493
1494 #[gpui::test(iterations = 100)]
1495 fn test_random_inlays(cx: &mut App, mut rng: StdRng) {
1496 init_test(cx);
1497
1498 let operations = env::var("OPERATIONS")
1499 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1500 .unwrap_or(10);
1501
1502 let len = rng.gen_range(0..30);
1503 let buffer = if rng.r#gen() {
1504 let text = util::RandomCharIter::new(&mut rng)
1505 .take(len)
1506 .collect::<String>();
1507 MultiBuffer::build_simple(&text, cx)
1508 } else {
1509 MultiBuffer::build_random(&mut rng, cx)
1510 };
1511 let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1512 let mut next_inlay_id = 0;
1513 log::info!("buffer text: {:?}", buffer_snapshot.text());
1514 let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1515 for _ in 0..operations {
1516 let mut inlay_edits = Patch::default();
1517
1518 let mut prev_inlay_text = inlay_snapshot.text();
1519 let mut buffer_edits = Vec::new();
1520 match rng.gen_range(0..=100) {
1521 0..=50 => {
1522 let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1523 log::info!("mutated text: {:?}", snapshot.text());
1524 inlay_edits = Patch::new(edits);
1525 }
1526 _ => buffer.update(cx, |buffer, cx| {
1527 let subscription = buffer.subscribe();
1528 let edit_count = rng.gen_range(1..=5);
1529 buffer.randomly_mutate(&mut rng, edit_count, cx);
1530 buffer_snapshot = buffer.snapshot(cx);
1531 let edits = subscription.consume().into_inner();
1532 log::info!("editing {:?}", edits);
1533 buffer_edits.extend(edits);
1534 }),
1535 };
1536
1537 let (new_inlay_snapshot, new_inlay_edits) =
1538 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
1539 inlay_snapshot = new_inlay_snapshot;
1540 inlay_edits = inlay_edits.compose(new_inlay_edits);
1541
1542 log::info!("buffer text: {:?}", buffer_snapshot.text());
1543 log::info!("inlay text: {:?}", inlay_snapshot.text());
1544
1545 let inlays = inlay_map
1546 .inlays
1547 .iter()
1548 .filter(|inlay| inlay.position.is_valid(&buffer_snapshot))
1549 .map(|inlay| {
1550 let offset = inlay.position.to_offset(&buffer_snapshot);
1551 (offset, inlay.clone())
1552 })
1553 .collect::<Vec<_>>();
1554 let mut expected_text = Rope::from(buffer_snapshot.text());
1555 for (offset, inlay) in inlays.iter().rev() {
1556 expected_text.replace(*offset..*offset, &inlay.text.to_string());
1557 }
1558 assert_eq!(inlay_snapshot.text(), expected_text.to_string());
1559
1560 let expected_buffer_rows = inlay_snapshot.row_infos(0).collect::<Vec<_>>();
1561 assert_eq!(
1562 expected_buffer_rows.len() as u32,
1563 expected_text.max_point().row + 1
1564 );
1565 for row_start in 0..expected_buffer_rows.len() {
1566 assert_eq!(
1567 inlay_snapshot
1568 .row_infos(row_start as u32)
1569 .collect::<Vec<_>>(),
1570 &expected_buffer_rows[row_start..],
1571 "incorrect buffer rows starting at {}",
1572 row_start
1573 );
1574 }
1575
1576 let mut text_highlights = TextHighlights::default();
1577 let text_highlight_count = rng.gen_range(0_usize..10);
1578 let mut text_highlight_ranges = (0..text_highlight_count)
1579 .map(|_| buffer_snapshot.random_byte_range(0, &mut rng))
1580 .collect::<Vec<_>>();
1581 text_highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
1582 log::info!("highlighting text ranges {text_highlight_ranges:?}");
1583 text_highlights.insert(
1584 TypeId::of::<()>(),
1585 Arc::new((
1586 HighlightStyle::default(),
1587 text_highlight_ranges
1588 .into_iter()
1589 .map(|range| {
1590 buffer_snapshot.anchor_before(range.start)
1591 ..buffer_snapshot.anchor_after(range.end)
1592 })
1593 .collect(),
1594 )),
1595 );
1596
1597 let mut inlay_highlights = InlayHighlights::default();
1598 if !inlays.is_empty() {
1599 let inlay_highlight_count = rng.gen_range(0..inlays.len());
1600 let mut inlay_indices = BTreeSet::default();
1601 while inlay_indices.len() < inlay_highlight_count {
1602 inlay_indices.insert(rng.gen_range(0..inlays.len()));
1603 }
1604 let new_highlights = TreeMap::from_ordered_entries(
1605 inlay_indices
1606 .into_iter()
1607 .filter_map(|i| {
1608 let (_, inlay) = &inlays[i];
1609 let inlay_text_len = inlay.text.len();
1610 match inlay_text_len {
1611 0 => None,
1612 1 => Some(InlayHighlight {
1613 inlay: inlay.id,
1614 inlay_position: inlay.position,
1615 range: 0..1,
1616 }),
1617 n => {
1618 let inlay_text = inlay.text.to_string();
1619 let mut highlight_end = rng.gen_range(1..n);
1620 let mut highlight_start = rng.gen_range(0..highlight_end);
1621 while !inlay_text.is_char_boundary(highlight_end) {
1622 highlight_end += 1;
1623 }
1624 while !inlay_text.is_char_boundary(highlight_start) {
1625 highlight_start -= 1;
1626 }
1627 Some(InlayHighlight {
1628 inlay: inlay.id,
1629 inlay_position: inlay.position,
1630 range: highlight_start..highlight_end,
1631 })
1632 }
1633 }
1634 })
1635 .map(|highlight| (highlight.inlay, (HighlightStyle::default(), highlight))),
1636 );
1637 log::info!("highlighting inlay ranges {new_highlights:?}");
1638 inlay_highlights.insert(TypeId::of::<()>(), new_highlights);
1639 }
1640
1641 for _ in 0..5 {
1642 let mut end = rng.gen_range(0..=inlay_snapshot.len().0);
1643 end = expected_text.clip_offset(end, Bias::Right);
1644 let mut start = rng.gen_range(0..=end);
1645 start = expected_text.clip_offset(start, Bias::Right);
1646
1647 let range = InlayOffset(start)..InlayOffset(end);
1648 log::info!("calling inlay_snapshot.chunks({range:?})");
1649 let actual_text = inlay_snapshot
1650 .chunks(
1651 range,
1652 false,
1653 Highlights {
1654 text_highlights: Some(&text_highlights),
1655 inlay_highlights: Some(&inlay_highlights),
1656 ..Highlights::default()
1657 },
1658 )
1659 .map(|chunk| chunk.text)
1660 .collect::<String>();
1661 assert_eq!(
1662 actual_text,
1663 expected_text.slice(start..end).to_string(),
1664 "incorrect text in range {:?}",
1665 start..end
1666 );
1667
1668 assert_eq!(
1669 inlay_snapshot.text_summary_for_range(InlayOffset(start)..InlayOffset(end)),
1670 expected_text.slice(start..end).summary()
1671 );
1672 }
1673
1674 for edit in inlay_edits {
1675 prev_inlay_text.replace_range(
1676 edit.new.start.0..edit.new.start.0 + edit.old_len().0,
1677 &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0],
1678 );
1679 }
1680 assert_eq!(prev_inlay_text, inlay_snapshot.text());
1681
1682 assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
1683 assert_eq!(expected_text.len(), inlay_snapshot.len().0);
1684
1685 let mut buffer_point = Point::default();
1686 let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
1687 let mut buffer_chars = buffer_snapshot.chars_at(0);
1688 loop {
1689 // Ensure conversion from buffer coordinates to inlay coordinates
1690 // is consistent.
1691 let buffer_offset = buffer_snapshot.point_to_offset(buffer_point);
1692 assert_eq!(
1693 inlay_snapshot.to_point(inlay_snapshot.to_inlay_offset(buffer_offset)),
1694 inlay_point
1695 );
1696
1697 // No matter which bias we clip an inlay point with, it doesn't move
1698 // because it was constructed from a buffer point.
1699 assert_eq!(
1700 inlay_snapshot.clip_point(inlay_point, Bias::Left),
1701 inlay_point,
1702 "invalid inlay point for buffer point {:?} when clipped left",
1703 buffer_point
1704 );
1705 assert_eq!(
1706 inlay_snapshot.clip_point(inlay_point, Bias::Right),
1707 inlay_point,
1708 "invalid inlay point for buffer point {:?} when clipped right",
1709 buffer_point
1710 );
1711
1712 if let Some(ch) = buffer_chars.next() {
1713 if ch == '\n' {
1714 buffer_point += Point::new(1, 0);
1715 } else {
1716 buffer_point += Point::new(0, ch.len_utf8() as u32);
1717 }
1718
1719 // Ensure that moving forward in the buffer always moves the inlay point forward as well.
1720 let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
1721 assert!(new_inlay_point > inlay_point);
1722 inlay_point = new_inlay_point;
1723 } else {
1724 break;
1725 }
1726 }
1727
1728 let mut inlay_point = InlayPoint::default();
1729 let mut inlay_offset = InlayOffset::default();
1730 for ch in expected_text.chars() {
1731 assert_eq!(
1732 inlay_snapshot.to_offset(inlay_point),
1733 inlay_offset,
1734 "invalid to_offset({:?})",
1735 inlay_point
1736 );
1737 assert_eq!(
1738 inlay_snapshot.to_point(inlay_offset),
1739 inlay_point,
1740 "invalid to_point({:?})",
1741 inlay_offset
1742 );
1743
1744 let mut bytes = [0; 4];
1745 for byte in ch.encode_utf8(&mut bytes).as_bytes() {
1746 inlay_offset.0 += 1;
1747 if *byte == b'\n' {
1748 inlay_point.0 += Point::new(1, 0);
1749 } else {
1750 inlay_point.0 += Point::new(0, 1);
1751 }
1752
1753 let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left);
1754 let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right);
1755 assert!(
1756 clipped_left_point <= clipped_right_point,
1757 "inlay point {:?} when clipped left is greater than when clipped right ({:?} > {:?})",
1758 inlay_point,
1759 clipped_left_point,
1760 clipped_right_point
1761 );
1762
1763 // Ensure the clipped points are at valid text locations.
1764 assert_eq!(
1765 clipped_left_point.0,
1766 expected_text.clip_point(clipped_left_point.0, Bias::Left)
1767 );
1768 assert_eq!(
1769 clipped_right_point.0,
1770 expected_text.clip_point(clipped_right_point.0, Bias::Right)
1771 );
1772
1773 // Ensure the clipped points never overshoot the end of the map.
1774 assert!(clipped_left_point <= inlay_snapshot.max_point());
1775 assert!(clipped_right_point <= inlay_snapshot.max_point());
1776
1777 // Ensure the clipped points are at valid buffer locations.
1778 assert_eq!(
1779 inlay_snapshot
1780 .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_left_point)),
1781 clipped_left_point,
1782 "to_buffer_point({:?}) = {:?}",
1783 clipped_left_point,
1784 inlay_snapshot.to_buffer_point(clipped_left_point),
1785 );
1786 assert_eq!(
1787 inlay_snapshot
1788 .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_right_point)),
1789 clipped_right_point,
1790 "to_buffer_point({:?}) = {:?}",
1791 clipped_right_point,
1792 inlay_snapshot.to_buffer_point(clipped_right_point),
1793 );
1794 }
1795 }
1796 }
1797 }
1798
1799 fn init_test(cx: &mut App) {
1800 let store = SettingsStore::test(cx);
1801 cx.set_global(store);
1802 theme::init(theme::LoadThemes::JustBase, cx);
1803 }
1804}