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