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