1use crate::{
2 inlay_cache::{Inlay, InlayId, InlayProperties},
3 multi_buffer::{MultiBufferChunks, MultiBufferRows},
4 MultiBufferSnapshot, ToOffset,
5};
6use collections::{BTreeSet, HashMap};
7use gpui::fonts::HighlightStyle;
8use language::{Chunk, Edit, Point, Rope, TextSummary};
9use parking_lot::Mutex;
10use std::{
11 cmp,
12 ops::{Add, AddAssign, Range, Sub},
13};
14use sum_tree::{Bias, Cursor, SumTree};
15use text::Patch;
16use util::post_inc;
17
18pub struct InlayMap {
19 snapshot: Mutex<InlaySnapshot>,
20 inlays_by_id: HashMap<InlayId, Inlay>,
21 inlays: Vec<Inlay>,
22}
23
24#[derive(Clone)]
25pub struct InlaySnapshot {
26 pub buffer: MultiBufferSnapshot,
27 transforms: SumTree<Transform>,
28 pub version: usize,
29}
30
31#[derive(Clone, Debug)]
32enum Transform {
33 Isomorphic(TextSummary),
34 Inlay(Inlay),
35}
36
37impl sum_tree::Item for Transform {
38 type Summary = TransformSummary;
39
40 fn summary(&self) -> Self::Summary {
41 match self {
42 Transform::Isomorphic(summary) => TransformSummary {
43 input: summary.clone(),
44 output: summary.clone(),
45 },
46 Transform::Inlay(inlay) => TransformSummary {
47 input: TextSummary::default(),
48 output: inlay.text.summary(),
49 },
50 }
51 }
52}
53
54#[derive(Clone, Debug, Default)]
55struct TransformSummary {
56 input: TextSummary,
57 output: TextSummary,
58}
59
60impl sum_tree::Summary for TransformSummary {
61 type Context = ();
62
63 fn add_summary(&mut self, other: &Self, _: &()) {
64 self.input += &other.input;
65 self.output += &other.output;
66 }
67}
68
69pub type InlayEdit = Edit<InlayOffset>;
70
71#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
72pub struct InlayOffset(pub usize);
73
74impl Add for InlayOffset {
75 type Output = Self;
76
77 fn add(self, rhs: Self) -> Self::Output {
78 Self(self.0 + rhs.0)
79 }
80}
81
82impl Sub for InlayOffset {
83 type Output = Self;
84
85 fn sub(self, rhs: Self) -> Self::Output {
86 Self(self.0 - rhs.0)
87 }
88}
89
90impl AddAssign for InlayOffset {
91 fn add_assign(&mut self, rhs: Self) {
92 self.0 += rhs.0;
93 }
94}
95
96impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
97 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
98 self.0 += &summary.output.len;
99 }
100}
101
102#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
103pub struct InlayPoint(pub Point);
104
105impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
106 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
107 self.0 += &summary.output.lines;
108 }
109}
110
111impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
112 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
113 *self += &summary.input.len;
114 }
115}
116
117impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
118 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
119 *self += &summary.input.lines;
120 }
121}
122
123#[derive(Clone)]
124pub struct InlayBufferRows<'a> {
125 transforms: Cursor<'a, Transform, (InlayPoint, Point)>,
126 buffer_rows: MultiBufferRows<'a>,
127 inlay_row: u32,
128}
129
130pub struct InlayChunks<'a> {
131 transforms: Cursor<'a, Transform, (InlayOffset, usize)>,
132 buffer_chunks: MultiBufferChunks<'a>,
133 buffer_chunk: Option<Chunk<'a>>,
134 inlay_chunks: Option<text::Chunks<'a>>,
135 output_offset: InlayOffset,
136 max_output_offset: InlayOffset,
137 highlight_style: Option<HighlightStyle>,
138}
139
140impl<'a> Iterator for InlayChunks<'a> {
141 type Item = Chunk<'a>;
142
143 fn next(&mut self) -> Option<Self::Item> {
144 if self.output_offset == self.max_output_offset {
145 return None;
146 }
147
148 let chunk = match self.transforms.item()? {
149 Transform::Isomorphic(_) => {
150 let chunk = self
151 .buffer_chunk
152 .get_or_insert_with(|| self.buffer_chunks.next().unwrap());
153 if chunk.text.is_empty() {
154 *chunk = self.buffer_chunks.next().unwrap();
155 }
156
157 let (prefix, suffix) = chunk.text.split_at(cmp::min(
158 self.transforms.end(&()).0 .0 - self.output_offset.0,
159 chunk.text.len(),
160 ));
161
162 chunk.text = suffix;
163 self.output_offset.0 += prefix.len();
164 Chunk {
165 text: prefix,
166 ..chunk.clone()
167 }
168 }
169 Transform::Inlay(inlay) => {
170 let inlay_chunks = self.inlay_chunks.get_or_insert_with(|| {
171 let start = self.output_offset - self.transforms.start().0;
172 let end = cmp::min(self.max_output_offset, self.transforms.end(&()).0)
173 - self.transforms.start().0;
174 inlay.text.chunks_in_range(start.0..end.0)
175 });
176
177 let chunk = inlay_chunks.next().unwrap();
178 self.output_offset.0 += chunk.len();
179 Chunk {
180 text: chunk,
181 highlight_style: self.highlight_style,
182 ..Default::default()
183 }
184 }
185 };
186
187 if self.output_offset == self.transforms.end(&()).0 {
188 self.inlay_chunks = None;
189 self.transforms.next(&());
190 }
191
192 Some(chunk)
193 }
194}
195
196impl<'a> Iterator for InlayBufferRows<'a> {
197 type Item = Option<u32>;
198
199 fn next(&mut self) -> Option<Self::Item> {
200 let buffer_row = if self.inlay_row == 0 {
201 self.buffer_rows.next().unwrap()
202 } else {
203 match self.transforms.item()? {
204 Transform::Inlay(_) => None,
205 Transform::Isomorphic(_) => self.buffer_rows.next().unwrap(),
206 }
207 };
208
209 self.inlay_row += 1;
210 self.transforms
211 .seek_forward(&InlayPoint::new(self.inlay_row, 0), Bias::Left, &());
212
213 Some(buffer_row)
214 }
215}
216
217impl InlayPoint {
218 pub fn new(row: u32, column: u32) -> Self {
219 Self(Point::new(row, column))
220 }
221
222 pub fn row(self) -> u32 {
223 self.0.row
224 }
225
226 pub fn column(self) -> u32 {
227 self.0.column
228 }
229}
230
231impl InlayMap {
232 pub fn new(buffer: MultiBufferSnapshot) -> (Self, InlaySnapshot) {
233 let version = 0;
234 let snapshot = InlaySnapshot {
235 buffer: buffer.clone(),
236 transforms: SumTree::from_item(Transform::Isomorphic(buffer.text_summary()), &()),
237 version,
238 };
239
240 (
241 Self {
242 snapshot: Mutex::new(snapshot.clone()),
243 inlays_by_id: Default::default(),
244 inlays: Default::default(),
245 },
246 snapshot,
247 )
248 }
249
250 pub fn sync(
251 &mut self,
252 buffer_snapshot: MultiBufferSnapshot,
253 mut buffer_edits: Vec<text::Edit<usize>>,
254 ) -> (InlaySnapshot, Vec<InlayEdit>) {
255 let mut snapshot = self.snapshot.lock();
256
257 if buffer_edits.is_empty() {
258 if snapshot.buffer.trailing_excerpt_update_count()
259 != buffer_snapshot.trailing_excerpt_update_count()
260 {
261 buffer_edits.push(Edit {
262 old: snapshot.buffer.len()..snapshot.buffer.len(),
263 new: buffer_snapshot.len()..buffer_snapshot.len(),
264 });
265 }
266 }
267
268 if buffer_edits.is_empty() {
269 if snapshot.buffer.edit_count() != buffer_snapshot.edit_count()
270 || snapshot.buffer.parse_count() != buffer_snapshot.parse_count()
271 || snapshot.buffer.diagnostics_update_count()
272 != buffer_snapshot.diagnostics_update_count()
273 || snapshot.buffer.git_diff_update_count()
274 != buffer_snapshot.git_diff_update_count()
275 || snapshot.buffer.trailing_excerpt_update_count()
276 != buffer_snapshot.trailing_excerpt_update_count()
277 {
278 snapshot.version += 1;
279 }
280
281 snapshot.buffer = buffer_snapshot;
282 (snapshot.clone(), Vec::new())
283 } else {
284 let mut inlay_edits = Patch::default();
285 let mut new_transforms = SumTree::new();
286 let mut cursor = snapshot.transforms.cursor::<(usize, InlayOffset)>();
287 let mut buffer_edits_iter = buffer_edits.iter().peekable();
288 while let Some(buffer_edit) = buffer_edits_iter.next() {
289 new_transforms
290 .push_tree(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &());
291 if let Some(Transform::Isomorphic(transform)) = cursor.item() {
292 if cursor.end(&()).0 == buffer_edit.old.start {
293 new_transforms.push(Transform::Isomorphic(transform.clone()), &());
294 cursor.next(&());
295 }
296 }
297
298 // Remove all the inlays and transforms contained by the edit.
299 let old_start =
300 cursor.start().1 + InlayOffset(buffer_edit.old.start - cursor.start().0);
301 cursor.seek(&buffer_edit.old.end, Bias::Right, &());
302 let old_end =
303 cursor.start().1 + InlayOffset(buffer_edit.old.end - cursor.start().0);
304
305 // Push the unchanged prefix.
306 let prefix_start = new_transforms.summary().input.len;
307 let prefix_end = buffer_edit.new.start;
308 push_isomorphic(
309 &mut new_transforms,
310 buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
311 );
312 let new_start = InlayOffset(new_transforms.summary().output.len);
313
314 let start_ix = match self.inlays.binary_search_by(|probe| {
315 probe
316 .position
317 .to_offset(&buffer_snapshot)
318 .cmp(&buffer_edit.new.start)
319 .then(std::cmp::Ordering::Greater)
320 }) {
321 Ok(ix) | Err(ix) => ix,
322 };
323
324 for inlay in &self.inlays[start_ix..] {
325 let buffer_offset = inlay.position.to_offset(&buffer_snapshot);
326 if buffer_offset > buffer_edit.new.end {
327 break;
328 }
329
330 let prefix_start = new_transforms.summary().input.len;
331 let prefix_end = buffer_offset;
332 push_isomorphic(
333 &mut new_transforms,
334 buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
335 );
336
337 if inlay.position.is_valid(&buffer_snapshot) {
338 new_transforms.push(Transform::Inlay(inlay.clone()), &());
339 }
340 }
341
342 // Apply the rest of the edit.
343 let transform_start = new_transforms.summary().input.len;
344 push_isomorphic(
345 &mut new_transforms,
346 buffer_snapshot.text_summary_for_range(transform_start..buffer_edit.new.end),
347 );
348 let new_end = InlayOffset(new_transforms.summary().output.len);
349 inlay_edits.push(Edit {
350 old: old_start..old_end,
351 new: new_start..new_end,
352 });
353
354 // If the next edit doesn't intersect the current isomorphic transform, then
355 // we can push its remainder.
356 if buffer_edits_iter
357 .peek()
358 .map_or(true, |edit| edit.old.start >= cursor.end(&()).0)
359 {
360 let transform_start = new_transforms.summary().input.len;
361 let transform_end =
362 buffer_edit.new.end + (cursor.end(&()).0 - buffer_edit.old.end);
363 push_isomorphic(
364 &mut new_transforms,
365 buffer_snapshot.text_summary_for_range(transform_start..transform_end),
366 );
367 cursor.next(&());
368 }
369 }
370
371 new_transforms.push_tree(cursor.suffix(&()), &());
372 if new_transforms.first().is_none() {
373 new_transforms.push(Transform::Isomorphic(Default::default()), &());
374 }
375
376 drop(cursor);
377 snapshot.transforms = new_transforms;
378 snapshot.version += 1;
379 snapshot.buffer = buffer_snapshot;
380 snapshot.check_invariants();
381
382 (snapshot.clone(), inlay_edits.into_inner())
383 }
384 }
385
386 pub fn splice<T: Into<Rope>>(
387 &mut self,
388 to_remove: Vec<InlayId>,
389 to_insert: Vec<(InlayId, InlayProperties<T>)>,
390 ) -> (InlaySnapshot, Vec<InlayEdit>) {
391 let snapshot = self.snapshot.lock();
392 let mut edits = BTreeSet::new();
393
394 self.inlays.retain(|inlay| !to_remove.contains(&inlay.id));
395 for inlay_id in to_remove {
396 if let Some(inlay) = self.inlays_by_id.remove(&inlay_id) {
397 let offset = inlay.position.to_offset(&snapshot.buffer);
398 edits.insert(offset);
399 }
400 }
401
402 for (id, properties) in to_insert {
403 let inlay = Inlay {
404 id,
405 position: properties.position,
406 text: properties.text.into(),
407 };
408 self.inlays_by_id.insert(inlay.id, inlay.clone());
409 match self
410 .inlays
411 .binary_search_by(|probe| probe.position.cmp(&inlay.position, &snapshot.buffer))
412 {
413 Ok(ix) | Err(ix) => {
414 self.inlays.insert(ix, inlay.clone());
415 }
416 }
417
418 let offset = inlay.position.to_offset(&snapshot.buffer);
419 edits.insert(offset);
420 }
421
422 let buffer_edits = edits
423 .into_iter()
424 .map(|offset| Edit {
425 old: offset..offset,
426 new: offset..offset,
427 })
428 .collect();
429 let buffer_snapshot = snapshot.buffer.clone();
430 drop(snapshot);
431 self.sync(buffer_snapshot, buffer_edits)
432 }
433
434 #[cfg(any(test, feature = "test-support"))]
435 pub(crate) fn randomly_mutate(
436 &mut self,
437 next_inlay_id: &mut usize,
438 rng: &mut rand::rngs::StdRng,
439 ) -> (InlaySnapshot, Vec<InlayEdit>) {
440 use rand::prelude::*;
441
442 let mut to_remove = Vec::new();
443 let mut to_insert = Vec::new();
444 let mut snapshot = self.snapshot.lock();
445 for _ in 0..rng.gen_range(1..=5) {
446 if self.inlays.is_empty() || rng.gen() {
447 let position = snapshot.buffer.random_byte_range(0, rng).start;
448 let bias = if rng.gen() { Bias::Left } else { Bias::Right };
449 let len = rng.gen_range(1..=5);
450 let text = util::RandomCharIter::new(&mut *rng)
451 .filter(|ch| *ch != '\r')
452 .take(len)
453 .collect::<String>();
454 log::info!(
455 "creating inlay at buffer offset {} with bias {:?} and text {:?}",
456 position,
457 bias,
458 text
459 );
460 to_insert.push((
461 InlayId(post_inc(next_inlay_id)),
462 InlayProperties {
463 position: snapshot.buffer.anchor_at(position, bias),
464 text,
465 },
466 ));
467 } else {
468 to_remove.push(*self.inlays_by_id.keys().choose(rng).unwrap());
469 }
470 }
471 log::info!("removing inlays: {:?}", to_remove);
472
473 drop(snapshot);
474 self.splice(to_remove, to_insert)
475 }
476}
477
478impl InlaySnapshot {
479 pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
480 let mut cursor = self
481 .transforms
482 .cursor::<(InlayOffset, (InlayPoint, usize))>();
483 cursor.seek(&offset, Bias::Right, &());
484 let overshoot = offset.0 - cursor.start().0 .0;
485 match cursor.item() {
486 Some(Transform::Isomorphic(_)) => {
487 let buffer_offset_start = cursor.start().1 .1;
488 let buffer_offset_end = buffer_offset_start + overshoot;
489 let buffer_start = self.buffer.offset_to_point(buffer_offset_start);
490 let buffer_end = self.buffer.offset_to_point(buffer_offset_end);
491 InlayPoint(cursor.start().1 .0 .0 + (buffer_end - buffer_start))
492 }
493 Some(Transform::Inlay(inlay)) => {
494 let overshoot = inlay.text.offset_to_point(overshoot);
495 InlayPoint(cursor.start().1 .0 .0 + overshoot)
496 }
497 None => self.max_point(),
498 }
499 }
500
501 pub fn len(&self) -> InlayOffset {
502 InlayOffset(self.transforms.summary().output.len)
503 }
504
505 pub fn max_point(&self) -> InlayPoint {
506 InlayPoint(self.transforms.summary().output.lines)
507 }
508
509 pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
510 let mut cursor = self
511 .transforms
512 .cursor::<(InlayPoint, (InlayOffset, Point))>();
513 cursor.seek(&point, Bias::Right, &());
514 let overshoot = point.0 - cursor.start().0 .0;
515 match cursor.item() {
516 Some(Transform::Isomorphic(_)) => {
517 let buffer_point_start = cursor.start().1 .1;
518 let buffer_point_end = buffer_point_start + overshoot;
519 let buffer_offset_start = self.buffer.point_to_offset(buffer_point_start);
520 let buffer_offset_end = self.buffer.point_to_offset(buffer_point_end);
521 InlayOffset(cursor.start().1 .0 .0 + (buffer_offset_end - buffer_offset_start))
522 }
523 Some(Transform::Inlay(inlay)) => {
524 let overshoot = inlay.text.point_to_offset(overshoot);
525 InlayOffset(cursor.start().1 .0 .0 + overshoot)
526 }
527 None => self.len(),
528 }
529 }
530
531 pub fn chars_at(&self, start: InlayPoint) -> impl '_ + Iterator<Item = char> {
532 self.chunks(self.to_offset(start)..self.len(), false, None)
533 .flat_map(|chunk| chunk.text.chars())
534 }
535
536 pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
537 let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
538 cursor.seek(&point, Bias::Right, &());
539 match cursor.item() {
540 Some(Transform::Isomorphic(_)) => {
541 let overshoot = point.0 - cursor.start().0 .0;
542 cursor.start().1 + overshoot
543 }
544 Some(Transform::Inlay(_)) => cursor.start().1,
545 None => self.buffer.max_point(),
546 }
547 }
548
549 pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize {
550 let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
551 cursor.seek(&offset, Bias::Right, &());
552 match cursor.item() {
553 Some(Transform::Isomorphic(_)) => {
554 let overshoot = offset - cursor.start().0;
555 cursor.start().1 + overshoot.0
556 }
557 Some(Transform::Inlay(_)) => cursor.start().1,
558 None => self.buffer.len(),
559 }
560 }
561
562 pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset {
563 let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>();
564 cursor.seek(&offset, Bias::Left, &());
565 match cursor.item() {
566 Some(Transform::Isomorphic(_)) => {
567 let overshoot = offset - cursor.start().0;
568 InlayOffset(cursor.start().1 .0 + overshoot)
569 }
570 Some(Transform::Inlay(_)) => cursor.start().1,
571 None => self.len(),
572 }
573 }
574
575 pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
576 let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>();
577 cursor.seek(&point, Bias::Left, &());
578 match cursor.item() {
579 Some(Transform::Isomorphic(_)) => {
580 let overshoot = point - cursor.start().0;
581 InlayPoint(cursor.start().1 .0 + overshoot)
582 }
583 Some(Transform::Inlay(_)) => cursor.start().1,
584 None => self.max_point(),
585 }
586 }
587
588 pub fn clip_point(&self, point: InlayPoint, bias: Bias) -> InlayPoint {
589 let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
590 cursor.seek(&point, Bias::Left, &());
591
592 let mut bias = bias;
593 let mut skipped_inlay = false;
594 loop {
595 match cursor.item() {
596 Some(Transform::Isomorphic(transform)) => {
597 let overshoot = if skipped_inlay {
598 match bias {
599 Bias::Left => transform.lines,
600 Bias::Right => {
601 if transform.first_line_chars == 0 {
602 Point::new(1, 0)
603 } else {
604 Point::new(0, 1)
605 }
606 }
607 }
608 } else {
609 point.0 - cursor.start().0 .0
610 };
611 let buffer_point = cursor.start().1 + overshoot;
612 let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias);
613 let clipped_overshoot = clipped_buffer_point - cursor.start().1;
614 return InlayPoint(cursor.start().0 .0 + clipped_overshoot);
615 }
616 Some(Transform::Inlay(_)) => skipped_inlay = true,
617 None => match bias {
618 Bias::Left => return Default::default(),
619 Bias::Right => bias = Bias::Left,
620 },
621 }
622
623 if bias == Bias::Left {
624 cursor.prev(&());
625 } else {
626 cursor.next(&());
627 }
628 }
629 }
630
631 pub fn text_summary(&self) -> TextSummary {
632 self.transforms.summary().output.clone()
633 }
634
635 pub fn text_summary_for_range(&self, range: Range<InlayPoint>) -> TextSummary {
636 let mut summary = TextSummary::default();
637
638 let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
639 cursor.seek(&range.start, Bias::Right, &());
640
641 let overshoot = range.start.0 - cursor.start().0 .0;
642 match cursor.item() {
643 Some(Transform::Isomorphic(_)) => {
644 let buffer_start = cursor.start().1;
645 let suffix_start = buffer_start + overshoot;
646 let suffix_end =
647 buffer_start + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0);
648 summary = self.buffer.text_summary_for_range(suffix_start..suffix_end);
649 cursor.next(&());
650 }
651 Some(Transform::Inlay(inlay)) => {
652 let suffix_start = inlay.text.point_to_offset(overshoot);
653 let suffix_end = inlay.text.point_to_offset(
654 cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0,
655 );
656 summary = inlay.text.cursor(suffix_start).summary(suffix_end);
657 cursor.next(&());
658 }
659 None => {}
660 }
661
662 if range.end > cursor.start().0 {
663 summary += cursor
664 .summary::<_, TransformSummary>(&range.end, Bias::Right, &())
665 .output;
666
667 let overshoot = range.end.0 - cursor.start().0 .0;
668 match cursor.item() {
669 Some(Transform::Isomorphic(_)) => {
670 let prefix_start = cursor.start().1;
671 let prefix_end = prefix_start + overshoot;
672 summary += self
673 .buffer
674 .text_summary_for_range::<TextSummary, Point>(prefix_start..prefix_end);
675 }
676 Some(Transform::Inlay(inlay)) => {
677 let prefix_end = inlay.text.point_to_offset(overshoot);
678 summary += inlay.text.cursor(0).summary::<TextSummary>(prefix_end);
679 }
680 None => {}
681 }
682 }
683
684 summary
685 }
686
687 pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> {
688 let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
689 let inlay_point = InlayPoint::new(row, 0);
690 cursor.seek(&inlay_point, Bias::Left, &());
691
692 let mut buffer_point = cursor.start().1;
693 let buffer_row = if row == 0 {
694 0
695 } else {
696 match cursor.item() {
697 Some(Transform::Isomorphic(_)) => {
698 buffer_point += inlay_point.0 - cursor.start().0 .0;
699 buffer_point.row
700 }
701 _ => cmp::min(buffer_point.row + 1, self.buffer.max_point().row),
702 }
703 };
704
705 InlayBufferRows {
706 transforms: cursor,
707 inlay_row: inlay_point.row(),
708 buffer_rows: self.buffer.buffer_rows(buffer_row),
709 }
710 }
711
712 pub fn line_len(&self, row: u32) -> u32 {
713 let line_start = self.to_offset(InlayPoint::new(row, 0)).0;
714 let line_end = if row >= self.max_point().row() {
715 self.len().0
716 } else {
717 self.to_offset(InlayPoint::new(row + 1, 0)).0 - 1
718 };
719 (line_end - line_start) as u32
720 }
721
722 pub fn chunks<'a>(
723 &'a self,
724 range: Range<InlayOffset>,
725 language_aware: bool,
726 inlay_highlight_style: Option<HighlightStyle>,
727 ) -> InlayChunks<'a> {
728 let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
729 cursor.seek(&range.start, Bias::Right, &());
730
731 let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end);
732 let buffer_chunks = self.buffer.chunks(buffer_range, language_aware);
733
734 InlayChunks {
735 transforms: cursor,
736 buffer_chunks,
737 inlay_chunks: None,
738 buffer_chunk: None,
739 output_offset: range.start,
740 max_output_offset: range.end,
741 highlight_style: inlay_highlight_style,
742 }
743 }
744
745 #[cfg(test)]
746 pub fn text(&self) -> String {
747 self.chunks(Default::default()..self.len(), false, None)
748 .map(|chunk| chunk.text)
749 .collect()
750 }
751
752 fn check_invariants(&self) {
753 #[cfg(any(debug_assertions, feature = "test-support"))]
754 {
755 assert_eq!(self.transforms.summary().input, self.buffer.text_summary());
756 }
757 }
758}
759
760fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
761 if summary.len == 0 {
762 return;
763 }
764
765 let mut summary = Some(summary);
766 sum_tree.update_last(
767 |transform| {
768 if let Transform::Isomorphic(transform) = transform {
769 *transform += summary.take().unwrap();
770 }
771 },
772 &(),
773 );
774
775 if let Some(summary) = summary {
776 sum_tree.push(Transform::Isomorphic(summary), &());
777 }
778}
779
780#[cfg(test)]
781mod tests {
782 use super::*;
783 use crate::MultiBuffer;
784 use gpui::AppContext;
785 use rand::prelude::*;
786 use settings::SettingsStore;
787 use std::env;
788 use text::Patch;
789 use util::post_inc;
790
791 #[gpui::test]
792 fn test_basic_inlays(cx: &mut AppContext) {
793 let buffer = MultiBuffer::build_simple("abcdefghi", cx);
794 let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
795 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
796 assert_eq!(inlay_snapshot.text(), "abcdefghi");
797 let mut next_inlay_id = 0;
798
799 let (inlay_snapshot, _) = inlay_map.splice(
800 Vec::new(),
801 vec![(
802 InlayId(post_inc(&mut next_inlay_id)),
803 InlayProperties {
804 position: buffer.read(cx).snapshot(cx).anchor_after(3),
805 text: "|123|",
806 },
807 )],
808 );
809 assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
810 assert_eq!(
811 inlay_snapshot.to_inlay_point(Point::new(0, 0)),
812 InlayPoint::new(0, 0)
813 );
814 assert_eq!(
815 inlay_snapshot.to_inlay_point(Point::new(0, 1)),
816 InlayPoint::new(0, 1)
817 );
818 assert_eq!(
819 inlay_snapshot.to_inlay_point(Point::new(0, 2)),
820 InlayPoint::new(0, 2)
821 );
822 assert_eq!(
823 inlay_snapshot.to_inlay_point(Point::new(0, 3)),
824 InlayPoint::new(0, 3)
825 );
826 assert_eq!(
827 inlay_snapshot.to_inlay_point(Point::new(0, 4)),
828 InlayPoint::new(0, 9)
829 );
830 assert_eq!(
831 inlay_snapshot.to_inlay_point(Point::new(0, 5)),
832 InlayPoint::new(0, 10)
833 );
834 assert_eq!(
835 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
836 InlayPoint::new(0, 0)
837 );
838 assert_eq!(
839 inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
840 InlayPoint::new(0, 0)
841 );
842 assert_eq!(
843 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
844 InlayPoint::new(0, 3)
845 );
846 assert_eq!(
847 inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
848 InlayPoint::new(0, 3)
849 );
850 assert_eq!(
851 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
852 InlayPoint::new(0, 3)
853 );
854 assert_eq!(
855 inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
856 InlayPoint::new(0, 9)
857 );
858
859 // Edits before or after the inlay should not affect it.
860 buffer.update(cx, |buffer, cx| {
861 buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx)
862 });
863 let (inlay_snapshot, _) = inlay_map.sync(
864 buffer.read(cx).snapshot(cx),
865 buffer_edits.consume().into_inner(),
866 );
867 assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
868
869 // An edit surrounding the inlay should invalidate it.
870 buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx));
871 let (inlay_snapshot, _) = inlay_map.sync(
872 buffer.read(cx).snapshot(cx),
873 buffer_edits.consume().into_inner(),
874 );
875 assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
876
877 let (inlay_snapshot, _) = inlay_map.splice(
878 Vec::new(),
879 vec![
880 (
881 InlayId(post_inc(&mut next_inlay_id)),
882 InlayProperties {
883 position: buffer.read(cx).snapshot(cx).anchor_before(3),
884 text: "|123|",
885 },
886 ),
887 (
888 InlayId(post_inc(&mut next_inlay_id)),
889 InlayProperties {
890 position: buffer.read(cx).snapshot(cx).anchor_after(3),
891 text: "|456|",
892 },
893 ),
894 ],
895 );
896 assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
897
898 // Edits ending where the inlay starts should not move it if it has a left bias.
899 buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx));
900 let (inlay_snapshot, _) = inlay_map.sync(
901 buffer.read(cx).snapshot(cx),
902 buffer_edits.consume().into_inner(),
903 );
904 assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
905
906 // The inlays can be manually removed.
907 let (inlay_snapshot, _) = inlay_map
908 .splice::<String>(inlay_map.inlays_by_id.keys().copied().collect(), Vec::new());
909 assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi");
910 }
911
912 #[gpui::test]
913 fn test_inlay_buffer_rows(cx: &mut AppContext) {
914 let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx);
915 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
916 assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi");
917
918 let (inlay_snapshot, _) = inlay_map.splice(
919 Vec::new(),
920 vec![
921 (
922 InlayId(0),
923 InlayProperties {
924 position: buffer.read(cx).snapshot(cx).anchor_before(0),
925 text: "|123|\n",
926 },
927 ),
928 (
929 InlayId(1),
930 InlayProperties {
931 position: buffer.read(cx).snapshot(cx).anchor_before(4),
932 text: "|456|",
933 },
934 ),
935 (
936 InlayId(1),
937 InlayProperties {
938 position: buffer.read(cx).snapshot(cx).anchor_before(7),
939 text: "\n|567|\n",
940 },
941 ),
942 ],
943 );
944 assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
945 assert_eq!(
946 inlay_snapshot.buffer_rows(0).collect::<Vec<_>>(),
947 vec![Some(0), None, Some(1), None, None, Some(2)]
948 );
949 }
950
951 #[gpui::test(iterations = 100)]
952 fn test_random_inlays(cx: &mut AppContext, mut rng: StdRng) {
953 init_test(cx);
954
955 let operations = env::var("OPERATIONS")
956 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
957 .unwrap_or(10);
958
959 let len = rng.gen_range(0..30);
960 let buffer = if rng.gen() {
961 let text = util::RandomCharIter::new(&mut rng)
962 .take(len)
963 .collect::<String>();
964 MultiBuffer::build_simple(&text, cx)
965 } else {
966 MultiBuffer::build_random(&mut rng, cx)
967 };
968 let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
969 log::info!("buffer text: {:?}", buffer_snapshot.text());
970
971 let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
972 let mut next_inlay_id = 0;
973
974 for _ in 0..operations {
975 let mut inlay_edits = Patch::default();
976
977 let mut prev_inlay_text = inlay_snapshot.text();
978 let mut buffer_edits = Vec::new();
979 match rng.gen_range(0..=100) {
980 0..=50 => {
981 let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
982 log::info!("mutated text: {:?}", snapshot.text());
983 inlay_edits = Patch::new(edits);
984 }
985 _ => buffer.update(cx, |buffer, cx| {
986 let subscription = buffer.subscribe();
987 let edit_count = rng.gen_range(1..=5);
988 buffer.randomly_mutate(&mut rng, edit_count, cx);
989 buffer_snapshot = buffer.snapshot(cx);
990 let edits = subscription.consume().into_inner();
991 log::info!("editing {:?}", edits);
992 buffer_edits.extend(edits);
993 }),
994 };
995
996 let (new_inlay_snapshot, new_inlay_edits) =
997 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
998 inlay_snapshot = new_inlay_snapshot;
999 inlay_edits = inlay_edits.compose(new_inlay_edits);
1000
1001 log::info!("buffer text: {:?}", buffer_snapshot.text());
1002 log::info!("inlay text: {:?}", inlay_snapshot.text());
1003
1004 let inlays = inlay_map
1005 .inlays
1006 .iter()
1007 .filter(|inlay| inlay.position.is_valid(&buffer_snapshot))
1008 .map(|inlay| {
1009 let offset = inlay.position.to_offset(&buffer_snapshot);
1010 (offset, inlay.clone())
1011 })
1012 .collect::<Vec<_>>();
1013 let mut expected_text = Rope::from(buffer_snapshot.text().as_str());
1014 for (offset, inlay) in inlays.into_iter().rev() {
1015 expected_text.replace(offset..offset, &inlay.text.to_string());
1016 }
1017 assert_eq!(inlay_snapshot.text(), expected_text.to_string());
1018
1019 let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::<Vec<_>>();
1020 assert_eq!(
1021 expected_buffer_rows.len() as u32,
1022 expected_text.max_point().row + 1
1023 );
1024 for row_start in 0..expected_buffer_rows.len() {
1025 assert_eq!(
1026 inlay_snapshot
1027 .buffer_rows(row_start as u32)
1028 .collect::<Vec<_>>(),
1029 &expected_buffer_rows[row_start..],
1030 "incorrect buffer rows starting at {}",
1031 row_start
1032 );
1033 }
1034
1035 for _ in 0..5 {
1036 let mut end = rng.gen_range(0..=inlay_snapshot.len().0);
1037 end = expected_text.clip_offset(end, Bias::Right);
1038 let mut start = rng.gen_range(0..=end);
1039 start = expected_text.clip_offset(start, Bias::Right);
1040
1041 let actual_text = inlay_snapshot
1042 .chunks(InlayOffset(start)..InlayOffset(end), false, None)
1043 .map(|chunk| chunk.text)
1044 .collect::<String>();
1045 assert_eq!(
1046 actual_text,
1047 expected_text.slice(start..end).to_string(),
1048 "incorrect text in range {:?}",
1049 start..end
1050 );
1051
1052 let start_point = InlayPoint(expected_text.offset_to_point(start));
1053 let end_point = InlayPoint(expected_text.offset_to_point(end));
1054 assert_eq!(
1055 inlay_snapshot.text_summary_for_range(start_point..end_point),
1056 expected_text.slice(start..end).summary()
1057 );
1058 }
1059
1060 for edit in inlay_edits {
1061 prev_inlay_text.replace_range(
1062 edit.new.start.0..edit.new.start.0 + edit.old_len().0,
1063 &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0],
1064 );
1065 }
1066 assert_eq!(prev_inlay_text, inlay_snapshot.text());
1067
1068 assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
1069 assert_eq!(expected_text.len(), inlay_snapshot.len().0);
1070
1071 let mut inlay_point = InlayPoint::default();
1072 let mut inlay_offset = InlayOffset::default();
1073 for ch in expected_text.chars() {
1074 assert_eq!(
1075 inlay_snapshot.to_offset(inlay_point),
1076 inlay_offset,
1077 "invalid to_offset({:?})",
1078 inlay_point
1079 );
1080 assert_eq!(
1081 inlay_snapshot.to_point(inlay_offset),
1082 inlay_point,
1083 "invalid to_point({:?})",
1084 inlay_offset
1085 );
1086 assert_eq!(
1087 inlay_snapshot.to_inlay_point(inlay_snapshot.to_buffer_point(inlay_point)),
1088 inlay_snapshot.clip_point(inlay_point, Bias::Left),
1089 "to_buffer_point({:?}) = {:?}",
1090 inlay_point,
1091 inlay_snapshot.to_buffer_point(inlay_point),
1092 );
1093
1094 let mut bytes = [0; 4];
1095 for byte in ch.encode_utf8(&mut bytes).as_bytes() {
1096 inlay_offset.0 += 1;
1097 if *byte == b'\n' {
1098 inlay_point.0 += Point::new(1, 0);
1099 } else {
1100 inlay_point.0 += Point::new(0, 1);
1101 }
1102
1103 let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left);
1104 let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right);
1105 assert!(
1106 clipped_left_point <= clipped_right_point,
1107 "clipped left point {:?} is greater than clipped right point {:?}",
1108 clipped_left_point,
1109 clipped_right_point
1110 );
1111
1112 // Ensure the clipped points are at valid text locations.
1113 assert_eq!(
1114 clipped_left_point.0,
1115 expected_text.clip_point(clipped_left_point.0, Bias::Left)
1116 );
1117 assert_eq!(
1118 clipped_right_point.0,
1119 expected_text.clip_point(clipped_right_point.0, Bias::Right)
1120 );
1121
1122 // Ensure the clipped points never overshoot the end of the map.
1123 assert!(clipped_left_point <= inlay_snapshot.max_point());
1124 assert!(clipped_right_point <= inlay_snapshot.max_point());
1125 }
1126 }
1127 }
1128 }
1129
1130 fn init_test(cx: &mut AppContext) {
1131 cx.set_global(SettingsStore::test(cx));
1132 theme::init((), cx);
1133 }
1134}