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