1use super::{
2 inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot},
3 Highlights,
4};
5use gpui::{AnyElement, ElementId, WindowContext};
6use language::{Chunk, ChunkRenderer, Edit, Point, TextSummary};
7use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToOffset};
8use std::{
9 cmp::{self, Ordering},
10 fmt, iter,
11 ops::{Add, AddAssign, Deref, DerefMut, Range, Sub},
12 sync::Arc,
13};
14use sum_tree::{Bias, Cursor, FilterCursor, SumTree, Summary};
15use util::post_inc;
16
17#[derive(Clone)]
18pub struct FoldPlaceholder {
19 /// Creates an element to represent this fold's placeholder.
20 pub render: Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut WindowContext) -> AnyElement>,
21 /// If true, the element is constrained to the shaped width of an ellipsis.
22 pub constrain_width: bool,
23 /// If true, merges the fold with an adjacent one.
24 pub merge_adjacent: bool,
25}
26
27impl FoldPlaceholder {
28 #[cfg(any(test, feature = "test-support"))]
29 pub fn test() -> Self {
30 use gpui::IntoElement;
31
32 Self {
33 render: Arc::new(|_id, _range, _cx| gpui::Empty.into_any_element()),
34 constrain_width: true,
35 merge_adjacent: true,
36 }
37 }
38}
39
40impl fmt::Debug for FoldPlaceholder {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 f.debug_struct("FoldPlaceholder")
43 .field("constrain_width", &self.constrain_width)
44 .finish()
45 }
46}
47
48impl Eq for FoldPlaceholder {}
49
50impl PartialEq for FoldPlaceholder {
51 fn eq(&self, other: &Self) -> bool {
52 Arc::ptr_eq(&self.render, &other.render) && self.constrain_width == other.constrain_width
53 }
54}
55
56#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
57pub struct FoldPoint(pub Point);
58
59impl FoldPoint {
60 pub fn new(row: u32, column: u32) -> Self {
61 Self(Point::new(row, column))
62 }
63
64 pub fn row(self) -> u32 {
65 self.0.row
66 }
67
68 pub fn column(self) -> u32 {
69 self.0.column
70 }
71
72 pub fn row_mut(&mut self) -> &mut u32 {
73 &mut self.0.row
74 }
75
76 #[cfg(test)]
77 pub fn column_mut(&mut self) -> &mut u32 {
78 &mut self.0.column
79 }
80
81 pub fn to_inlay_point(self, snapshot: &FoldSnapshot) -> InlayPoint {
82 let mut cursor = snapshot.transforms.cursor::<(FoldPoint, InlayPoint)>(&());
83 cursor.seek(&self, Bias::Right, &());
84 let overshoot = self.0 - cursor.start().0 .0;
85 InlayPoint(cursor.start().1 .0 + overshoot)
86 }
87
88 pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset {
89 let mut cursor = snapshot
90 .transforms
91 .cursor::<(FoldPoint, TransformSummary)>(&());
92 cursor.seek(&self, Bias::Right, &());
93 let overshoot = self.0 - cursor.start().1.output.lines;
94 let mut offset = cursor.start().1.output.len;
95 if !overshoot.is_zero() {
96 let transform = cursor.item().expect("display point out of range");
97 assert!(transform.placeholder.is_none());
98 let end_inlay_offset = snapshot
99 .inlay_snapshot
100 .to_offset(InlayPoint(cursor.start().1.input.lines + overshoot));
101 offset += end_inlay_offset.0 - cursor.start().1.input.len;
102 }
103 FoldOffset(offset)
104 }
105}
106
107impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint {
108 fn zero(_cx: &()) -> Self {
109 Default::default()
110 }
111
112 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
113 self.0 += &summary.output.lines;
114 }
115}
116
117pub(crate) struct FoldMapWriter<'a>(&'a mut FoldMap);
118
119impl<'a> FoldMapWriter<'a> {
120 pub(crate) fn fold<T: ToOffset>(
121 &mut self,
122 ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
123 ) -> (FoldSnapshot, Vec<FoldEdit>) {
124 let mut edits = Vec::new();
125 let mut folds = Vec::new();
126 let snapshot = self.0.snapshot.inlay_snapshot.clone();
127 for (range, fold_text) in ranges.into_iter() {
128 let buffer = &snapshot.buffer;
129 let range = range.start.to_offset(buffer)..range.end.to_offset(buffer);
130
131 // Ignore any empty ranges.
132 if range.start == range.end {
133 continue;
134 }
135
136 // For now, ignore any ranges that span an excerpt boundary.
137 let fold_range =
138 FoldRange(buffer.anchor_after(range.start)..buffer.anchor_before(range.end));
139 if fold_range.0.start.excerpt_id != fold_range.0.end.excerpt_id {
140 continue;
141 }
142
143 folds.push(Fold {
144 id: FoldId(post_inc(&mut self.0.next_fold_id.0)),
145 range: fold_range,
146 placeholder: fold_text,
147 });
148
149 let inlay_range =
150 snapshot.to_inlay_offset(range.start)..snapshot.to_inlay_offset(range.end);
151 edits.push(InlayEdit {
152 old: inlay_range.clone(),
153 new: inlay_range,
154 });
155 }
156
157 let buffer = &snapshot.buffer;
158 folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(&a.range, &b.range, buffer));
159
160 self.0.snapshot.folds = {
161 let mut new_tree = SumTree::new(buffer);
162 let mut cursor = self.0.snapshot.folds.cursor::<FoldRange>(buffer);
163 for fold in folds {
164 new_tree.append(cursor.slice(&fold.range, Bias::Right, buffer), buffer);
165 new_tree.push(fold, buffer);
166 }
167 new_tree.append(cursor.suffix(buffer), buffer);
168 new_tree
169 };
170
171 let edits = consolidate_inlay_edits(edits);
172 let edits = self.0.sync(snapshot.clone(), edits);
173 (self.0.snapshot.clone(), edits)
174 }
175
176 pub(crate) fn unfold<T: ToOffset>(
177 &mut self,
178 ranges: impl IntoIterator<Item = Range<T>>,
179 inclusive: bool,
180 ) -> (FoldSnapshot, Vec<FoldEdit>) {
181 let mut edits = Vec::new();
182 let mut fold_ixs_to_delete = Vec::new();
183 let snapshot = self.0.snapshot.inlay_snapshot.clone();
184 let buffer = &snapshot.buffer;
185 for range in ranges.into_iter() {
186 // Remove intersecting folds and add their ranges to edits that are passed to sync.
187 let mut folds_cursor =
188 intersecting_folds(&snapshot, &self.0.snapshot.folds, range, inclusive);
189 while let Some(fold) = folds_cursor.item() {
190 let offset_range =
191 fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer);
192 if offset_range.end > offset_range.start {
193 let inlay_range = snapshot.to_inlay_offset(offset_range.start)
194 ..snapshot.to_inlay_offset(offset_range.end);
195 edits.push(InlayEdit {
196 old: inlay_range.clone(),
197 new: inlay_range,
198 });
199 }
200 fold_ixs_to_delete.push(*folds_cursor.start());
201 folds_cursor.next(buffer);
202 }
203 }
204
205 fold_ixs_to_delete.sort_unstable();
206 fold_ixs_to_delete.dedup();
207
208 self.0.snapshot.folds = {
209 let mut cursor = self.0.snapshot.folds.cursor::<usize>(buffer);
210 let mut folds = SumTree::new(buffer);
211 for fold_ix in fold_ixs_to_delete {
212 folds.append(cursor.slice(&fold_ix, Bias::Right, buffer), buffer);
213 cursor.next(buffer);
214 }
215 folds.append(cursor.suffix(buffer), buffer);
216 folds
217 };
218
219 let edits = consolidate_inlay_edits(edits);
220 let edits = self.0.sync(snapshot.clone(), edits);
221 (self.0.snapshot.clone(), edits)
222 }
223}
224
225/// Decides where the fold indicators should be; also tracks parts of a source file that are currently folded.
226///
227/// See the [`display_map` module documentation](crate::display_map) for more information.
228pub(crate) struct FoldMap {
229 snapshot: FoldSnapshot,
230 next_fold_id: FoldId,
231}
232
233impl FoldMap {
234 pub(crate) fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) {
235 let this = Self {
236 snapshot: FoldSnapshot {
237 folds: SumTree::new(&inlay_snapshot.buffer),
238 transforms: SumTree::from_item(
239 Transform {
240 summary: TransformSummary {
241 input: inlay_snapshot.text_summary(),
242 output: inlay_snapshot.text_summary(),
243 },
244 placeholder: None,
245 },
246 &(),
247 ),
248 inlay_snapshot: inlay_snapshot.clone(),
249 version: 0,
250 },
251 next_fold_id: FoldId::default(),
252 };
253 let snapshot = this.snapshot.clone();
254 (this, snapshot)
255 }
256
257 pub fn read(
258 &mut self,
259 inlay_snapshot: InlaySnapshot,
260 edits: Vec<InlayEdit>,
261 ) -> (FoldSnapshot, Vec<FoldEdit>) {
262 let edits = self.sync(inlay_snapshot, edits);
263 self.check_invariants();
264 (self.snapshot.clone(), edits)
265 }
266
267 pub fn write(
268 &mut self,
269 inlay_snapshot: InlaySnapshot,
270 edits: Vec<InlayEdit>,
271 ) -> (FoldMapWriter, FoldSnapshot, Vec<FoldEdit>) {
272 let (snapshot, edits) = self.read(inlay_snapshot, edits);
273 (FoldMapWriter(self), snapshot, edits)
274 }
275
276 fn check_invariants(&self) {
277 if cfg!(test) {
278 assert_eq!(
279 self.snapshot.transforms.summary().input.len,
280 self.snapshot.inlay_snapshot.len().0,
281 "transform tree does not match inlay snapshot's length"
282 );
283
284 let mut prev_transform_isomorphic = false;
285 for transform in self.snapshot.transforms.iter() {
286 if !transform.is_fold() && prev_transform_isomorphic {
287 panic!(
288 "found adjacent isomorphic transforms: {:?}",
289 self.snapshot.transforms.items(&())
290 );
291 }
292 prev_transform_isomorphic = !transform.is_fold();
293 }
294
295 let mut folds = self.snapshot.folds.iter().peekable();
296 while let Some(fold) = folds.next() {
297 if let Some(next_fold) = folds.peek() {
298 let comparison = fold
299 .range
300 .cmp(&next_fold.range, &self.snapshot.inlay_snapshot.buffer);
301 assert!(comparison.is_le());
302 }
303 }
304 }
305 }
306
307 fn sync(
308 &mut self,
309 inlay_snapshot: InlaySnapshot,
310 inlay_edits: Vec<InlayEdit>,
311 ) -> Vec<FoldEdit> {
312 if inlay_edits.is_empty() {
313 if self.snapshot.inlay_snapshot.version != inlay_snapshot.version {
314 self.snapshot.version += 1;
315 }
316 self.snapshot.inlay_snapshot = inlay_snapshot;
317 Vec::new()
318 } else {
319 let mut inlay_edits_iter = inlay_edits.iter().cloned().peekable();
320
321 let mut new_transforms = SumTree::<Transform>::default();
322 let mut cursor = self.snapshot.transforms.cursor::<InlayOffset>(&());
323 cursor.seek(&InlayOffset(0), Bias::Right, &());
324
325 while let Some(mut edit) = inlay_edits_iter.next() {
326 if let Some(item) = cursor.item() {
327 if !item.is_fold() {
328 new_transforms.update_last(
329 |transform| {
330 if !transform.is_fold() {
331 transform.summary.add_summary(&item.summary, &());
332 cursor.next(&());
333 }
334 },
335 &(),
336 );
337 }
338 }
339 new_transforms.append(cursor.slice(&edit.old.start, Bias::Left, &()), &());
340 edit.new.start -= edit.old.start - *cursor.start();
341 edit.old.start = *cursor.start();
342
343 cursor.seek(&edit.old.end, Bias::Right, &());
344 cursor.next(&());
345
346 let mut delta = edit.new_len().0 as isize - edit.old_len().0 as isize;
347 loop {
348 edit.old.end = *cursor.start();
349
350 if let Some(next_edit) = inlay_edits_iter.peek() {
351 if next_edit.old.start > edit.old.end {
352 break;
353 }
354
355 let next_edit = inlay_edits_iter.next().unwrap();
356 delta += next_edit.new_len().0 as isize - next_edit.old_len().0 as isize;
357
358 if next_edit.old.end >= edit.old.end {
359 edit.old.end = next_edit.old.end;
360 cursor.seek(&edit.old.end, Bias::Right, &());
361 cursor.next(&());
362 }
363 } else {
364 break;
365 }
366 }
367
368 edit.new.end =
369 InlayOffset(((edit.new.start + edit.old_len()).0 as isize + delta) as usize);
370
371 let anchor = inlay_snapshot
372 .buffer
373 .anchor_before(inlay_snapshot.to_buffer_offset(edit.new.start));
374 let mut folds_cursor = self
375 .snapshot
376 .folds
377 .cursor::<FoldRange>(&inlay_snapshot.buffer);
378 folds_cursor.seek(
379 &FoldRange(anchor..Anchor::max()),
380 Bias::Left,
381 &inlay_snapshot.buffer,
382 );
383
384 let mut folds = iter::from_fn({
385 let inlay_snapshot = &inlay_snapshot;
386 move || {
387 let item = folds_cursor.item().map(|fold| {
388 let buffer_start = fold.range.start.to_offset(&inlay_snapshot.buffer);
389 let buffer_end = fold.range.end.to_offset(&inlay_snapshot.buffer);
390 (
391 fold.clone(),
392 inlay_snapshot.to_inlay_offset(buffer_start)
393 ..inlay_snapshot.to_inlay_offset(buffer_end),
394 )
395 });
396 folds_cursor.next(&inlay_snapshot.buffer);
397 item
398 }
399 })
400 .peekable();
401
402 while folds
403 .peek()
404 .map_or(false, |(_, fold_range)| fold_range.start < edit.new.end)
405 {
406 let (fold, mut fold_range) = folds.next().unwrap();
407 let sum = new_transforms.summary();
408
409 assert!(fold_range.start.0 >= sum.input.len);
410
411 while folds.peek().map_or(false, |(next_fold, next_fold_range)| {
412 next_fold_range.start < fold_range.end
413 || (next_fold_range.start == fold_range.end
414 && fold.placeholder.merge_adjacent
415 && next_fold.placeholder.merge_adjacent)
416 }) {
417 let (_, next_fold_range) = folds.next().unwrap();
418 if next_fold_range.end > fold_range.end {
419 fold_range.end = next_fold_range.end;
420 }
421 }
422
423 if fold_range.start.0 > sum.input.len {
424 let text_summary = inlay_snapshot
425 .text_summary_for_range(InlayOffset(sum.input.len)..fold_range.start);
426 push_isomorphic(&mut new_transforms, text_summary);
427 }
428
429 if fold_range.end > fold_range.start {
430 const ELLIPSIS: &str = "⋯";
431
432 let fold_id = fold.id;
433 new_transforms.push(
434 Transform {
435 summary: TransformSummary {
436 output: TextSummary::from(ELLIPSIS),
437 input: inlay_snapshot
438 .text_summary_for_range(fold_range.start..fold_range.end),
439 },
440 placeholder: Some(TransformPlaceholder {
441 text: ELLIPSIS,
442 renderer: ChunkRenderer {
443 render: Arc::new(move |cx| {
444 (fold.placeholder.render)(
445 fold_id,
446 fold.range.0.clone(),
447 cx,
448 )
449 }),
450 constrain_width: fold.placeholder.constrain_width,
451 },
452 }),
453 },
454 &(),
455 );
456 }
457 }
458
459 let sum = new_transforms.summary();
460 if sum.input.len < edit.new.end.0 {
461 let text_summary = inlay_snapshot
462 .text_summary_for_range(InlayOffset(sum.input.len)..edit.new.end);
463 push_isomorphic(&mut new_transforms, text_summary);
464 }
465 }
466
467 new_transforms.append(cursor.suffix(&()), &());
468 if new_transforms.is_empty() {
469 let text_summary = inlay_snapshot.text_summary();
470 push_isomorphic(&mut new_transforms, text_summary);
471 }
472
473 drop(cursor);
474
475 let mut fold_edits = Vec::with_capacity(inlay_edits.len());
476 {
477 let mut old_transforms = self
478 .snapshot
479 .transforms
480 .cursor::<(InlayOffset, FoldOffset)>(&());
481 let mut new_transforms = new_transforms.cursor::<(InlayOffset, FoldOffset)>(&());
482
483 for mut edit in inlay_edits {
484 old_transforms.seek(&edit.old.start, Bias::Left, &());
485 if old_transforms.item().map_or(false, |t| t.is_fold()) {
486 edit.old.start = old_transforms.start().0;
487 }
488 let old_start =
489 old_transforms.start().1 .0 + (edit.old.start - old_transforms.start().0).0;
490
491 old_transforms.seek_forward(&edit.old.end, Bias::Right, &());
492 if old_transforms.item().map_or(false, |t| t.is_fold()) {
493 old_transforms.next(&());
494 edit.old.end = old_transforms.start().0;
495 }
496 let old_end =
497 old_transforms.start().1 .0 + (edit.old.end - old_transforms.start().0).0;
498
499 new_transforms.seek(&edit.new.start, Bias::Left, &());
500 if new_transforms.item().map_or(false, |t| t.is_fold()) {
501 edit.new.start = new_transforms.start().0;
502 }
503 let new_start =
504 new_transforms.start().1 .0 + (edit.new.start - new_transforms.start().0).0;
505
506 new_transforms.seek_forward(&edit.new.end, Bias::Right, &());
507 if new_transforms.item().map_or(false, |t| t.is_fold()) {
508 new_transforms.next(&());
509 edit.new.end = new_transforms.start().0;
510 }
511 let new_end =
512 new_transforms.start().1 .0 + (edit.new.end - new_transforms.start().0).0;
513
514 fold_edits.push(FoldEdit {
515 old: FoldOffset(old_start)..FoldOffset(old_end),
516 new: FoldOffset(new_start)..FoldOffset(new_end),
517 });
518 }
519
520 fold_edits = consolidate_fold_edits(fold_edits);
521 }
522
523 self.snapshot.transforms = new_transforms;
524 self.snapshot.inlay_snapshot = inlay_snapshot;
525 self.snapshot.version += 1;
526 fold_edits
527 }
528 }
529}
530
531#[derive(Clone)]
532pub struct FoldSnapshot {
533 transforms: SumTree<Transform>,
534 folds: SumTree<Fold>,
535 pub inlay_snapshot: InlaySnapshot,
536 pub version: usize,
537}
538
539impl FoldSnapshot {
540 #[cfg(test)]
541 pub fn text(&self) -> String {
542 self.chunks(FoldOffset(0)..self.len(), false, Highlights::default())
543 .map(|c| c.text)
544 .collect()
545 }
546
547 #[cfg(test)]
548 pub fn fold_count(&self) -> usize {
549 self.folds.items(&self.inlay_snapshot.buffer).len()
550 }
551
552 pub fn text_summary_for_range(&self, range: Range<FoldPoint>) -> TextSummary {
553 let mut summary = TextSummary::default();
554
555 let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&());
556 cursor.seek(&range.start, Bias::Right, &());
557 if let Some(transform) = cursor.item() {
558 let start_in_transform = range.start.0 - cursor.start().0 .0;
559 let end_in_transform = cmp::min(range.end, cursor.end(&()).0).0 - cursor.start().0 .0;
560 if let Some(placeholder) = transform.placeholder.as_ref() {
561 summary = TextSummary::from(
562 &placeholder.text
563 [start_in_transform.column as usize..end_in_transform.column as usize],
564 );
565 } else {
566 let inlay_start = self
567 .inlay_snapshot
568 .to_offset(InlayPoint(cursor.start().1 .0 + start_in_transform));
569 let inlay_end = self
570 .inlay_snapshot
571 .to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform));
572 summary = self
573 .inlay_snapshot
574 .text_summary_for_range(inlay_start..inlay_end);
575 }
576 }
577
578 if range.end > cursor.end(&()).0 {
579 cursor.next(&());
580 summary += &cursor
581 .summary::<_, TransformSummary>(&range.end, Bias::Right, &())
582 .output;
583 if let Some(transform) = cursor.item() {
584 let end_in_transform = range.end.0 - cursor.start().0 .0;
585 if let Some(placeholder) = transform.placeholder.as_ref() {
586 summary +=
587 TextSummary::from(&placeholder.text[..end_in_transform.column as usize]);
588 } else {
589 let inlay_start = self.inlay_snapshot.to_offset(cursor.start().1);
590 let inlay_end = self
591 .inlay_snapshot
592 .to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform));
593 summary += self
594 .inlay_snapshot
595 .text_summary_for_range(inlay_start..inlay_end);
596 }
597 }
598 }
599
600 summary
601 }
602
603 pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint {
604 let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(&());
605 cursor.seek(&point, Bias::Right, &());
606 if cursor.item().map_or(false, |t| t.is_fold()) {
607 if bias == Bias::Left || point == cursor.start().0 {
608 cursor.start().1
609 } else {
610 cursor.end(&()).1
611 }
612 } else {
613 let overshoot = point.0 - cursor.start().0 .0;
614 FoldPoint(cmp::min(
615 cursor.start().1 .0 + overshoot,
616 cursor.end(&()).1 .0,
617 ))
618 }
619 }
620
621 pub fn len(&self) -> FoldOffset {
622 FoldOffset(self.transforms.summary().output.len)
623 }
624
625 pub fn line_len(&self, row: u32) -> u32 {
626 let line_start = FoldPoint::new(row, 0).to_offset(self).0;
627 let line_end = if row >= self.max_point().row() {
628 self.len().0
629 } else {
630 FoldPoint::new(row + 1, 0).to_offset(self).0 - 1
631 };
632 (line_end - line_start) as u32
633 }
634
635 pub fn buffer_rows(&self, start_row: u32) -> FoldBufferRows {
636 if start_row > self.transforms.summary().output.lines.row {
637 panic!("invalid display row {}", start_row);
638 }
639
640 let fold_point = FoldPoint::new(start_row, 0);
641 let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&());
642 cursor.seek(&fold_point, Bias::Left, &());
643
644 let overshoot = fold_point.0 - cursor.start().0 .0;
645 let inlay_point = InlayPoint(cursor.start().1 .0 + overshoot);
646 let input_buffer_rows = self.inlay_snapshot.buffer_rows(inlay_point.row());
647
648 FoldBufferRows {
649 fold_point,
650 input_buffer_rows,
651 cursor,
652 }
653 }
654
655 pub fn max_point(&self) -> FoldPoint {
656 FoldPoint(self.transforms.summary().output.lines)
657 }
658
659 #[cfg(test)]
660 pub fn longest_row(&self) -> u32 {
661 self.transforms.summary().output.longest_row
662 }
663
664 pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Fold>
665 where
666 T: ToOffset,
667 {
668 let mut folds = intersecting_folds(&self.inlay_snapshot, &self.folds, range, false);
669 iter::from_fn(move || {
670 let item = folds.item();
671 folds.next(&self.inlay_snapshot.buffer);
672 item
673 })
674 }
675
676 pub fn intersects_fold<T>(&self, offset: T) -> bool
677 where
678 T: ToOffset,
679 {
680 let buffer_offset = offset.to_offset(&self.inlay_snapshot.buffer);
681 let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset);
682 let mut cursor = self.transforms.cursor::<InlayOffset>(&());
683 cursor.seek(&inlay_offset, Bias::Right, &());
684 cursor.item().map_or(false, |t| t.placeholder.is_some())
685 }
686
687 pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
688 let mut inlay_point = self
689 .inlay_snapshot
690 .to_inlay_point(Point::new(buffer_row.0, 0));
691 let mut cursor = self.transforms.cursor::<InlayPoint>(&());
692 cursor.seek(&inlay_point, Bias::Right, &());
693 loop {
694 match cursor.item() {
695 Some(transform) => {
696 let buffer_point = self.inlay_snapshot.to_buffer_point(inlay_point);
697 if buffer_point.row != buffer_row.0 {
698 return false;
699 } else if transform.placeholder.is_some() {
700 return true;
701 }
702 }
703 None => return false,
704 }
705
706 if cursor.end(&()).row() == inlay_point.row() {
707 cursor.next(&());
708 } else {
709 inlay_point.0 += Point::new(1, 0);
710 cursor.seek(&inlay_point, Bias::Right, &());
711 }
712 }
713 }
714
715 pub(crate) fn chunks<'a>(
716 &'a self,
717 range: Range<FoldOffset>,
718 language_aware: bool,
719 highlights: Highlights<'a>,
720 ) -> FoldChunks<'a> {
721 let mut transform_cursor = self.transforms.cursor::<(FoldOffset, InlayOffset)>(&());
722 transform_cursor.seek(&range.start, Bias::Right, &());
723
724 let inlay_start = {
725 let overshoot = range.start.0 - transform_cursor.start().0 .0;
726 transform_cursor.start().1 + InlayOffset(overshoot)
727 };
728
729 let transform_end = transform_cursor.end(&());
730
731 let inlay_end = if transform_cursor
732 .item()
733 .map_or(true, |transform| transform.is_fold())
734 {
735 inlay_start
736 } else if range.end < transform_end.0 {
737 let overshoot = range.end.0 - transform_cursor.start().0 .0;
738 transform_cursor.start().1 + InlayOffset(overshoot)
739 } else {
740 transform_end.1
741 };
742
743 FoldChunks {
744 transform_cursor,
745 inlay_chunks: self.inlay_snapshot.chunks(
746 inlay_start..inlay_end,
747 language_aware,
748 highlights,
749 ),
750 inlay_chunk: None,
751 inlay_offset: inlay_start,
752 output_offset: range.start,
753 max_output_offset: range.end,
754 }
755 }
756
757 pub fn chars_at(&self, start: FoldPoint) -> impl '_ + Iterator<Item = char> {
758 self.chunks(
759 start.to_offset(self)..self.len(),
760 false,
761 Highlights::default(),
762 )
763 .flat_map(|chunk| chunk.text.chars())
764 }
765
766 #[cfg(test)]
767 pub fn clip_offset(&self, offset: FoldOffset, bias: Bias) -> FoldOffset {
768 if offset > self.len() {
769 self.len()
770 } else {
771 self.clip_point(offset.to_point(self), bias).to_offset(self)
772 }
773 }
774
775 pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint {
776 let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&());
777 cursor.seek(&point, Bias::Right, &());
778 if let Some(transform) = cursor.item() {
779 let transform_start = cursor.start().0 .0;
780 if transform.placeholder.is_some() {
781 if point.0 == transform_start || matches!(bias, Bias::Left) {
782 FoldPoint(transform_start)
783 } else {
784 FoldPoint(cursor.end(&()).0 .0)
785 }
786 } else {
787 let overshoot = InlayPoint(point.0 - transform_start);
788 let inlay_point = cursor.start().1 + overshoot;
789 let clipped_inlay_point = self.inlay_snapshot.clip_point(inlay_point, bias);
790 FoldPoint(cursor.start().0 .0 + (clipped_inlay_point - cursor.start().1).0)
791 }
792 } else {
793 FoldPoint(self.transforms.summary().output.lines)
794 }
795 }
796}
797
798fn push_isomorphic(transforms: &mut SumTree<Transform>, summary: TextSummary) {
799 let mut did_merge = false;
800 transforms.update_last(
801 |last| {
802 if !last.is_fold() {
803 last.summary.input += summary.clone();
804 last.summary.output += summary.clone();
805 did_merge = true;
806 }
807 },
808 &(),
809 );
810 if !did_merge {
811 transforms.push(
812 Transform {
813 summary: TransformSummary {
814 input: summary.clone(),
815 output: summary,
816 },
817 placeholder: None,
818 },
819 &(),
820 )
821 }
822}
823
824fn intersecting_folds<'a, T>(
825 inlay_snapshot: &'a InlaySnapshot,
826 folds: &'a SumTree<Fold>,
827 range: Range<T>,
828 inclusive: bool,
829) -> FilterCursor<'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, usize>
830where
831 T: ToOffset,
832{
833 let buffer = &inlay_snapshot.buffer;
834 let start = buffer.anchor_before(range.start.to_offset(buffer));
835 let end = buffer.anchor_after(range.end.to_offset(buffer));
836 let mut cursor = folds.filter::<_, usize>(buffer, move |summary| {
837 let start_cmp = start.cmp(&summary.max_end, buffer);
838 let end_cmp = end.cmp(&summary.min_start, buffer);
839
840 if inclusive {
841 start_cmp <= Ordering::Equal && end_cmp >= Ordering::Equal
842 } else {
843 start_cmp == Ordering::Less && end_cmp == Ordering::Greater
844 }
845 });
846 cursor.next(buffer);
847 cursor
848}
849
850fn consolidate_inlay_edits(mut edits: Vec<InlayEdit>) -> Vec<InlayEdit> {
851 edits.sort_unstable_by(|a, b| {
852 a.old
853 .start
854 .cmp(&b.old.start)
855 .then_with(|| b.old.end.cmp(&a.old.end))
856 });
857
858 let _old_alloc_ptr = edits.as_ptr();
859 let mut inlay_edits = edits.into_iter();
860
861 if let Some(mut first_edit) = inlay_edits.next() {
862 // This code relies on reusing allocations from the Vec<_> - at the time of writing .flatten() prevents them.
863 #[allow(clippy::filter_map_identity)]
864 let mut v: Vec<_> = inlay_edits
865 .scan(&mut first_edit, |prev_edit, edit| {
866 if prev_edit.old.end >= edit.old.start {
867 prev_edit.old.end = prev_edit.old.end.max(edit.old.end);
868 prev_edit.new.start = prev_edit.new.start.min(edit.new.start);
869 prev_edit.new.end = prev_edit.new.end.max(edit.new.end);
870 Some(None) // Skip this edit, it's merged
871 } else {
872 let prev = std::mem::replace(*prev_edit, edit);
873 Some(Some(prev)) // Yield the previous edit
874 }
875 })
876 .filter_map(|x| x)
877 .collect();
878 v.push(first_edit.clone());
879 debug_assert_eq!(_old_alloc_ptr, v.as_ptr(), "Inlay edits were reallocated");
880 v
881 } else {
882 vec![]
883 }
884}
885
886fn consolidate_fold_edits(mut edits: Vec<FoldEdit>) -> Vec<FoldEdit> {
887 edits.sort_unstable_by(|a, b| {
888 a.old
889 .start
890 .cmp(&b.old.start)
891 .then_with(|| b.old.end.cmp(&a.old.end))
892 });
893 let _old_alloc_ptr = edits.as_ptr();
894 let mut fold_edits = edits.into_iter();
895
896 if let Some(mut first_edit) = fold_edits.next() {
897 // This code relies on reusing allocations from the Vec<_> - at the time of writing .flatten() prevents them.
898 #[allow(clippy::filter_map_identity)]
899 let mut v: Vec<_> = fold_edits
900 .scan(&mut first_edit, |prev_edit, edit| {
901 if prev_edit.old.end >= edit.old.start {
902 prev_edit.old.end = prev_edit.old.end.max(edit.old.end);
903 prev_edit.new.start = prev_edit.new.start.min(edit.new.start);
904 prev_edit.new.end = prev_edit.new.end.max(edit.new.end);
905 Some(None) // Skip this edit, it's merged
906 } else {
907 let prev = std::mem::replace(*prev_edit, edit);
908 Some(Some(prev)) // Yield the previous edit
909 }
910 })
911 .filter_map(|x| x)
912 .collect();
913 v.push(first_edit.clone());
914 v
915 } else {
916 vec![]
917 }
918}
919
920#[derive(Clone, Debug, Default)]
921struct Transform {
922 summary: TransformSummary,
923 placeholder: Option<TransformPlaceholder>,
924}
925
926#[derive(Clone, Debug)]
927struct TransformPlaceholder {
928 text: &'static str,
929 renderer: ChunkRenderer,
930}
931
932impl Transform {
933 fn is_fold(&self) -> bool {
934 self.placeholder.is_some()
935 }
936}
937
938#[derive(Clone, Debug, Default, Eq, PartialEq)]
939struct TransformSummary {
940 output: TextSummary,
941 input: TextSummary,
942}
943
944impl sum_tree::Item for Transform {
945 type Summary = TransformSummary;
946
947 fn summary(&self, _cx: &()) -> Self::Summary {
948 self.summary.clone()
949 }
950}
951
952impl sum_tree::Summary for TransformSummary {
953 type Context = ();
954
955 fn zero(_cx: &()) -> Self {
956 Default::default()
957 }
958
959 fn add_summary(&mut self, other: &Self, _: &()) {
960 self.input += &other.input;
961 self.output += &other.output;
962 }
963}
964
965#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
966pub struct FoldId(usize);
967
968impl From<FoldId> for ElementId {
969 fn from(val: FoldId) -> Self {
970 ElementId::Integer(val.0)
971 }
972}
973
974#[derive(Clone, Debug, Eq, PartialEq)]
975pub struct Fold {
976 pub id: FoldId,
977 pub range: FoldRange,
978 pub placeholder: FoldPlaceholder,
979}
980
981#[derive(Clone, Debug, Eq, PartialEq)]
982pub struct FoldRange(Range<Anchor>);
983
984impl Deref for FoldRange {
985 type Target = Range<Anchor>;
986
987 fn deref(&self) -> &Self::Target {
988 &self.0
989 }
990}
991
992impl DerefMut for FoldRange {
993 fn deref_mut(&mut self) -> &mut Self::Target {
994 &mut self.0
995 }
996}
997
998impl Default for FoldRange {
999 fn default() -> Self {
1000 Self(Anchor::min()..Anchor::max())
1001 }
1002}
1003
1004impl sum_tree::Item for Fold {
1005 type Summary = FoldSummary;
1006
1007 fn summary(&self, _cx: &MultiBufferSnapshot) -> Self::Summary {
1008 FoldSummary {
1009 start: self.range.start,
1010 end: self.range.end,
1011 min_start: self.range.start,
1012 max_end: self.range.end,
1013 count: 1,
1014 }
1015 }
1016}
1017
1018#[derive(Clone, Debug)]
1019pub struct FoldSummary {
1020 start: Anchor,
1021 end: Anchor,
1022 min_start: Anchor,
1023 max_end: Anchor,
1024 count: usize,
1025}
1026
1027impl Default for FoldSummary {
1028 fn default() -> Self {
1029 Self {
1030 start: Anchor::min(),
1031 end: Anchor::max(),
1032 min_start: Anchor::max(),
1033 max_end: Anchor::min(),
1034 count: 0,
1035 }
1036 }
1037}
1038
1039impl sum_tree::Summary for FoldSummary {
1040 type Context = MultiBufferSnapshot;
1041
1042 fn zero(_cx: &MultiBufferSnapshot) -> Self {
1043 Default::default()
1044 }
1045
1046 fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
1047 if other.min_start.cmp(&self.min_start, buffer) == Ordering::Less {
1048 self.min_start = other.min_start;
1049 }
1050 if other.max_end.cmp(&self.max_end, buffer) == Ordering::Greater {
1051 self.max_end = other.max_end;
1052 }
1053
1054 #[cfg(debug_assertions)]
1055 {
1056 let start_comparison = self.start.cmp(&other.start, buffer);
1057 assert!(start_comparison <= Ordering::Equal);
1058 if start_comparison == Ordering::Equal {
1059 assert!(self.end.cmp(&other.end, buffer) >= Ordering::Equal);
1060 }
1061 }
1062
1063 self.start = other.start;
1064 self.end = other.end;
1065 self.count += other.count;
1066 }
1067}
1068
1069impl<'a> sum_tree::Dimension<'a, FoldSummary> for FoldRange {
1070 fn zero(_cx: &MultiBufferSnapshot) -> Self {
1071 Default::default()
1072 }
1073
1074 fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) {
1075 self.0.start = summary.start;
1076 self.0.end = summary.end;
1077 }
1078}
1079
1080impl<'a> sum_tree::SeekTarget<'a, FoldSummary, FoldRange> for FoldRange {
1081 fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering {
1082 AnchorRangeExt::cmp(&self.0, &other.0, buffer)
1083 }
1084}
1085
1086impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
1087 fn zero(_cx: &MultiBufferSnapshot) -> Self {
1088 Default::default()
1089 }
1090
1091 fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) {
1092 *self += summary.count;
1093 }
1094}
1095
1096#[derive(Clone)]
1097pub struct FoldBufferRows<'a> {
1098 cursor: Cursor<'a, Transform, (FoldPoint, InlayPoint)>,
1099 input_buffer_rows: InlayBufferRows<'a>,
1100 fold_point: FoldPoint,
1101}
1102
1103impl<'a> Iterator for FoldBufferRows<'a> {
1104 type Item = Option<u32>;
1105
1106 fn next(&mut self) -> Option<Self::Item> {
1107 let mut traversed_fold = false;
1108 while self.fold_point > self.cursor.end(&()).0 {
1109 self.cursor.next(&());
1110 traversed_fold = true;
1111 if self.cursor.item().is_none() {
1112 break;
1113 }
1114 }
1115
1116 if self.cursor.item().is_some() {
1117 if traversed_fold {
1118 self.input_buffer_rows.seek(self.cursor.start().1.row());
1119 self.input_buffer_rows.next();
1120 }
1121 *self.fold_point.row_mut() += 1;
1122 self.input_buffer_rows.next()
1123 } else {
1124 None
1125 }
1126 }
1127}
1128
1129pub struct FoldChunks<'a> {
1130 transform_cursor: Cursor<'a, Transform, (FoldOffset, InlayOffset)>,
1131 inlay_chunks: InlayChunks<'a>,
1132 inlay_chunk: Option<(InlayOffset, Chunk<'a>)>,
1133 inlay_offset: InlayOffset,
1134 output_offset: FoldOffset,
1135 max_output_offset: FoldOffset,
1136}
1137
1138impl<'a> Iterator for FoldChunks<'a> {
1139 type Item = Chunk<'a>;
1140
1141 fn next(&mut self) -> Option<Self::Item> {
1142 if self.output_offset >= self.max_output_offset {
1143 return None;
1144 }
1145
1146 let transform = self.transform_cursor.item()?;
1147
1148 // If we're in a fold, then return the fold's display text and
1149 // advance the transform and buffer cursors to the end of the fold.
1150 if let Some(placeholder) = transform.placeholder.as_ref() {
1151 self.inlay_chunk.take();
1152 self.inlay_offset += InlayOffset(transform.summary.input.len);
1153
1154 while self.inlay_offset >= self.transform_cursor.end(&()).1
1155 && self.transform_cursor.item().is_some()
1156 {
1157 self.transform_cursor.next(&());
1158 }
1159
1160 self.output_offset.0 += placeholder.text.len();
1161 return Some(Chunk {
1162 text: placeholder.text,
1163 renderer: Some(placeholder.renderer.clone()),
1164 ..Default::default()
1165 });
1166 }
1167
1168 // When we reach a non-fold region, seek the underlying text
1169 // chunk iterator to the next unfolded range.
1170 if self.inlay_offset == self.transform_cursor.start().1
1171 && self.inlay_chunks.offset() != self.inlay_offset
1172 {
1173 let transform_start = self.transform_cursor.start();
1174 let transform_end = self.transform_cursor.end(&());
1175 let inlay_end = if self.max_output_offset < transform_end.0 {
1176 let overshoot = self.max_output_offset.0 - transform_start.0 .0;
1177 transform_start.1 + InlayOffset(overshoot)
1178 } else {
1179 transform_end.1
1180 };
1181
1182 self.inlay_chunks.seek(self.inlay_offset..inlay_end);
1183 }
1184
1185 // Retrieve a chunk from the current location in the buffer.
1186 if self.inlay_chunk.is_none() {
1187 let chunk_offset = self.inlay_chunks.offset();
1188 self.inlay_chunk = self.inlay_chunks.next().map(|chunk| (chunk_offset, chunk));
1189 }
1190
1191 // Otherwise, take a chunk from the buffer's text.
1192 if let Some((buffer_chunk_start, mut chunk)) = self.inlay_chunk.clone() {
1193 let buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len());
1194 let transform_end = self.transform_cursor.end(&()).1;
1195 let chunk_end = buffer_chunk_end.min(transform_end);
1196
1197 chunk.text = &chunk.text
1198 [(self.inlay_offset - buffer_chunk_start).0..(chunk_end - buffer_chunk_start).0];
1199
1200 if chunk_end == transform_end {
1201 self.transform_cursor.next(&());
1202 } else if chunk_end == buffer_chunk_end {
1203 self.inlay_chunk.take();
1204 }
1205
1206 self.inlay_offset = chunk_end;
1207 self.output_offset.0 += chunk.text.len();
1208 return Some(chunk);
1209 }
1210
1211 None
1212 }
1213}
1214
1215#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
1216pub struct FoldOffset(pub usize);
1217
1218impl FoldOffset {
1219 pub fn to_point(self, snapshot: &FoldSnapshot) -> FoldPoint {
1220 let mut cursor = snapshot
1221 .transforms
1222 .cursor::<(FoldOffset, TransformSummary)>(&());
1223 cursor.seek(&self, Bias::Right, &());
1224 let overshoot = if cursor.item().map_or(true, |t| t.is_fold()) {
1225 Point::new(0, (self.0 - cursor.start().0 .0) as u32)
1226 } else {
1227 let inlay_offset = cursor.start().1.input.len + self.0 - cursor.start().0 .0;
1228 let inlay_point = snapshot.inlay_snapshot.to_point(InlayOffset(inlay_offset));
1229 inlay_point.0 - cursor.start().1.input.lines
1230 };
1231 FoldPoint(cursor.start().1.output.lines + overshoot)
1232 }
1233
1234 #[cfg(test)]
1235 pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset {
1236 let mut cursor = snapshot.transforms.cursor::<(FoldOffset, InlayOffset)>(&());
1237 cursor.seek(&self, Bias::Right, &());
1238 let overshoot = self.0 - cursor.start().0 .0;
1239 InlayOffset(cursor.start().1 .0 + overshoot)
1240 }
1241}
1242
1243impl Add for FoldOffset {
1244 type Output = Self;
1245
1246 fn add(self, rhs: Self) -> Self::Output {
1247 Self(self.0 + rhs.0)
1248 }
1249}
1250
1251impl AddAssign for FoldOffset {
1252 fn add_assign(&mut self, rhs: Self) {
1253 self.0 += rhs.0;
1254 }
1255}
1256
1257impl Sub for FoldOffset {
1258 type Output = Self;
1259
1260 fn sub(self, rhs: Self) -> Self::Output {
1261 Self(self.0 - rhs.0)
1262 }
1263}
1264
1265impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset {
1266 fn zero(_cx: &()) -> Self {
1267 Default::default()
1268 }
1269
1270 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1271 self.0 += &summary.output.len;
1272 }
1273}
1274
1275impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
1276 fn zero(_cx: &()) -> Self {
1277 Default::default()
1278 }
1279
1280 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1281 self.0 += &summary.input.lines;
1282 }
1283}
1284
1285impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
1286 fn zero(_cx: &()) -> Self {
1287 Default::default()
1288 }
1289
1290 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1291 self.0 += &summary.input.len;
1292 }
1293}
1294
1295pub type FoldEdit = Edit<FoldOffset>;
1296
1297#[cfg(test)]
1298mod tests {
1299 use super::*;
1300 use crate::{display_map::inlay_map::InlayMap, MultiBuffer, ToPoint};
1301 use collections::HashSet;
1302 use rand::prelude::*;
1303 use settings::SettingsStore;
1304 use std::{env, mem};
1305 use text::Patch;
1306 use util::test::sample_text;
1307 use util::RandomCharIter;
1308 use Bias::{Left, Right};
1309
1310 #[gpui::test]
1311 fn test_basic_folds(cx: &mut gpui::AppContext) {
1312 init_test(cx);
1313 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1314 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1315 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1316 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1317 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1318
1319 let (mut writer, _, _) = map.write(inlay_snapshot, vec![]);
1320 let (snapshot2, edits) = writer.fold(vec![
1321 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1322 (Point::new(2, 4)..Point::new(4, 1), FoldPlaceholder::test()),
1323 ]);
1324 assert_eq!(snapshot2.text(), "aa⋯cc⋯eeeee");
1325 assert_eq!(
1326 edits,
1327 &[
1328 FoldEdit {
1329 old: FoldOffset(2)..FoldOffset(16),
1330 new: FoldOffset(2)..FoldOffset(5),
1331 },
1332 FoldEdit {
1333 old: FoldOffset(18)..FoldOffset(29),
1334 new: FoldOffset(7)..FoldOffset(10)
1335 },
1336 ]
1337 );
1338
1339 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1340 buffer.edit(
1341 vec![
1342 (Point::new(0, 0)..Point::new(0, 1), "123"),
1343 (Point::new(2, 3)..Point::new(2, 3), "123"),
1344 ],
1345 None,
1346 cx,
1347 );
1348 buffer.snapshot(cx)
1349 });
1350
1351 let (inlay_snapshot, inlay_edits) =
1352 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1353 let (snapshot3, edits) = map.read(inlay_snapshot, inlay_edits);
1354 assert_eq!(snapshot3.text(), "123a⋯c123c⋯eeeee");
1355 assert_eq!(
1356 edits,
1357 &[
1358 FoldEdit {
1359 old: FoldOffset(0)..FoldOffset(1),
1360 new: FoldOffset(0)..FoldOffset(3),
1361 },
1362 FoldEdit {
1363 old: FoldOffset(6)..FoldOffset(6),
1364 new: FoldOffset(8)..FoldOffset(11),
1365 },
1366 ]
1367 );
1368
1369 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1370 buffer.edit([(Point::new(2, 6)..Point::new(4, 3), "456")], None, cx);
1371 buffer.snapshot(cx)
1372 });
1373 let (inlay_snapshot, inlay_edits) =
1374 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1375 let (snapshot4, _) = map.read(inlay_snapshot.clone(), inlay_edits);
1376 assert_eq!(snapshot4.text(), "123a⋯c123456eee");
1377
1378 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1379 writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), false);
1380 let (snapshot5, _) = map.read(inlay_snapshot.clone(), vec![]);
1381 assert_eq!(snapshot5.text(), "123a⋯c123456eee");
1382
1383 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1384 writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), true);
1385 let (snapshot6, _) = map.read(inlay_snapshot, vec![]);
1386 assert_eq!(snapshot6.text(), "123aaaaa\nbbbbbb\nccc123456eee");
1387 }
1388
1389 #[gpui::test]
1390 fn test_adjacent_folds(cx: &mut gpui::AppContext) {
1391 init_test(cx);
1392 let buffer = MultiBuffer::build_simple("abcdefghijkl", cx);
1393 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1394 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1395 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1396
1397 {
1398 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1399
1400 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1401 writer.fold(vec![(5..8, FoldPlaceholder::test())]);
1402 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1403 assert_eq!(snapshot.text(), "abcde⋯ijkl");
1404
1405 // Create an fold adjacent to the start of the first fold.
1406 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1407 writer.fold(vec![
1408 (0..1, FoldPlaceholder::test()),
1409 (2..5, FoldPlaceholder::test()),
1410 ]);
1411 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1412 assert_eq!(snapshot.text(), "⋯b⋯ijkl");
1413
1414 // Create an fold adjacent to the end of the first fold.
1415 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1416 writer.fold(vec![
1417 (11..11, FoldPlaceholder::test()),
1418 (8..10, FoldPlaceholder::test()),
1419 ]);
1420 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1421 assert_eq!(snapshot.text(), "⋯b⋯kl");
1422 }
1423
1424 {
1425 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1426
1427 // Create two adjacent folds.
1428 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1429 writer.fold(vec![
1430 (0..2, FoldPlaceholder::test()),
1431 (2..5, FoldPlaceholder::test()),
1432 ]);
1433 let (snapshot, _) = map.read(inlay_snapshot, vec![]);
1434 assert_eq!(snapshot.text(), "⋯fghijkl");
1435
1436 // Edit within one of the folds.
1437 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1438 buffer.edit([(0..1, "12345")], None, cx);
1439 buffer.snapshot(cx)
1440 });
1441 let (inlay_snapshot, inlay_edits) =
1442 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1443 let (snapshot, _) = map.read(inlay_snapshot, inlay_edits);
1444 assert_eq!(snapshot.text(), "12345⋯fghijkl");
1445 }
1446 }
1447
1448 #[gpui::test]
1449 fn test_overlapping_folds(cx: &mut gpui::AppContext) {
1450 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1451 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1452 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
1453 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1454 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1455 writer.fold(vec![
1456 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1457 (Point::new(0, 4)..Point::new(1, 0), FoldPlaceholder::test()),
1458 (Point::new(1, 2)..Point::new(3, 2), FoldPlaceholder::test()),
1459 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
1460 ]);
1461 let (snapshot, _) = map.read(inlay_snapshot, vec![]);
1462 assert_eq!(snapshot.text(), "aa⋯eeeee");
1463 }
1464
1465 #[gpui::test]
1466 fn test_merging_folds_via_edit(cx: &mut gpui::AppContext) {
1467 init_test(cx);
1468 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1469 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1470 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1471 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1472 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1473
1474 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1475 writer.fold(vec![
1476 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1477 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
1478 ]);
1479 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1480 assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee");
1481
1482 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1483 buffer.edit([(Point::new(2, 2)..Point::new(3, 1), "")], None, cx);
1484 buffer.snapshot(cx)
1485 });
1486 let (inlay_snapshot, inlay_edits) =
1487 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1488 let (snapshot, _) = map.read(inlay_snapshot, inlay_edits);
1489 assert_eq!(snapshot.text(), "aa⋯eeeee");
1490 }
1491
1492 #[gpui::test]
1493 fn test_folds_in_range(cx: &mut gpui::AppContext) {
1494 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1495 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1496 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1497 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1498
1499 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1500 writer.fold(vec![
1501 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1502 (Point::new(0, 4)..Point::new(1, 0), FoldPlaceholder::test()),
1503 (Point::new(1, 2)..Point::new(3, 2), FoldPlaceholder::test()),
1504 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
1505 ]);
1506 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1507 let fold_ranges = snapshot
1508 .folds_in_range(Point::new(1, 0)..Point::new(1, 3))
1509 .map(|fold| {
1510 fold.range.start.to_point(&buffer_snapshot)
1511 ..fold.range.end.to_point(&buffer_snapshot)
1512 })
1513 .collect::<Vec<_>>();
1514 assert_eq!(
1515 fold_ranges,
1516 vec![
1517 Point::new(0, 2)..Point::new(2, 2),
1518 Point::new(1, 2)..Point::new(3, 2)
1519 ]
1520 );
1521 }
1522
1523 #[gpui::test(iterations = 100)]
1524 fn test_random_folds(cx: &mut gpui::AppContext, mut rng: StdRng) {
1525 init_test(cx);
1526 let operations = env::var("OPERATIONS")
1527 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1528 .unwrap_or(10);
1529
1530 let len = rng.gen_range(0..10);
1531 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
1532 let buffer = if rng.gen() {
1533 MultiBuffer::build_simple(&text, cx)
1534 } else {
1535 MultiBuffer::build_random(&mut rng, cx)
1536 };
1537 let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1538 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1539 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1540
1541 let (mut initial_snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1542 let mut snapshot_edits = Vec::new();
1543
1544 let mut next_inlay_id = 0;
1545 for _ in 0..operations {
1546 log::info!("text: {:?}", buffer_snapshot.text());
1547 let mut buffer_edits = Vec::new();
1548 let mut inlay_edits = Vec::new();
1549 match rng.gen_range(0..=100) {
1550 0..=39 => {
1551 snapshot_edits.extend(map.randomly_mutate(&mut rng));
1552 }
1553 40..=59 => {
1554 let (_, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1555 inlay_edits = edits;
1556 }
1557 _ => buffer.update(cx, |buffer, cx| {
1558 let subscription = buffer.subscribe();
1559 let edit_count = rng.gen_range(1..=5);
1560 buffer.randomly_mutate(&mut rng, edit_count, cx);
1561 buffer_snapshot = buffer.snapshot(cx);
1562 let edits = subscription.consume().into_inner();
1563 log::info!("editing {:?}", edits);
1564 buffer_edits.extend(edits);
1565 }),
1566 };
1567
1568 let (inlay_snapshot, new_inlay_edits) =
1569 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
1570 log::info!("inlay text {:?}", inlay_snapshot.text());
1571
1572 let inlay_edits = Patch::new(inlay_edits)
1573 .compose(new_inlay_edits)
1574 .into_inner();
1575 let (snapshot, edits) = map.read(inlay_snapshot.clone(), inlay_edits);
1576 snapshot_edits.push((snapshot.clone(), edits));
1577
1578 let mut expected_text: String = inlay_snapshot.text().to_string();
1579 for fold_range in map.merged_folds().into_iter().rev() {
1580 let fold_inlay_start = inlay_snapshot.to_inlay_offset(fold_range.start);
1581 let fold_inlay_end = inlay_snapshot.to_inlay_offset(fold_range.end);
1582 expected_text.replace_range(fold_inlay_start.0..fold_inlay_end.0, "⋯");
1583 }
1584
1585 assert_eq!(snapshot.text(), expected_text);
1586 log::info!(
1587 "fold text {:?} ({} lines)",
1588 expected_text,
1589 expected_text.matches('\n').count() + 1
1590 );
1591
1592 let mut prev_row = 0;
1593 let mut expected_buffer_rows = Vec::new();
1594 for fold_range in map.merged_folds() {
1595 let fold_start = inlay_snapshot
1596 .to_point(inlay_snapshot.to_inlay_offset(fold_range.start))
1597 .row();
1598 let fold_end = inlay_snapshot
1599 .to_point(inlay_snapshot.to_inlay_offset(fold_range.end))
1600 .row();
1601 expected_buffer_rows.extend(
1602 inlay_snapshot
1603 .buffer_rows(prev_row)
1604 .take((1 + fold_start - prev_row) as usize),
1605 );
1606 prev_row = 1 + fold_end;
1607 }
1608 expected_buffer_rows.extend(inlay_snapshot.buffer_rows(prev_row));
1609
1610 assert_eq!(
1611 expected_buffer_rows.len(),
1612 expected_text.matches('\n').count() + 1,
1613 "wrong expected buffer rows {:?}. text: {:?}",
1614 expected_buffer_rows,
1615 expected_text
1616 );
1617
1618 for (output_row, line) in expected_text.lines().enumerate() {
1619 let line_len = snapshot.line_len(output_row as u32);
1620 assert_eq!(line_len, line.len() as u32);
1621 }
1622
1623 let longest_row = snapshot.longest_row();
1624 let longest_char_column = expected_text
1625 .split('\n')
1626 .nth(longest_row as usize)
1627 .unwrap()
1628 .chars()
1629 .count();
1630 let mut fold_point = FoldPoint::new(0, 0);
1631 let mut fold_offset = FoldOffset(0);
1632 let mut char_column = 0;
1633 for c in expected_text.chars() {
1634 let inlay_point = fold_point.to_inlay_point(&snapshot);
1635 let inlay_offset = fold_offset.to_inlay_offset(&snapshot);
1636 assert_eq!(
1637 snapshot.to_fold_point(inlay_point, Right),
1638 fold_point,
1639 "{:?} -> fold point",
1640 inlay_point,
1641 );
1642 assert_eq!(
1643 inlay_snapshot.to_offset(inlay_point),
1644 inlay_offset,
1645 "inlay_snapshot.to_offset({:?})",
1646 inlay_point,
1647 );
1648 assert_eq!(
1649 fold_point.to_offset(&snapshot),
1650 fold_offset,
1651 "fold_point.to_offset({:?})",
1652 fold_point,
1653 );
1654
1655 if c == '\n' {
1656 *fold_point.row_mut() += 1;
1657 *fold_point.column_mut() = 0;
1658 char_column = 0;
1659 } else {
1660 *fold_point.column_mut() += c.len_utf8() as u32;
1661 char_column += 1;
1662 }
1663 fold_offset.0 += c.len_utf8();
1664 if char_column > longest_char_column {
1665 panic!(
1666 "invalid longest row {:?} (chars {}), found row {:?} (chars: {})",
1667 longest_row,
1668 longest_char_column,
1669 fold_point.row(),
1670 char_column
1671 );
1672 }
1673 }
1674
1675 for _ in 0..5 {
1676 let mut start = snapshot
1677 .clip_offset(FoldOffset(rng.gen_range(0..=snapshot.len().0)), Bias::Left);
1678 let mut end = snapshot
1679 .clip_offset(FoldOffset(rng.gen_range(0..=snapshot.len().0)), Bias::Right);
1680 if start > end {
1681 mem::swap(&mut start, &mut end);
1682 }
1683
1684 let text = &expected_text[start.0..end.0];
1685 assert_eq!(
1686 snapshot
1687 .chunks(start..end, false, Highlights::default())
1688 .map(|c| c.text)
1689 .collect::<String>(),
1690 text,
1691 );
1692 }
1693
1694 let mut fold_row = 0;
1695 while fold_row < expected_buffer_rows.len() as u32 {
1696 assert_eq!(
1697 snapshot.buffer_rows(fold_row).collect::<Vec<_>>(),
1698 expected_buffer_rows[(fold_row as usize)..],
1699 "wrong buffer rows starting at fold row {}",
1700 fold_row,
1701 );
1702 fold_row += 1;
1703 }
1704
1705 let folded_buffer_rows = map
1706 .merged_folds()
1707 .iter()
1708 .flat_map(|fold_range| {
1709 let start_row = fold_range.start.to_point(&buffer_snapshot).row;
1710 let end = fold_range.end.to_point(&buffer_snapshot);
1711 if end.column == 0 {
1712 start_row..end.row
1713 } else {
1714 start_row..end.row + 1
1715 }
1716 })
1717 .collect::<HashSet<_>>();
1718 for row in 0..=buffer_snapshot.max_point().row {
1719 assert_eq!(
1720 snapshot.is_line_folded(MultiBufferRow(row)),
1721 folded_buffer_rows.contains(&row),
1722 "expected buffer row {}{} to be folded",
1723 row,
1724 if folded_buffer_rows.contains(&row) {
1725 ""
1726 } else {
1727 " not"
1728 }
1729 );
1730 }
1731
1732 for _ in 0..5 {
1733 let end =
1734 buffer_snapshot.clip_offset(rng.gen_range(0..=buffer_snapshot.len()), Right);
1735 let start = buffer_snapshot.clip_offset(rng.gen_range(0..=end), Left);
1736 let expected_folds = map
1737 .snapshot
1738 .folds
1739 .items(&buffer_snapshot)
1740 .into_iter()
1741 .filter(|fold| {
1742 let start = buffer_snapshot.anchor_before(start);
1743 let end = buffer_snapshot.anchor_after(end);
1744 start.cmp(&fold.range.end, &buffer_snapshot) == Ordering::Less
1745 && end.cmp(&fold.range.start, &buffer_snapshot) == Ordering::Greater
1746 })
1747 .collect::<Vec<_>>();
1748
1749 assert_eq!(
1750 snapshot
1751 .folds_in_range(start..end)
1752 .cloned()
1753 .collect::<Vec<_>>(),
1754 expected_folds
1755 );
1756 }
1757
1758 let text = snapshot.text();
1759 for _ in 0..5 {
1760 let start_row = rng.gen_range(0..=snapshot.max_point().row());
1761 let start_column = rng.gen_range(0..=snapshot.line_len(start_row));
1762 let end_row = rng.gen_range(0..=snapshot.max_point().row());
1763 let end_column = rng.gen_range(0..=snapshot.line_len(end_row));
1764 let mut start =
1765 snapshot.clip_point(FoldPoint::new(start_row, start_column), Bias::Left);
1766 let mut end = snapshot.clip_point(FoldPoint::new(end_row, end_column), Bias::Right);
1767 if start > end {
1768 mem::swap(&mut start, &mut end);
1769 }
1770
1771 let lines = start..end;
1772 let bytes = start.to_offset(&snapshot)..end.to_offset(&snapshot);
1773 assert_eq!(
1774 snapshot.text_summary_for_range(lines),
1775 TextSummary::from(&text[bytes.start.0..bytes.end.0])
1776 )
1777 }
1778
1779 let mut text = initial_snapshot.text();
1780 for (snapshot, edits) in snapshot_edits.drain(..) {
1781 let new_text = snapshot.text();
1782 for edit in edits {
1783 let old_bytes = edit.new.start.0..edit.new.start.0 + edit.old_len().0;
1784 let new_bytes = edit.new.start.0..edit.new.end.0;
1785 text.replace_range(old_bytes, &new_text[new_bytes]);
1786 }
1787
1788 assert_eq!(text, new_text);
1789 initial_snapshot = snapshot;
1790 }
1791 }
1792 }
1793
1794 #[gpui::test]
1795 fn test_buffer_rows(cx: &mut gpui::AppContext) {
1796 let text = sample_text(6, 6, 'a') + "\n";
1797 let buffer = MultiBuffer::build_simple(&text, cx);
1798
1799 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1800 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
1801 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1802
1803 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1804 writer.fold(vec![
1805 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1806 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
1807 ]);
1808
1809 let (snapshot, _) = map.read(inlay_snapshot, vec![]);
1810 assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee\nffffff\n");
1811 assert_eq!(
1812 snapshot.buffer_rows(0).collect::<Vec<_>>(),
1813 [Some(0), Some(3), Some(5), Some(6)]
1814 );
1815 assert_eq!(snapshot.buffer_rows(3).collect::<Vec<_>>(), [Some(6)]);
1816 }
1817
1818 fn init_test(cx: &mut gpui::AppContext) {
1819 let store = SettingsStore::test(cx);
1820 cx.set_global(store);
1821 }
1822
1823 impl FoldMap {
1824 fn merged_folds(&self) -> Vec<Range<usize>> {
1825 let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
1826 let buffer = &inlay_snapshot.buffer;
1827 let mut folds = self.snapshot.folds.items(buffer);
1828 // Ensure sorting doesn't change how folds get merged and displayed.
1829 folds.sort_by(|a, b| a.range.cmp(&b.range, buffer));
1830 let mut folds = folds
1831 .iter()
1832 .map(|fold| fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer))
1833 .peekable();
1834
1835 let mut merged_folds = Vec::new();
1836 while let Some(mut fold_range) = folds.next() {
1837 while let Some(next_range) = folds.peek() {
1838 if fold_range.end >= next_range.start {
1839 if next_range.end > fold_range.end {
1840 fold_range.end = next_range.end;
1841 }
1842 folds.next();
1843 } else {
1844 break;
1845 }
1846 }
1847 if fold_range.end > fold_range.start {
1848 merged_folds.push(fold_range);
1849 }
1850 }
1851 merged_folds
1852 }
1853
1854 pub fn randomly_mutate(
1855 &mut self,
1856 rng: &mut impl Rng,
1857 ) -> Vec<(FoldSnapshot, Vec<FoldEdit>)> {
1858 let mut snapshot_edits = Vec::new();
1859 match rng.gen_range(0..=100) {
1860 0..=39 if !self.snapshot.folds.is_empty() => {
1861 let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
1862 let buffer = &inlay_snapshot.buffer;
1863 let mut to_unfold = Vec::new();
1864 for _ in 0..rng.gen_range(1..=3) {
1865 let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
1866 let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
1867 to_unfold.push(start..end);
1868 }
1869 let inclusive = rng.gen();
1870 log::info!("unfolding {:?} (inclusive: {})", to_unfold, inclusive);
1871 let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
1872 snapshot_edits.push((snapshot, edits));
1873 let (snapshot, edits) = writer.unfold(to_unfold, inclusive);
1874 snapshot_edits.push((snapshot, edits));
1875 }
1876 _ => {
1877 let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
1878 let buffer = &inlay_snapshot.buffer;
1879 let mut to_fold = Vec::new();
1880 for _ in 0..rng.gen_range(1..=2) {
1881 let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
1882 let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
1883 to_fold.push((start..end, FoldPlaceholder::test()));
1884 }
1885 log::info!("folding {:?}", to_fold);
1886 let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
1887 snapshot_edits.push((snapshot, edits));
1888 let (snapshot, edits) = writer.fold(to_fold);
1889 snapshot_edits.push((snapshot, edits));
1890 }
1891 }
1892 snapshot_edits
1893 }
1894 }
1895}