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