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