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