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