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