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