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 inlay_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 empty_text_highlights = TextHighlights::default();
1003 let text_highlights = text_highlights.unwrap_or_else(|| &empty_text_highlights);
1004
1005 let mut highlight_endpoints = Vec::new();
1006 if !text_highlights.is_empty() {
1007 while cursor.start().0 < range.end {
1008 let transform_start = self
1009 .buffer
1010 .anchor_after(self.to_buffer_offset(cmp::max(range.start, cursor.start().0)));
1011 let transform_start = self.to_inlay_offset(transform_start.to_offset(&self.buffer));
1012
1013 let transform_end = {
1014 let overshoot = InlayOffset(range.end.0 - cursor.start().0 .0);
1015 self.buffer.anchor_before(self.to_buffer_offset(cmp::min(
1016 cursor.end(&()).0,
1017 cursor.start().0 + overshoot,
1018 )))
1019 };
1020 let transform_end = self.to_inlay_offset(transform_end.to_offset(&self.buffer));
1021
1022 for (tag, text_highlights) in text_highlights.iter() {
1023 let style = text_highlights.0;
1024 let ranges = &text_highlights.1;
1025
1026 let start_ix = match ranges.binary_search_by(|probe| {
1027 let cmp = self
1028 .document_to_inlay_range(probe)
1029 .end
1030 .cmp(&transform_start);
1031 if cmp.is_gt() {
1032 cmp::Ordering::Greater
1033 } else {
1034 cmp::Ordering::Less
1035 }
1036 }) {
1037 Ok(i) | Err(i) => i,
1038 };
1039 for range in &ranges[start_ix..] {
1040 let range = self.document_to_inlay_range(range);
1041 if range.start.cmp(&transform_end).is_ge() {
1042 break;
1043 }
1044
1045 highlight_endpoints.push(HighlightEndpoint {
1046 offset: range.start,
1047 is_start: true,
1048 tag: *tag,
1049 style,
1050 });
1051 highlight_endpoints.push(HighlightEndpoint {
1052 offset: range.end,
1053 is_start: false,
1054 tag: *tag,
1055 style,
1056 });
1057 }
1058 }
1059
1060 cursor.next(&());
1061 }
1062 highlight_endpoints.sort();
1063 cursor.seek(&range.start, Bias::Right, &());
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: inlay_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::{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 sum_tree::TreeMap;
1154 use text::Patch;
1155 use util::post_inc;
1156
1157 #[test]
1158 fn test_inlay_properties_label_padding() {
1159 assert_eq!(
1160 Inlay::hint(
1161 0,
1162 Anchor::min(),
1163 &InlayHint {
1164 label: InlayHintLabel::String("a".to_string()),
1165 position: text::Anchor::default(),
1166 padding_left: false,
1167 padding_right: false,
1168 tooltip: None,
1169 kind: None,
1170 resolve_state: ResolveState::Resolved,
1171 },
1172 )
1173 .text
1174 .to_string(),
1175 "a",
1176 "Should not pad label if not requested"
1177 );
1178
1179 assert_eq!(
1180 Inlay::hint(
1181 0,
1182 Anchor::min(),
1183 &InlayHint {
1184 label: InlayHintLabel::String("a".to_string()),
1185 position: text::Anchor::default(),
1186 padding_left: true,
1187 padding_right: true,
1188 tooltip: None,
1189 kind: None,
1190 resolve_state: ResolveState::Resolved,
1191 },
1192 )
1193 .text
1194 .to_string(),
1195 " a ",
1196 "Should pad label for every side requested"
1197 );
1198
1199 assert_eq!(
1200 Inlay::hint(
1201 0,
1202 Anchor::min(),
1203 &InlayHint {
1204 label: InlayHintLabel::String(" a ".to_string()),
1205 position: text::Anchor::default(),
1206 padding_left: false,
1207 padding_right: false,
1208 tooltip: None,
1209 kind: None,
1210 resolve_state: ResolveState::Resolved,
1211 },
1212 )
1213 .text
1214 .to_string(),
1215 " a ",
1216 "Should not change already padded label"
1217 );
1218
1219 assert_eq!(
1220 Inlay::hint(
1221 0,
1222 Anchor::min(),
1223 &InlayHint {
1224 label: InlayHintLabel::String(" a ".to_string()),
1225 position: text::Anchor::default(),
1226 padding_left: true,
1227 padding_right: true,
1228 tooltip: None,
1229 kind: None,
1230 resolve_state: ResolveState::Resolved,
1231 },
1232 )
1233 .text
1234 .to_string(),
1235 " a ",
1236 "Should not change already padded label"
1237 );
1238 }
1239
1240 #[gpui::test]
1241 fn test_basic_inlays(cx: &mut AppContext) {
1242 let buffer = MultiBuffer::build_simple("abcdefghi", cx);
1243 let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
1244 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1245 assert_eq!(inlay_snapshot.text(), "abcdefghi");
1246 let mut next_inlay_id = 0;
1247
1248 let (inlay_snapshot, _) = inlay_map.splice(
1249 Vec::new(),
1250 vec![Inlay {
1251 id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1252 position: buffer.read(cx).snapshot(cx).anchor_after(3),
1253 text: "|123|".into(),
1254 }],
1255 );
1256 assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
1257 assert_eq!(
1258 inlay_snapshot.to_inlay_point(Point::new(0, 0)),
1259 InlayPoint::new(0, 0)
1260 );
1261 assert_eq!(
1262 inlay_snapshot.to_inlay_point(Point::new(0, 1)),
1263 InlayPoint::new(0, 1)
1264 );
1265 assert_eq!(
1266 inlay_snapshot.to_inlay_point(Point::new(0, 2)),
1267 InlayPoint::new(0, 2)
1268 );
1269 assert_eq!(
1270 inlay_snapshot.to_inlay_point(Point::new(0, 3)),
1271 InlayPoint::new(0, 3)
1272 );
1273 assert_eq!(
1274 inlay_snapshot.to_inlay_point(Point::new(0, 4)),
1275 InlayPoint::new(0, 9)
1276 );
1277 assert_eq!(
1278 inlay_snapshot.to_inlay_point(Point::new(0, 5)),
1279 InlayPoint::new(0, 10)
1280 );
1281 assert_eq!(
1282 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1283 InlayPoint::new(0, 0)
1284 );
1285 assert_eq!(
1286 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1287 InlayPoint::new(0, 0)
1288 );
1289 assert_eq!(
1290 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1291 InlayPoint::new(0, 3)
1292 );
1293 assert_eq!(
1294 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1295 InlayPoint::new(0, 3)
1296 );
1297 assert_eq!(
1298 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1299 InlayPoint::new(0, 3)
1300 );
1301 assert_eq!(
1302 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1303 InlayPoint::new(0, 9)
1304 );
1305
1306 // Edits before or after the inlay should not affect it.
1307 buffer.update(cx, |buffer, cx| {
1308 buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx)
1309 });
1310 let (inlay_snapshot, _) = inlay_map.sync(
1311 buffer.read(cx).snapshot(cx),
1312 buffer_edits.consume().into_inner(),
1313 );
1314 assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
1315
1316 // An edit surrounding the inlay should invalidate it.
1317 buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx));
1318 let (inlay_snapshot, _) = inlay_map.sync(
1319 buffer.read(cx).snapshot(cx),
1320 buffer_edits.consume().into_inner(),
1321 );
1322 assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
1323
1324 let (inlay_snapshot, _) = inlay_map.splice(
1325 Vec::new(),
1326 vec![
1327 Inlay {
1328 id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1329 position: buffer.read(cx).snapshot(cx).anchor_before(3),
1330 text: "|123|".into(),
1331 },
1332 Inlay {
1333 id: InlayId::Suggestion(post_inc(&mut next_inlay_id)),
1334 position: buffer.read(cx).snapshot(cx).anchor_after(3),
1335 text: "|456|".into(),
1336 },
1337 ],
1338 );
1339 assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
1340
1341 // Edits ending where the inlay starts should not move it if it has a left bias.
1342 buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx));
1343 let (inlay_snapshot, _) = inlay_map.sync(
1344 buffer.read(cx).snapshot(cx),
1345 buffer_edits.consume().into_inner(),
1346 );
1347 assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
1348
1349 assert_eq!(
1350 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1351 InlayPoint::new(0, 0)
1352 );
1353 assert_eq!(
1354 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1355 InlayPoint::new(0, 0)
1356 );
1357
1358 assert_eq!(
1359 inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Left),
1360 InlayPoint::new(0, 1)
1361 );
1362 assert_eq!(
1363 inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Right),
1364 InlayPoint::new(0, 1)
1365 );
1366
1367 assert_eq!(
1368 inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Left),
1369 InlayPoint::new(0, 2)
1370 );
1371 assert_eq!(
1372 inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Right),
1373 InlayPoint::new(0, 2)
1374 );
1375
1376 assert_eq!(
1377 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1378 InlayPoint::new(0, 2)
1379 );
1380 assert_eq!(
1381 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1382 InlayPoint::new(0, 8)
1383 );
1384
1385 assert_eq!(
1386 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1387 InlayPoint::new(0, 2)
1388 );
1389 assert_eq!(
1390 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1391 InlayPoint::new(0, 8)
1392 );
1393
1394 assert_eq!(
1395 inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Left),
1396 InlayPoint::new(0, 2)
1397 );
1398 assert_eq!(
1399 inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Right),
1400 InlayPoint::new(0, 8)
1401 );
1402
1403 assert_eq!(
1404 inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Left),
1405 InlayPoint::new(0, 2)
1406 );
1407 assert_eq!(
1408 inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Right),
1409 InlayPoint::new(0, 8)
1410 );
1411
1412 assert_eq!(
1413 inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Left),
1414 InlayPoint::new(0, 2)
1415 );
1416 assert_eq!(
1417 inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Right),
1418 InlayPoint::new(0, 8)
1419 );
1420
1421 assert_eq!(
1422 inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Left),
1423 InlayPoint::new(0, 8)
1424 );
1425 assert_eq!(
1426 inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Right),
1427 InlayPoint::new(0, 8)
1428 );
1429
1430 assert_eq!(
1431 inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Left),
1432 InlayPoint::new(0, 9)
1433 );
1434 assert_eq!(
1435 inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Right),
1436 InlayPoint::new(0, 9)
1437 );
1438
1439 assert_eq!(
1440 inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Left),
1441 InlayPoint::new(0, 10)
1442 );
1443 assert_eq!(
1444 inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Right),
1445 InlayPoint::new(0, 10)
1446 );
1447
1448 assert_eq!(
1449 inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Left),
1450 InlayPoint::new(0, 11)
1451 );
1452 assert_eq!(
1453 inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Right),
1454 InlayPoint::new(0, 11)
1455 );
1456
1457 assert_eq!(
1458 inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Left),
1459 InlayPoint::new(0, 11)
1460 );
1461 assert_eq!(
1462 inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Right),
1463 InlayPoint::new(0, 17)
1464 );
1465
1466 assert_eq!(
1467 inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Left),
1468 InlayPoint::new(0, 11)
1469 );
1470 assert_eq!(
1471 inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Right),
1472 InlayPoint::new(0, 17)
1473 );
1474
1475 assert_eq!(
1476 inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Left),
1477 InlayPoint::new(0, 11)
1478 );
1479 assert_eq!(
1480 inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Right),
1481 InlayPoint::new(0, 17)
1482 );
1483
1484 assert_eq!(
1485 inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Left),
1486 InlayPoint::new(0, 11)
1487 );
1488 assert_eq!(
1489 inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Right),
1490 InlayPoint::new(0, 17)
1491 );
1492
1493 assert_eq!(
1494 inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Left),
1495 InlayPoint::new(0, 11)
1496 );
1497 assert_eq!(
1498 inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Right),
1499 InlayPoint::new(0, 17)
1500 );
1501
1502 assert_eq!(
1503 inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Left),
1504 InlayPoint::new(0, 17)
1505 );
1506 assert_eq!(
1507 inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Right),
1508 InlayPoint::new(0, 17)
1509 );
1510
1511 assert_eq!(
1512 inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Left),
1513 InlayPoint::new(0, 18)
1514 );
1515 assert_eq!(
1516 inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Right),
1517 InlayPoint::new(0, 18)
1518 );
1519
1520 // The inlays can be manually removed.
1521 let (inlay_snapshot, _) = inlay_map.splice(
1522 inlay_map.inlays.iter().map(|inlay| inlay.id).collect(),
1523 Vec::new(),
1524 );
1525 assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi");
1526 }
1527
1528 #[gpui::test]
1529 fn test_inlay_buffer_rows(cx: &mut AppContext) {
1530 let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx);
1531 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1532 assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi");
1533 let mut next_inlay_id = 0;
1534
1535 let (inlay_snapshot, _) = inlay_map.splice(
1536 Vec::new(),
1537 vec![
1538 Inlay {
1539 id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1540 position: buffer.read(cx).snapshot(cx).anchor_before(0),
1541 text: "|123|\n".into(),
1542 },
1543 Inlay {
1544 id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1545 position: buffer.read(cx).snapshot(cx).anchor_before(4),
1546 text: "|456|".into(),
1547 },
1548 Inlay {
1549 id: InlayId::Suggestion(post_inc(&mut next_inlay_id)),
1550 position: buffer.read(cx).snapshot(cx).anchor_before(7),
1551 text: "\n|567|\n".into(),
1552 },
1553 ],
1554 );
1555 assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
1556 assert_eq!(
1557 inlay_snapshot.buffer_rows(0).collect::<Vec<_>>(),
1558 vec![Some(0), None, Some(1), None, None, Some(2)]
1559 );
1560 }
1561
1562 #[gpui::test(iterations = 100)]
1563 fn test_random_inlays(cx: &mut AppContext, mut rng: StdRng) {
1564 init_test(cx);
1565
1566 let operations = env::var("OPERATIONS")
1567 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1568 .unwrap_or(10);
1569
1570 let len = rng.gen_range(0..30);
1571 let buffer = if rng.gen() {
1572 let text = util::RandomCharIter::new(&mut rng)
1573 .take(len)
1574 .collect::<String>();
1575 MultiBuffer::build_simple(&text, cx)
1576 } else {
1577 MultiBuffer::build_random(&mut rng, cx)
1578 };
1579 let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1580 let mut next_inlay_id = 0;
1581 log::info!("buffer text: {:?}", buffer_snapshot.text());
1582
1583 let mut highlights = TreeMap::default();
1584 let highlight_count = rng.gen_range(0_usize..10);
1585 let mut highlight_ranges = (0..highlight_count)
1586 .map(|_| buffer_snapshot.random_byte_range(0, &mut rng))
1587 .collect::<Vec<_>>();
1588 highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
1589 log::info!("highlighting ranges {:?}", highlight_ranges);
1590 let highlight_ranges = highlight_ranges
1591 .into_iter()
1592 .map(|range| {
1593 buffer_snapshot.anchor_before(range.start)..buffer_snapshot.anchor_after(range.end)
1594 })
1595 // TODO add inlay highlight tests
1596 .map(DocumentRange::Text)
1597 .collect::<Vec<_>>();
1598
1599 highlights.insert(
1600 Some(TypeId::of::<()>()),
1601 Arc::new((HighlightStyle::default(), highlight_ranges)),
1602 );
1603
1604 let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1605 for _ in 0..operations {
1606 let mut inlay_edits = Patch::default();
1607
1608 let mut prev_inlay_text = inlay_snapshot.text();
1609 let mut buffer_edits = Vec::new();
1610 match rng.gen_range(0..=100) {
1611 0..=50 => {
1612 let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1613 log::info!("mutated text: {:?}", snapshot.text());
1614 inlay_edits = Patch::new(edits);
1615 }
1616 _ => buffer.update(cx, |buffer, cx| {
1617 let subscription = buffer.subscribe();
1618 let edit_count = rng.gen_range(1..=5);
1619 buffer.randomly_mutate(&mut rng, edit_count, cx);
1620 buffer_snapshot = buffer.snapshot(cx);
1621 let edits = subscription.consume().into_inner();
1622 log::info!("editing {:?}", edits);
1623 buffer_edits.extend(edits);
1624 }),
1625 };
1626
1627 let (new_inlay_snapshot, new_inlay_edits) =
1628 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
1629 inlay_snapshot = new_inlay_snapshot;
1630 inlay_edits = inlay_edits.compose(new_inlay_edits);
1631
1632 log::info!("buffer text: {:?}", buffer_snapshot.text());
1633 log::info!("inlay text: {:?}", inlay_snapshot.text());
1634
1635 let inlays = inlay_map
1636 .inlays
1637 .iter()
1638 .filter(|inlay| inlay.position.is_valid(&buffer_snapshot))
1639 .map(|inlay| {
1640 let offset = inlay.position.to_offset(&buffer_snapshot);
1641 (offset, inlay.clone())
1642 })
1643 .collect::<Vec<_>>();
1644 let mut expected_text = Rope::from(buffer_snapshot.text());
1645 for (offset, inlay) in inlays.into_iter().rev() {
1646 expected_text.replace(offset..offset, &inlay.text.to_string());
1647 }
1648 assert_eq!(inlay_snapshot.text(), expected_text.to_string());
1649
1650 let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::<Vec<_>>();
1651 assert_eq!(
1652 expected_buffer_rows.len() as u32,
1653 expected_text.max_point().row + 1
1654 );
1655 for row_start in 0..expected_buffer_rows.len() {
1656 assert_eq!(
1657 inlay_snapshot
1658 .buffer_rows(row_start as u32)
1659 .collect::<Vec<_>>(),
1660 &expected_buffer_rows[row_start..],
1661 "incorrect buffer rows starting at {}",
1662 row_start
1663 );
1664 }
1665
1666 for _ in 0..5 {
1667 let mut end = rng.gen_range(0..=inlay_snapshot.len().0);
1668 end = expected_text.clip_offset(end, Bias::Right);
1669 let mut start = rng.gen_range(0..=end);
1670 start = expected_text.clip_offset(start, Bias::Right);
1671
1672 let actual_text = inlay_snapshot
1673 .chunks(
1674 InlayOffset(start)..InlayOffset(end),
1675 false,
1676 Some(&highlights),
1677 None,
1678 None,
1679 )
1680 .map(|chunk| chunk.text)
1681 .collect::<String>();
1682 assert_eq!(
1683 actual_text,
1684 expected_text.slice(start..end).to_string(),
1685 "incorrect text in range {:?}",
1686 start..end
1687 );
1688
1689 assert_eq!(
1690 inlay_snapshot.text_summary_for_range(InlayOffset(start)..InlayOffset(end)),
1691 expected_text.slice(start..end).summary()
1692 );
1693 }
1694
1695 for edit in inlay_edits {
1696 prev_inlay_text.replace_range(
1697 edit.new.start.0..edit.new.start.0 + edit.old_len().0,
1698 &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0],
1699 );
1700 }
1701 assert_eq!(prev_inlay_text, inlay_snapshot.text());
1702
1703 assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
1704 assert_eq!(expected_text.len(), inlay_snapshot.len().0);
1705
1706 let mut buffer_point = Point::default();
1707 let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
1708 let mut buffer_chars = buffer_snapshot.chars_at(0);
1709 loop {
1710 // Ensure conversion from buffer coordinates to inlay coordinates
1711 // is consistent.
1712 let buffer_offset = buffer_snapshot.point_to_offset(buffer_point);
1713 assert_eq!(
1714 inlay_snapshot.to_point(inlay_snapshot.to_inlay_offset(buffer_offset)),
1715 inlay_point
1716 );
1717
1718 // No matter which bias we clip an inlay point with, it doesn't move
1719 // because it was constructed from a buffer point.
1720 assert_eq!(
1721 inlay_snapshot.clip_point(inlay_point, Bias::Left),
1722 inlay_point,
1723 "invalid inlay point for buffer point {:?} when clipped left",
1724 buffer_point
1725 );
1726 assert_eq!(
1727 inlay_snapshot.clip_point(inlay_point, Bias::Right),
1728 inlay_point,
1729 "invalid inlay point for buffer point {:?} when clipped right",
1730 buffer_point
1731 );
1732
1733 if let Some(ch) = buffer_chars.next() {
1734 if ch == '\n' {
1735 buffer_point += Point::new(1, 0);
1736 } else {
1737 buffer_point += Point::new(0, ch.len_utf8() as u32);
1738 }
1739
1740 // Ensure that moving forward in the buffer always moves the inlay point forward as well.
1741 let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
1742 assert!(new_inlay_point > inlay_point);
1743 inlay_point = new_inlay_point;
1744 } else {
1745 break;
1746 }
1747 }
1748
1749 let mut inlay_point = InlayPoint::default();
1750 let mut inlay_offset = InlayOffset::default();
1751 for ch in expected_text.chars() {
1752 assert_eq!(
1753 inlay_snapshot.to_offset(inlay_point),
1754 inlay_offset,
1755 "invalid to_offset({:?})",
1756 inlay_point
1757 );
1758 assert_eq!(
1759 inlay_snapshot.to_point(inlay_offset),
1760 inlay_point,
1761 "invalid to_point({:?})",
1762 inlay_offset
1763 );
1764
1765 let mut bytes = [0; 4];
1766 for byte in ch.encode_utf8(&mut bytes).as_bytes() {
1767 inlay_offset.0 += 1;
1768 if *byte == b'\n' {
1769 inlay_point.0 += Point::new(1, 0);
1770 } else {
1771 inlay_point.0 += Point::new(0, 1);
1772 }
1773
1774 let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left);
1775 let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right);
1776 assert!(
1777 clipped_left_point <= clipped_right_point,
1778 "inlay point {:?} when clipped left is greater than when clipped right ({:?} > {:?})",
1779 inlay_point,
1780 clipped_left_point,
1781 clipped_right_point
1782 );
1783
1784 // Ensure the clipped points are at valid text locations.
1785 assert_eq!(
1786 clipped_left_point.0,
1787 expected_text.clip_point(clipped_left_point.0, Bias::Left)
1788 );
1789 assert_eq!(
1790 clipped_right_point.0,
1791 expected_text.clip_point(clipped_right_point.0, Bias::Right)
1792 );
1793
1794 // Ensure the clipped points never overshoot the end of the map.
1795 assert!(clipped_left_point <= inlay_snapshot.max_point());
1796 assert!(clipped_right_point <= inlay_snapshot.max_point());
1797
1798 // Ensure the clipped points are at valid buffer locations.
1799 assert_eq!(
1800 inlay_snapshot
1801 .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_left_point)),
1802 clipped_left_point,
1803 "to_buffer_point({:?}) = {:?}",
1804 clipped_left_point,
1805 inlay_snapshot.to_buffer_point(clipped_left_point),
1806 );
1807 assert_eq!(
1808 inlay_snapshot
1809 .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_right_point)),
1810 clipped_right_point,
1811 "to_buffer_point({:?}) = {:?}",
1812 clipped_right_point,
1813 inlay_snapshot.to_buffer_point(clipped_right_point),
1814 );
1815 }
1816 }
1817 }
1818 }
1819
1820 fn init_test(cx: &mut AppContext) {
1821 cx.set_global(SettingsStore::test(cx));
1822 theme::init((), cx);
1823 }
1824}