1use crate::{
2 multi_buffer::{MultiBufferChunks, MultiBufferRows},
3 Anchor, InlayId, MultiBufferSnapshot, ToOffset,
4};
5use collections::{BTreeMap, BTreeSet, HashSet};
6use gpui::fonts::HighlightStyle;
7use language::{Chunk, Edit, Point, TextSummary};
8use std::{
9 any::TypeId,
10 cmp,
11 iter::Peekable,
12 ops::{Add, AddAssign, Range, Sub, SubAssign},
13 vec,
14};
15use sum_tree::{Bias, Cursor, SumTree};
16use text::{Patch, Rope};
17
18use super::{InlayHighlights, TextHighlights};
19
20pub struct InlayMap {
21 snapshot: InlaySnapshot,
22 inlays: Vec<Inlay>,
23}
24
25#[derive(Clone)]
26pub struct InlaySnapshot {
27 pub buffer: MultiBufferSnapshot,
28 transforms: SumTree<Transform>,
29 pub version: usize,
30}
31
32#[derive(Clone, Debug)]
33enum Transform {
34 Isomorphic(TextSummary),
35 Inlay(Inlay),
36}
37
38#[derive(Debug, Clone)]
39pub struct Inlay {
40 pub id: InlayId,
41 pub position: Anchor,
42 pub text: text::Rope,
43}
44
45impl Inlay {
46 pub fn hint(id: usize, position: Anchor, hint: &project::InlayHint) -> Self {
47 let mut text = hint.text();
48 if hint.padding_right && !text.ends_with(' ') {
49 text.push(' ');
50 }
51 if hint.padding_left && !text.starts_with(' ') {
52 text.insert(0, ' ');
53 }
54 Self {
55 id: InlayId::Hint(id),
56 position,
57 text: text.into(),
58 }
59 }
60
61 pub fn suggestion<T: Into<Rope>>(id: usize, position: Anchor, text: T) -> Self {
62 Self {
63 id: InlayId::Suggestion(id),
64 position,
65 text: text.into(),
66 }
67 }
68}
69
70impl sum_tree::Item for Transform {
71 type Summary = TransformSummary;
72
73 fn summary(&self) -> Self::Summary {
74 match self {
75 Transform::Isomorphic(summary) => TransformSummary {
76 input: summary.clone(),
77 output: summary.clone(),
78 },
79 Transform::Inlay(inlay) => TransformSummary {
80 input: TextSummary::default(),
81 output: inlay.text.summary(),
82 },
83 }
84 }
85}
86
87#[derive(Clone, Debug, Default)]
88struct TransformSummary {
89 input: TextSummary,
90 output: TextSummary,
91}
92
93impl sum_tree::Summary for TransformSummary {
94 type Context = ();
95
96 fn add_summary(&mut self, other: &Self, _: &()) {
97 self.input += &other.input;
98 self.output += &other.output;
99 }
100}
101
102pub type InlayEdit = Edit<InlayOffset>;
103
104#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
105pub struct InlayOffset(pub usize);
106
107impl Add for InlayOffset {
108 type Output = Self;
109
110 fn add(self, rhs: Self) -> Self::Output {
111 Self(self.0 + rhs.0)
112 }
113}
114
115impl Sub for InlayOffset {
116 type Output = Self;
117
118 fn sub(self, rhs: Self) -> Self::Output {
119 Self(self.0 - rhs.0)
120 }
121}
122
123impl AddAssign for InlayOffset {
124 fn add_assign(&mut self, rhs: Self) {
125 self.0 += rhs.0;
126 }
127}
128
129impl SubAssign for InlayOffset {
130 fn sub_assign(&mut self, rhs: Self) {
131 self.0 -= rhs.0;
132 }
133}
134
135impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
136 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
137 self.0 += &summary.output.len;
138 }
139}
140
141#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
142pub struct InlayPoint(pub Point);
143
144impl Add for InlayPoint {
145 type Output = Self;
146
147 fn add(self, rhs: Self) -> Self::Output {
148 Self(self.0 + rhs.0)
149 }
150}
151
152impl Sub for InlayPoint {
153 type Output = Self;
154
155 fn sub(self, rhs: Self) -> Self::Output {
156 Self(self.0 - rhs.0)
157 }
158}
159
160impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
161 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
162 self.0 += &summary.output.lines;
163 }
164}
165
166impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
167 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
168 *self += &summary.input.len;
169 }
170}
171
172impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
173 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
174 *self += &summary.input.lines;
175 }
176}
177
178#[derive(Clone)]
179pub struct InlayBufferRows<'a> {
180 transforms: Cursor<'a, Transform, (InlayPoint, Point)>,
181 buffer_rows: MultiBufferRows<'a>,
182 inlay_row: u32,
183 max_buffer_row: u32,
184}
185
186#[derive(Debug, Copy, Clone, Eq, PartialEq)]
187struct HighlightEndpoint {
188 offset: InlayOffset,
189 is_start: bool,
190 tag: Option<TypeId>,
191 style: HighlightStyle,
192}
193
194impl PartialOrd for HighlightEndpoint {
195 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
196 Some(self.cmp(other))
197 }
198}
199
200impl Ord for HighlightEndpoint {
201 fn cmp(&self, other: &Self) -> cmp::Ordering {
202 self.offset
203 .cmp(&other.offset)
204 .then_with(|| other.is_start.cmp(&self.is_start))
205 }
206}
207
208pub struct InlayChunks<'a> {
209 transforms: Cursor<'a, Transform, (InlayOffset, usize)>,
210 buffer_chunks: MultiBufferChunks<'a>,
211 buffer_chunk: Option<Chunk<'a>>,
212 inlay_chunks: Option<text::Chunks<'a>>,
213 inlay_chunk: Option<&'a str>,
214 output_offset: InlayOffset,
215 max_output_offset: InlayOffset,
216 hint_highlight_style: Option<HighlightStyle>,
217 suggestion_highlight_style: Option<HighlightStyle>,
218 highlight_endpoints: Peekable<vec::IntoIter<HighlightEndpoint>>,
219 active_highlights: BTreeMap<Option<TypeId>, HighlightStyle>,
220 snapshot: &'a InlaySnapshot,
221}
222
223impl<'a> InlayChunks<'a> {
224 pub fn seek(&mut self, offset: InlayOffset) {
225 self.transforms.seek(&offset, Bias::Right, &());
226
227 let buffer_offset = self.snapshot.to_buffer_offset(offset);
228 self.buffer_chunks.seek(buffer_offset);
229 self.inlay_chunks = None;
230 self.buffer_chunk = None;
231 self.output_offset = offset;
232 }
233
234 pub fn offset(&self) -> InlayOffset {
235 self.output_offset
236 }
237}
238
239impl<'a> Iterator for InlayChunks<'a> {
240 type Item = Chunk<'a>;
241
242 fn next(&mut self) -> Option<Self::Item> {
243 if self.output_offset == self.max_output_offset {
244 return None;
245 }
246
247 // TODO kb highlights are not displayed still
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_highlights: Option<&'a InlayHighlights>,
997 inlay_highlight_style: Option<HighlightStyle>,
998 suggestion_highlight_style: Option<HighlightStyle>,
999 ) -> InlayChunks<'a> {
1000 let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
1001 cursor.seek(&range.start, Bias::Right, &());
1002
1003 let empty_text_highlights = TextHighlights::default();
1004 let text_highlights = text_highlights.unwrap_or_else(|| &empty_text_highlights);
1005 let empty_inlay_highlights = InlayHighlights::default();
1006 let inlay_highlights = inlay_highlights.unwrap_or_else(|| &empty_inlay_highlights);
1007
1008 let mut highlight_endpoints = Vec::new();
1009 if !text_highlights.is_empty() || !inlay_highlights.is_empty() {
1010 while cursor.start().0 < range.end {
1011 let transform_start = self
1012 .buffer
1013 .anchor_after(self.to_buffer_offset(cmp::max(range.start, cursor.start().0)));
1014
1015 let transform_end = {
1016 let overshoot = InlayOffset(range.end.0 - cursor.start().0 .0);
1017 self.buffer.anchor_before(self.to_buffer_offset(cmp::min(
1018 cursor.end(&()).0,
1019 cursor.start().0 + overshoot,
1020 )))
1021 };
1022
1023 let mut covered_tags = HashSet::default();
1024 for (tag, text_highlights) in text_highlights.iter() {
1025 covered_tags.insert(*tag);
1026 let style = text_highlights.0;
1027 let ranges = &text_highlights.1;
1028
1029 let start_ix = match ranges.binary_search_by(|probe| {
1030 let cmp = probe.end.cmp(&transform_start, &self.buffer);
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 if range.start.cmp(&transform_end, &self.buffer).is_ge() {
1041 break;
1042 }
1043
1044 highlight_endpoints.push(HighlightEndpoint {
1045 offset: self.to_inlay_offset(range.start.to_offset(&self.buffer)),
1046 is_start: true,
1047 tag: *tag,
1048 style,
1049 });
1050 highlight_endpoints.push(HighlightEndpoint {
1051 offset: self.to_inlay_offset(range.end.to_offset(&self.buffer)),
1052 is_start: false,
1053 tag: *tag,
1054 style,
1055 });
1056 }
1057
1058 if let Some(inlay_highlights) = inlay_highlights.get(tag) {
1059 self.push_inlay_highlight_range(
1060 inlay_highlights,
1061 transform_start,
1062 transform_end,
1063 &mut highlight_endpoints,
1064 tag,
1065 );
1066 }
1067 }
1068
1069 for (tag, inlay_highlights) in inlay_highlights
1070 .iter()
1071 .filter(|(tag, _)| !covered_tags.contains(tag))
1072 {
1073 self.push_inlay_highlight_range(
1074 inlay_highlights,
1075 transform_start,
1076 transform_end,
1077 &mut highlight_endpoints,
1078 tag,
1079 );
1080 }
1081
1082 cursor.next(&());
1083 }
1084 highlight_endpoints.sort();
1085 cursor.seek(&range.start, Bias::Right, &());
1086 }
1087
1088 let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end);
1089 let buffer_chunks = self.buffer.chunks(buffer_range, language_aware);
1090
1091 InlayChunks {
1092 transforms: cursor,
1093 buffer_chunks,
1094 inlay_chunks: None,
1095 inlay_chunk: None,
1096 buffer_chunk: None,
1097 output_offset: range.start,
1098 max_output_offset: range.end,
1099 hint_highlight_style: inlay_highlight_style,
1100 suggestion_highlight_style,
1101 highlight_endpoints: highlight_endpoints.into_iter().peekable(),
1102 active_highlights: Default::default(),
1103 snapshot: self,
1104 }
1105 }
1106
1107 fn push_inlay_highlight_range(
1108 &self,
1109 inlay_highlights: &std::sync::Arc<(
1110 HighlightStyle,
1111 Vec<crate::link_go_to_definition::InlayRange>,
1112 )>,
1113 transform_start: Anchor,
1114 transform_end: Anchor,
1115 highlight_endpoints: &mut Vec<HighlightEndpoint>,
1116 tag: &Option<TypeId>,
1117 ) {
1118 let style = inlay_highlights.0;
1119 let ranges = &inlay_highlights.1;
1120 let start_ix = match ranges
1121 .binary_search_by(|probe| probe.inlay_position.cmp(&transform_start, &self.buffer))
1122 {
1123 Ok(i) | Err(i) => i,
1124 };
1125 for range in &ranges[start_ix..] {
1126 if range
1127 .inlay_position
1128 .cmp(&transform_end, &self.buffer)
1129 .is_ge()
1130 {
1131 break;
1132 }
1133
1134 highlight_endpoints.push(HighlightEndpoint {
1135 offset: range.highlight_start,
1136 is_start: true,
1137 tag: *tag,
1138 style,
1139 });
1140 highlight_endpoints.push(HighlightEndpoint {
1141 offset: range.highlight_end,
1142 is_start: false,
1143 tag: *tag,
1144 style,
1145 });
1146 }
1147 }
1148
1149 #[cfg(test)]
1150 pub fn text(&self) -> String {
1151 self.chunks(
1152 Default::default()..self.len(),
1153 false,
1154 None,
1155 None,
1156 None,
1157 None,
1158 )
1159 .map(|chunk| chunk.text)
1160 .collect()
1161 }
1162
1163 fn check_invariants(&self) {
1164 #[cfg(any(debug_assertions, feature = "test-support"))]
1165 {
1166 assert_eq!(self.transforms.summary().input, self.buffer.text_summary());
1167 let mut transforms = self.transforms.iter().peekable();
1168 while let Some(transform) = transforms.next() {
1169 let transform_is_isomorphic = matches!(transform, Transform::Isomorphic(_));
1170 if let Some(next_transform) = transforms.peek() {
1171 let next_transform_is_isomorphic =
1172 matches!(next_transform, Transform::Isomorphic(_));
1173 assert!(
1174 !transform_is_isomorphic || !next_transform_is_isomorphic,
1175 "two adjacent isomorphic transforms"
1176 );
1177 }
1178 }
1179 }
1180 }
1181}
1182
1183fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
1184 if summary.len == 0 {
1185 return;
1186 }
1187
1188 let mut summary = Some(summary);
1189 sum_tree.update_last(
1190 |transform| {
1191 if let Transform::Isomorphic(transform) = transform {
1192 *transform += summary.take().unwrap();
1193 }
1194 },
1195 &(),
1196 );
1197
1198 if let Some(summary) = summary {
1199 sum_tree.push(Transform::Isomorphic(summary), &());
1200 }
1201}
1202
1203#[cfg(test)]
1204mod tests {
1205 use super::*;
1206 use crate::{InlayId, MultiBuffer};
1207 use gpui::AppContext;
1208 use project::{InlayHint, InlayHintLabel, ResolveState};
1209 use rand::prelude::*;
1210 use settings::SettingsStore;
1211 use std::{cmp::Reverse, env, sync::Arc};
1212 use sum_tree::TreeMap;
1213 use text::Patch;
1214 use util::post_inc;
1215
1216 #[test]
1217 fn test_inlay_properties_label_padding() {
1218 assert_eq!(
1219 Inlay::hint(
1220 0,
1221 Anchor::min(),
1222 &InlayHint {
1223 label: InlayHintLabel::String("a".to_string()),
1224 position: text::Anchor::default(),
1225 padding_left: false,
1226 padding_right: false,
1227 tooltip: None,
1228 kind: None,
1229 resolve_state: ResolveState::Resolved,
1230 },
1231 )
1232 .text
1233 .to_string(),
1234 "a",
1235 "Should not pad label if not requested"
1236 );
1237
1238 assert_eq!(
1239 Inlay::hint(
1240 0,
1241 Anchor::min(),
1242 &InlayHint {
1243 label: InlayHintLabel::String("a".to_string()),
1244 position: text::Anchor::default(),
1245 padding_left: true,
1246 padding_right: true,
1247 tooltip: None,
1248 kind: None,
1249 resolve_state: ResolveState::Resolved,
1250 },
1251 )
1252 .text
1253 .to_string(),
1254 " a ",
1255 "Should pad label for every side requested"
1256 );
1257
1258 assert_eq!(
1259 Inlay::hint(
1260 0,
1261 Anchor::min(),
1262 &InlayHint {
1263 label: InlayHintLabel::String(" a ".to_string()),
1264 position: text::Anchor::default(),
1265 padding_left: false,
1266 padding_right: false,
1267 tooltip: None,
1268 kind: None,
1269 resolve_state: ResolveState::Resolved,
1270 },
1271 )
1272 .text
1273 .to_string(),
1274 " a ",
1275 "Should not change already padded label"
1276 );
1277
1278 assert_eq!(
1279 Inlay::hint(
1280 0,
1281 Anchor::min(),
1282 &InlayHint {
1283 label: InlayHintLabel::String(" a ".to_string()),
1284 position: text::Anchor::default(),
1285 padding_left: true,
1286 padding_right: true,
1287 tooltip: None,
1288 kind: None,
1289 resolve_state: ResolveState::Resolved,
1290 },
1291 )
1292 .text
1293 .to_string(),
1294 " a ",
1295 "Should not change already padded label"
1296 );
1297 }
1298
1299 #[gpui::test]
1300 fn test_basic_inlays(cx: &mut AppContext) {
1301 let buffer = MultiBuffer::build_simple("abcdefghi", cx);
1302 let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
1303 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1304 assert_eq!(inlay_snapshot.text(), "abcdefghi");
1305 let mut next_inlay_id = 0;
1306
1307 let (inlay_snapshot, _) = inlay_map.splice(
1308 Vec::new(),
1309 vec![Inlay {
1310 id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1311 position: buffer.read(cx).snapshot(cx).anchor_after(3),
1312 text: "|123|".into(),
1313 }],
1314 );
1315 assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
1316 assert_eq!(
1317 inlay_snapshot.to_inlay_point(Point::new(0, 0)),
1318 InlayPoint::new(0, 0)
1319 );
1320 assert_eq!(
1321 inlay_snapshot.to_inlay_point(Point::new(0, 1)),
1322 InlayPoint::new(0, 1)
1323 );
1324 assert_eq!(
1325 inlay_snapshot.to_inlay_point(Point::new(0, 2)),
1326 InlayPoint::new(0, 2)
1327 );
1328 assert_eq!(
1329 inlay_snapshot.to_inlay_point(Point::new(0, 3)),
1330 InlayPoint::new(0, 3)
1331 );
1332 assert_eq!(
1333 inlay_snapshot.to_inlay_point(Point::new(0, 4)),
1334 InlayPoint::new(0, 9)
1335 );
1336 assert_eq!(
1337 inlay_snapshot.to_inlay_point(Point::new(0, 5)),
1338 InlayPoint::new(0, 10)
1339 );
1340 assert_eq!(
1341 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1342 InlayPoint::new(0, 0)
1343 );
1344 assert_eq!(
1345 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1346 InlayPoint::new(0, 0)
1347 );
1348 assert_eq!(
1349 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1350 InlayPoint::new(0, 3)
1351 );
1352 assert_eq!(
1353 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1354 InlayPoint::new(0, 3)
1355 );
1356 assert_eq!(
1357 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1358 InlayPoint::new(0, 3)
1359 );
1360 assert_eq!(
1361 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1362 InlayPoint::new(0, 9)
1363 );
1364
1365 // Edits before or after the inlay should not affect it.
1366 buffer.update(cx, |buffer, cx| {
1367 buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx)
1368 });
1369 let (inlay_snapshot, _) = inlay_map.sync(
1370 buffer.read(cx).snapshot(cx),
1371 buffer_edits.consume().into_inner(),
1372 );
1373 assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
1374
1375 // An edit surrounding the inlay should invalidate it.
1376 buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx));
1377 let (inlay_snapshot, _) = inlay_map.sync(
1378 buffer.read(cx).snapshot(cx),
1379 buffer_edits.consume().into_inner(),
1380 );
1381 assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
1382
1383 let (inlay_snapshot, _) = inlay_map.splice(
1384 Vec::new(),
1385 vec![
1386 Inlay {
1387 id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1388 position: buffer.read(cx).snapshot(cx).anchor_before(3),
1389 text: "|123|".into(),
1390 },
1391 Inlay {
1392 id: InlayId::Suggestion(post_inc(&mut next_inlay_id)),
1393 position: buffer.read(cx).snapshot(cx).anchor_after(3),
1394 text: "|456|".into(),
1395 },
1396 ],
1397 );
1398 assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
1399
1400 // Edits ending where the inlay starts should not move it if it has a left bias.
1401 buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx));
1402 let (inlay_snapshot, _) = inlay_map.sync(
1403 buffer.read(cx).snapshot(cx),
1404 buffer_edits.consume().into_inner(),
1405 );
1406 assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
1407
1408 assert_eq!(
1409 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1410 InlayPoint::new(0, 0)
1411 );
1412 assert_eq!(
1413 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1414 InlayPoint::new(0, 0)
1415 );
1416
1417 assert_eq!(
1418 inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Left),
1419 InlayPoint::new(0, 1)
1420 );
1421 assert_eq!(
1422 inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Right),
1423 InlayPoint::new(0, 1)
1424 );
1425
1426 assert_eq!(
1427 inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Left),
1428 InlayPoint::new(0, 2)
1429 );
1430 assert_eq!(
1431 inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Right),
1432 InlayPoint::new(0, 2)
1433 );
1434
1435 assert_eq!(
1436 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1437 InlayPoint::new(0, 2)
1438 );
1439 assert_eq!(
1440 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1441 InlayPoint::new(0, 8)
1442 );
1443
1444 assert_eq!(
1445 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1446 InlayPoint::new(0, 2)
1447 );
1448 assert_eq!(
1449 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1450 InlayPoint::new(0, 8)
1451 );
1452
1453 assert_eq!(
1454 inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Left),
1455 InlayPoint::new(0, 2)
1456 );
1457 assert_eq!(
1458 inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Right),
1459 InlayPoint::new(0, 8)
1460 );
1461
1462 assert_eq!(
1463 inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Left),
1464 InlayPoint::new(0, 2)
1465 );
1466 assert_eq!(
1467 inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Right),
1468 InlayPoint::new(0, 8)
1469 );
1470
1471 assert_eq!(
1472 inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Left),
1473 InlayPoint::new(0, 2)
1474 );
1475 assert_eq!(
1476 inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Right),
1477 InlayPoint::new(0, 8)
1478 );
1479
1480 assert_eq!(
1481 inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Left),
1482 InlayPoint::new(0, 8)
1483 );
1484 assert_eq!(
1485 inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Right),
1486 InlayPoint::new(0, 8)
1487 );
1488
1489 assert_eq!(
1490 inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Left),
1491 InlayPoint::new(0, 9)
1492 );
1493 assert_eq!(
1494 inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Right),
1495 InlayPoint::new(0, 9)
1496 );
1497
1498 assert_eq!(
1499 inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Left),
1500 InlayPoint::new(0, 10)
1501 );
1502 assert_eq!(
1503 inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Right),
1504 InlayPoint::new(0, 10)
1505 );
1506
1507 assert_eq!(
1508 inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Left),
1509 InlayPoint::new(0, 11)
1510 );
1511 assert_eq!(
1512 inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Right),
1513 InlayPoint::new(0, 11)
1514 );
1515
1516 assert_eq!(
1517 inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Left),
1518 InlayPoint::new(0, 11)
1519 );
1520 assert_eq!(
1521 inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Right),
1522 InlayPoint::new(0, 17)
1523 );
1524
1525 assert_eq!(
1526 inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Left),
1527 InlayPoint::new(0, 11)
1528 );
1529 assert_eq!(
1530 inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Right),
1531 InlayPoint::new(0, 17)
1532 );
1533
1534 assert_eq!(
1535 inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Left),
1536 InlayPoint::new(0, 11)
1537 );
1538 assert_eq!(
1539 inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Right),
1540 InlayPoint::new(0, 17)
1541 );
1542
1543 assert_eq!(
1544 inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Left),
1545 InlayPoint::new(0, 11)
1546 );
1547 assert_eq!(
1548 inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Right),
1549 InlayPoint::new(0, 17)
1550 );
1551
1552 assert_eq!(
1553 inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Left),
1554 InlayPoint::new(0, 11)
1555 );
1556 assert_eq!(
1557 inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Right),
1558 InlayPoint::new(0, 17)
1559 );
1560
1561 assert_eq!(
1562 inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Left),
1563 InlayPoint::new(0, 17)
1564 );
1565 assert_eq!(
1566 inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Right),
1567 InlayPoint::new(0, 17)
1568 );
1569
1570 assert_eq!(
1571 inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Left),
1572 InlayPoint::new(0, 18)
1573 );
1574 assert_eq!(
1575 inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Right),
1576 InlayPoint::new(0, 18)
1577 );
1578
1579 // The inlays can be manually removed.
1580 let (inlay_snapshot, _) = inlay_map.splice(
1581 inlay_map.inlays.iter().map(|inlay| inlay.id).collect(),
1582 Vec::new(),
1583 );
1584 assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi");
1585 }
1586
1587 #[gpui::test]
1588 fn test_inlay_buffer_rows(cx: &mut AppContext) {
1589 let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx);
1590 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1591 assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi");
1592 let mut next_inlay_id = 0;
1593
1594 let (inlay_snapshot, _) = inlay_map.splice(
1595 Vec::new(),
1596 vec![
1597 Inlay {
1598 id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1599 position: buffer.read(cx).snapshot(cx).anchor_before(0),
1600 text: "|123|\n".into(),
1601 },
1602 Inlay {
1603 id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1604 position: buffer.read(cx).snapshot(cx).anchor_before(4),
1605 text: "|456|".into(),
1606 },
1607 Inlay {
1608 id: InlayId::Suggestion(post_inc(&mut next_inlay_id)),
1609 position: buffer.read(cx).snapshot(cx).anchor_before(7),
1610 text: "\n|567|\n".into(),
1611 },
1612 ],
1613 );
1614 assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
1615 assert_eq!(
1616 inlay_snapshot.buffer_rows(0).collect::<Vec<_>>(),
1617 vec![Some(0), None, Some(1), None, None, Some(2)]
1618 );
1619 }
1620
1621 #[gpui::test(iterations = 100)]
1622 fn test_random_inlays(cx: &mut AppContext, mut rng: StdRng) {
1623 init_test(cx);
1624
1625 let operations = env::var("OPERATIONS")
1626 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1627 .unwrap_or(10);
1628
1629 let len = rng.gen_range(0..30);
1630 let buffer = if rng.gen() {
1631 let text = util::RandomCharIter::new(&mut rng)
1632 .take(len)
1633 .collect::<String>();
1634 MultiBuffer::build_simple(&text, cx)
1635 } else {
1636 MultiBuffer::build_random(&mut rng, cx)
1637 };
1638 let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1639 let mut next_inlay_id = 0;
1640 log::info!("buffer text: {:?}", buffer_snapshot.text());
1641
1642 let mut highlights = TreeMap::default();
1643 let highlight_count = rng.gen_range(0_usize..10);
1644 let mut highlight_ranges = (0..highlight_count)
1645 .map(|_| buffer_snapshot.random_byte_range(0, &mut rng))
1646 .collect::<Vec<_>>();
1647 highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
1648 log::info!("highlighting ranges {:?}", highlight_ranges);
1649 let highlight_ranges = highlight_ranges
1650 .into_iter()
1651 .map(|range| {
1652 buffer_snapshot.anchor_before(range.start)..buffer_snapshot.anchor_after(range.end)
1653 })
1654 .collect::<Vec<_>>();
1655
1656 highlights.insert(
1657 Some(TypeId::of::<()>()),
1658 Arc::new((HighlightStyle::default(), highlight_ranges)),
1659 );
1660
1661 let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1662 for _ in 0..operations {
1663 let mut inlay_edits = Patch::default();
1664
1665 let mut prev_inlay_text = inlay_snapshot.text();
1666 let mut buffer_edits = Vec::new();
1667 match rng.gen_range(0..=100) {
1668 0..=50 => {
1669 let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1670 log::info!("mutated text: {:?}", snapshot.text());
1671 inlay_edits = Patch::new(edits);
1672 }
1673 _ => buffer.update(cx, |buffer, cx| {
1674 let subscription = buffer.subscribe();
1675 let edit_count = rng.gen_range(1..=5);
1676 buffer.randomly_mutate(&mut rng, edit_count, cx);
1677 buffer_snapshot = buffer.snapshot(cx);
1678 let edits = subscription.consume().into_inner();
1679 log::info!("editing {:?}", edits);
1680 buffer_edits.extend(edits);
1681 }),
1682 };
1683
1684 let (new_inlay_snapshot, new_inlay_edits) =
1685 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
1686 inlay_snapshot = new_inlay_snapshot;
1687 inlay_edits = inlay_edits.compose(new_inlay_edits);
1688
1689 log::info!("buffer text: {:?}", buffer_snapshot.text());
1690 log::info!("inlay text: {:?}", inlay_snapshot.text());
1691
1692 let inlays = inlay_map
1693 .inlays
1694 .iter()
1695 .filter(|inlay| inlay.position.is_valid(&buffer_snapshot))
1696 .map(|inlay| {
1697 let offset = inlay.position.to_offset(&buffer_snapshot);
1698 (offset, inlay.clone())
1699 })
1700 .collect::<Vec<_>>();
1701 let mut expected_text = Rope::from(buffer_snapshot.text());
1702 for (offset, inlay) in inlays.into_iter().rev() {
1703 expected_text.replace(offset..offset, &inlay.text.to_string());
1704 }
1705 assert_eq!(inlay_snapshot.text(), expected_text.to_string());
1706
1707 let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::<Vec<_>>();
1708 assert_eq!(
1709 expected_buffer_rows.len() as u32,
1710 expected_text.max_point().row + 1
1711 );
1712 for row_start in 0..expected_buffer_rows.len() {
1713 assert_eq!(
1714 inlay_snapshot
1715 .buffer_rows(row_start as u32)
1716 .collect::<Vec<_>>(),
1717 &expected_buffer_rows[row_start..],
1718 "incorrect buffer rows starting at {}",
1719 row_start
1720 );
1721 }
1722
1723 for _ in 0..5 {
1724 let mut end = rng.gen_range(0..=inlay_snapshot.len().0);
1725 end = expected_text.clip_offset(end, Bias::Right);
1726 let mut start = rng.gen_range(0..=end);
1727 start = expected_text.clip_offset(start, Bias::Right);
1728
1729 let actual_text = inlay_snapshot
1730 .chunks(
1731 InlayOffset(start)..InlayOffset(end),
1732 false,
1733 Some(&highlights),
1734 // TODO kb add tests
1735 None,
1736 None,
1737 None,
1738 )
1739 .map(|chunk| chunk.text)
1740 .collect::<String>();
1741 assert_eq!(
1742 actual_text,
1743 expected_text.slice(start..end).to_string(),
1744 "incorrect text in range {:?}",
1745 start..end
1746 );
1747
1748 assert_eq!(
1749 inlay_snapshot.text_summary_for_range(InlayOffset(start)..InlayOffset(end)),
1750 expected_text.slice(start..end).summary()
1751 );
1752 }
1753
1754 for edit in inlay_edits {
1755 prev_inlay_text.replace_range(
1756 edit.new.start.0..edit.new.start.0 + edit.old_len().0,
1757 &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0],
1758 );
1759 }
1760 assert_eq!(prev_inlay_text, inlay_snapshot.text());
1761
1762 assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
1763 assert_eq!(expected_text.len(), inlay_snapshot.len().0);
1764
1765 let mut buffer_point = Point::default();
1766 let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
1767 let mut buffer_chars = buffer_snapshot.chars_at(0);
1768 loop {
1769 // Ensure conversion from buffer coordinates to inlay coordinates
1770 // is consistent.
1771 let buffer_offset = buffer_snapshot.point_to_offset(buffer_point);
1772 assert_eq!(
1773 inlay_snapshot.to_point(inlay_snapshot.to_inlay_offset(buffer_offset)),
1774 inlay_point
1775 );
1776
1777 // No matter which bias we clip an inlay point with, it doesn't move
1778 // because it was constructed from a buffer point.
1779 assert_eq!(
1780 inlay_snapshot.clip_point(inlay_point, Bias::Left),
1781 inlay_point,
1782 "invalid inlay point for buffer point {:?} when clipped left",
1783 buffer_point
1784 );
1785 assert_eq!(
1786 inlay_snapshot.clip_point(inlay_point, Bias::Right),
1787 inlay_point,
1788 "invalid inlay point for buffer point {:?} when clipped right",
1789 buffer_point
1790 );
1791
1792 if let Some(ch) = buffer_chars.next() {
1793 if ch == '\n' {
1794 buffer_point += Point::new(1, 0);
1795 } else {
1796 buffer_point += Point::new(0, ch.len_utf8() as u32);
1797 }
1798
1799 // Ensure that moving forward in the buffer always moves the inlay point forward as well.
1800 let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
1801 assert!(new_inlay_point > inlay_point);
1802 inlay_point = new_inlay_point;
1803 } else {
1804 break;
1805 }
1806 }
1807
1808 let mut inlay_point = InlayPoint::default();
1809 let mut inlay_offset = InlayOffset::default();
1810 for ch in expected_text.chars() {
1811 assert_eq!(
1812 inlay_snapshot.to_offset(inlay_point),
1813 inlay_offset,
1814 "invalid to_offset({:?})",
1815 inlay_point
1816 );
1817 assert_eq!(
1818 inlay_snapshot.to_point(inlay_offset),
1819 inlay_point,
1820 "invalid to_point({:?})",
1821 inlay_offset
1822 );
1823
1824 let mut bytes = [0; 4];
1825 for byte in ch.encode_utf8(&mut bytes).as_bytes() {
1826 inlay_offset.0 += 1;
1827 if *byte == b'\n' {
1828 inlay_point.0 += Point::new(1, 0);
1829 } else {
1830 inlay_point.0 += Point::new(0, 1);
1831 }
1832
1833 let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left);
1834 let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right);
1835 assert!(
1836 clipped_left_point <= clipped_right_point,
1837 "inlay point {:?} when clipped left is greater than when clipped right ({:?} > {:?})",
1838 inlay_point,
1839 clipped_left_point,
1840 clipped_right_point
1841 );
1842
1843 // Ensure the clipped points are at valid text locations.
1844 assert_eq!(
1845 clipped_left_point.0,
1846 expected_text.clip_point(clipped_left_point.0, Bias::Left)
1847 );
1848 assert_eq!(
1849 clipped_right_point.0,
1850 expected_text.clip_point(clipped_right_point.0, Bias::Right)
1851 );
1852
1853 // Ensure the clipped points never overshoot the end of the map.
1854 assert!(clipped_left_point <= inlay_snapshot.max_point());
1855 assert!(clipped_right_point <= inlay_snapshot.max_point());
1856
1857 // Ensure the clipped points are at valid buffer locations.
1858 assert_eq!(
1859 inlay_snapshot
1860 .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_left_point)),
1861 clipped_left_point,
1862 "to_buffer_point({:?}) = {:?}",
1863 clipped_left_point,
1864 inlay_snapshot.to_buffer_point(clipped_left_point),
1865 );
1866 assert_eq!(
1867 inlay_snapshot
1868 .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_right_point)),
1869 clipped_right_point,
1870 "to_buffer_point({:?}) = {:?}",
1871 clipped_right_point,
1872 inlay_snapshot.to_buffer_point(clipped_right_point),
1873 );
1874 }
1875 }
1876 }
1877 }
1878
1879 fn init_test(cx: &mut AppContext) {
1880 cx.set_global(SettingsStore::test(cx));
1881 theme::init((), cx);
1882 }
1883}