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