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