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