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> FoldBufferRows<'a> {
1104 pub(crate) fn seek(&mut self, row: u32) {
1105 let fold_point = FoldPoint::new(row, 0);
1106 self.cursor.seek(&fold_point, Bias::Left, &());
1107 let overshoot = fold_point.0 - self.cursor.start().0 .0;
1108 let inlay_point = InlayPoint(self.cursor.start().1 .0 + overshoot);
1109 self.input_buffer_rows.seek(inlay_point.row());
1110 self.fold_point = fold_point;
1111 }
1112}
1113
1114impl<'a> Iterator for FoldBufferRows<'a> {
1115 type Item = Option<u32>;
1116
1117 fn next(&mut self) -> Option<Self::Item> {
1118 let mut traversed_fold = false;
1119 while self.fold_point > self.cursor.end(&()).0 {
1120 self.cursor.next(&());
1121 traversed_fold = true;
1122 if self.cursor.item().is_none() {
1123 break;
1124 }
1125 }
1126
1127 if self.cursor.item().is_some() {
1128 if traversed_fold {
1129 self.input_buffer_rows.seek(self.cursor.start().1.row());
1130 self.input_buffer_rows.next();
1131 }
1132 *self.fold_point.row_mut() += 1;
1133 self.input_buffer_rows.next()
1134 } else {
1135 None
1136 }
1137 }
1138}
1139
1140pub struct FoldChunks<'a> {
1141 transform_cursor: Cursor<'a, Transform, (FoldOffset, InlayOffset)>,
1142 inlay_chunks: InlayChunks<'a>,
1143 inlay_chunk: Option<(InlayOffset, Chunk<'a>)>,
1144 inlay_offset: InlayOffset,
1145 output_offset: FoldOffset,
1146 max_output_offset: FoldOffset,
1147}
1148
1149impl<'a> FoldChunks<'a> {
1150 pub(crate) fn seek(&mut self, range: Range<FoldOffset>) {
1151 self.transform_cursor.seek(&range.start, Bias::Right, &());
1152
1153 let inlay_start = {
1154 let overshoot = range.start.0 - self.transform_cursor.start().0 .0;
1155 self.transform_cursor.start().1 + InlayOffset(overshoot)
1156 };
1157
1158 let transform_end = self.transform_cursor.end(&());
1159
1160 let inlay_end = if self
1161 .transform_cursor
1162 .item()
1163 .map_or(true, |transform| transform.is_fold())
1164 {
1165 inlay_start
1166 } else if range.end < transform_end.0 {
1167 let overshoot = range.end.0 - self.transform_cursor.start().0 .0;
1168 self.transform_cursor.start().1 + InlayOffset(overshoot)
1169 } else {
1170 transform_end.1
1171 };
1172
1173 self.inlay_chunks.seek(inlay_start..inlay_end);
1174 self.inlay_chunk = None;
1175 self.inlay_offset = inlay_start;
1176 self.output_offset = range.start;
1177 self.max_output_offset = range.end;
1178 }
1179}
1180
1181impl<'a> Iterator for FoldChunks<'a> {
1182 type Item = Chunk<'a>;
1183
1184 fn next(&mut self) -> Option<Self::Item> {
1185 if self.output_offset >= self.max_output_offset {
1186 return None;
1187 }
1188
1189 let transform = self.transform_cursor.item()?;
1190
1191 // If we're in a fold, then return the fold's display text and
1192 // advance the transform and buffer cursors to the end of the fold.
1193 if let Some(placeholder) = transform.placeholder.as_ref() {
1194 self.inlay_chunk.take();
1195 self.inlay_offset += InlayOffset(transform.summary.input.len);
1196
1197 while self.inlay_offset >= self.transform_cursor.end(&()).1
1198 && self.transform_cursor.item().is_some()
1199 {
1200 self.transform_cursor.next(&());
1201 }
1202
1203 self.output_offset.0 += placeholder.text.len();
1204 return Some(Chunk {
1205 text: placeholder.text,
1206 renderer: Some(placeholder.renderer.clone()),
1207 ..Default::default()
1208 });
1209 }
1210
1211 // When we reach a non-fold region, seek the underlying text
1212 // chunk iterator to the next unfolded range.
1213 if self.inlay_offset == self.transform_cursor.start().1
1214 && self.inlay_chunks.offset() != self.inlay_offset
1215 {
1216 let transform_start = self.transform_cursor.start();
1217 let transform_end = self.transform_cursor.end(&());
1218 let inlay_end = if self.max_output_offset < transform_end.0 {
1219 let overshoot = self.max_output_offset.0 - transform_start.0 .0;
1220 transform_start.1 + InlayOffset(overshoot)
1221 } else {
1222 transform_end.1
1223 };
1224
1225 self.inlay_chunks.seek(self.inlay_offset..inlay_end);
1226 }
1227
1228 // Retrieve a chunk from the current location in the buffer.
1229 if self.inlay_chunk.is_none() {
1230 let chunk_offset = self.inlay_chunks.offset();
1231 self.inlay_chunk = self.inlay_chunks.next().map(|chunk| (chunk_offset, chunk));
1232 }
1233
1234 // Otherwise, take a chunk from the buffer's text.
1235 if let Some((buffer_chunk_start, mut chunk)) = self.inlay_chunk.clone() {
1236 let buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len());
1237 let transform_end = self.transform_cursor.end(&()).1;
1238 let chunk_end = buffer_chunk_end.min(transform_end);
1239
1240 chunk.text = &chunk.text
1241 [(self.inlay_offset - buffer_chunk_start).0..(chunk_end - buffer_chunk_start).0];
1242
1243 if chunk_end == transform_end {
1244 self.transform_cursor.next(&());
1245 } else if chunk_end == buffer_chunk_end {
1246 self.inlay_chunk.take();
1247 }
1248
1249 self.inlay_offset = chunk_end;
1250 self.output_offset.0 += chunk.text.len();
1251 return Some(chunk);
1252 }
1253
1254 None
1255 }
1256}
1257
1258#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
1259pub struct FoldOffset(pub usize);
1260
1261impl FoldOffset {
1262 pub fn to_point(self, snapshot: &FoldSnapshot) -> FoldPoint {
1263 let mut cursor = snapshot
1264 .transforms
1265 .cursor::<(FoldOffset, TransformSummary)>(&());
1266 cursor.seek(&self, Bias::Right, &());
1267 let overshoot = if cursor.item().map_or(true, |t| t.is_fold()) {
1268 Point::new(0, (self.0 - cursor.start().0 .0) as u32)
1269 } else {
1270 let inlay_offset = cursor.start().1.input.len + self.0 - cursor.start().0 .0;
1271 let inlay_point = snapshot.inlay_snapshot.to_point(InlayOffset(inlay_offset));
1272 inlay_point.0 - cursor.start().1.input.lines
1273 };
1274 FoldPoint(cursor.start().1.output.lines + overshoot)
1275 }
1276
1277 #[cfg(test)]
1278 pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset {
1279 let mut cursor = snapshot.transforms.cursor::<(FoldOffset, InlayOffset)>(&());
1280 cursor.seek(&self, Bias::Right, &());
1281 let overshoot = self.0 - cursor.start().0 .0;
1282 InlayOffset(cursor.start().1 .0 + overshoot)
1283 }
1284}
1285
1286impl Add for FoldOffset {
1287 type Output = Self;
1288
1289 fn add(self, rhs: Self) -> Self::Output {
1290 Self(self.0 + rhs.0)
1291 }
1292}
1293
1294impl AddAssign for FoldOffset {
1295 fn add_assign(&mut self, rhs: Self) {
1296 self.0 += rhs.0;
1297 }
1298}
1299
1300impl Sub for FoldOffset {
1301 type Output = Self;
1302
1303 fn sub(self, rhs: Self) -> Self::Output {
1304 Self(self.0 - rhs.0)
1305 }
1306}
1307
1308impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset {
1309 fn zero(_cx: &()) -> Self {
1310 Default::default()
1311 }
1312
1313 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1314 self.0 += &summary.output.len;
1315 }
1316}
1317
1318impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
1319 fn zero(_cx: &()) -> Self {
1320 Default::default()
1321 }
1322
1323 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1324 self.0 += &summary.input.lines;
1325 }
1326}
1327
1328impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
1329 fn zero(_cx: &()) -> Self {
1330 Default::default()
1331 }
1332
1333 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1334 self.0 += &summary.input.len;
1335 }
1336}
1337
1338pub type FoldEdit = Edit<FoldOffset>;
1339
1340#[cfg(test)]
1341mod tests {
1342 use super::*;
1343 use crate::{display_map::inlay_map::InlayMap, MultiBuffer, ToPoint};
1344 use collections::HashSet;
1345 use rand::prelude::*;
1346 use settings::SettingsStore;
1347 use std::{env, mem};
1348 use text::Patch;
1349 use util::test::sample_text;
1350 use util::RandomCharIter;
1351 use Bias::{Left, Right};
1352
1353 #[gpui::test]
1354 fn test_basic_folds(cx: &mut gpui::AppContext) {
1355 init_test(cx);
1356 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1357 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1358 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1359 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1360 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1361
1362 let (mut writer, _, _) = map.write(inlay_snapshot, vec![]);
1363 let (snapshot2, edits) = writer.fold(vec![
1364 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1365 (Point::new(2, 4)..Point::new(4, 1), FoldPlaceholder::test()),
1366 ]);
1367 assert_eq!(snapshot2.text(), "aa⋯cc⋯eeeee");
1368 assert_eq!(
1369 edits,
1370 &[
1371 FoldEdit {
1372 old: FoldOffset(2)..FoldOffset(16),
1373 new: FoldOffset(2)..FoldOffset(5),
1374 },
1375 FoldEdit {
1376 old: FoldOffset(18)..FoldOffset(29),
1377 new: FoldOffset(7)..FoldOffset(10)
1378 },
1379 ]
1380 );
1381
1382 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1383 buffer.edit(
1384 vec![
1385 (Point::new(0, 0)..Point::new(0, 1), "123"),
1386 (Point::new(2, 3)..Point::new(2, 3), "123"),
1387 ],
1388 None,
1389 cx,
1390 );
1391 buffer.snapshot(cx)
1392 });
1393
1394 let (inlay_snapshot, inlay_edits) =
1395 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1396 let (snapshot3, edits) = map.read(inlay_snapshot, inlay_edits);
1397 assert_eq!(snapshot3.text(), "123a⋯c123c⋯eeeee");
1398 assert_eq!(
1399 edits,
1400 &[
1401 FoldEdit {
1402 old: FoldOffset(0)..FoldOffset(1),
1403 new: FoldOffset(0)..FoldOffset(3),
1404 },
1405 FoldEdit {
1406 old: FoldOffset(6)..FoldOffset(6),
1407 new: FoldOffset(8)..FoldOffset(11),
1408 },
1409 ]
1410 );
1411
1412 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1413 buffer.edit([(Point::new(2, 6)..Point::new(4, 3), "456")], None, cx);
1414 buffer.snapshot(cx)
1415 });
1416 let (inlay_snapshot, inlay_edits) =
1417 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1418 let (snapshot4, _) = map.read(inlay_snapshot.clone(), inlay_edits);
1419 assert_eq!(snapshot4.text(), "123a⋯c123456eee");
1420
1421 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1422 writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), false);
1423 let (snapshot5, _) = map.read(inlay_snapshot.clone(), vec![]);
1424 assert_eq!(snapshot5.text(), "123a⋯c123456eee");
1425
1426 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1427 writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), true);
1428 let (snapshot6, _) = map.read(inlay_snapshot, vec![]);
1429 assert_eq!(snapshot6.text(), "123aaaaa\nbbbbbb\nccc123456eee");
1430 }
1431
1432 #[gpui::test]
1433 fn test_adjacent_folds(cx: &mut gpui::AppContext) {
1434 init_test(cx);
1435 let buffer = MultiBuffer::build_simple("abcdefghijkl", cx);
1436 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1437 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1438 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1439
1440 {
1441 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1442
1443 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1444 writer.fold(vec![(5..8, FoldPlaceholder::test())]);
1445 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1446 assert_eq!(snapshot.text(), "abcde⋯ijkl");
1447
1448 // Create an fold adjacent to the start of the first fold.
1449 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1450 writer.fold(vec![
1451 (0..1, FoldPlaceholder::test()),
1452 (2..5, FoldPlaceholder::test()),
1453 ]);
1454 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1455 assert_eq!(snapshot.text(), "⋯b⋯ijkl");
1456
1457 // Create an fold adjacent to the end of the first fold.
1458 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1459 writer.fold(vec![
1460 (11..11, FoldPlaceholder::test()),
1461 (8..10, FoldPlaceholder::test()),
1462 ]);
1463 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1464 assert_eq!(snapshot.text(), "⋯b⋯kl");
1465 }
1466
1467 {
1468 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1469
1470 // Create two adjacent folds.
1471 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1472 writer.fold(vec![
1473 (0..2, FoldPlaceholder::test()),
1474 (2..5, FoldPlaceholder::test()),
1475 ]);
1476 let (snapshot, _) = map.read(inlay_snapshot, vec![]);
1477 assert_eq!(snapshot.text(), "⋯fghijkl");
1478
1479 // Edit within one of the folds.
1480 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1481 buffer.edit([(0..1, "12345")], None, cx);
1482 buffer.snapshot(cx)
1483 });
1484 let (inlay_snapshot, inlay_edits) =
1485 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1486 let (snapshot, _) = map.read(inlay_snapshot, inlay_edits);
1487 assert_eq!(snapshot.text(), "12345⋯fghijkl");
1488 }
1489 }
1490
1491 #[gpui::test]
1492 fn test_overlapping_folds(cx: &mut gpui::AppContext) {
1493 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1494 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1495 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
1496 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1497 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1498 writer.fold(vec![
1499 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1500 (Point::new(0, 4)..Point::new(1, 0), FoldPlaceholder::test()),
1501 (Point::new(1, 2)..Point::new(3, 2), FoldPlaceholder::test()),
1502 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
1503 ]);
1504 let (snapshot, _) = map.read(inlay_snapshot, vec![]);
1505 assert_eq!(snapshot.text(), "aa⋯eeeee");
1506 }
1507
1508 #[gpui::test]
1509 fn test_merging_folds_via_edit(cx: &mut gpui::AppContext) {
1510 init_test(cx);
1511 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1512 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1513 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1514 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1515 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1516
1517 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1518 writer.fold(vec![
1519 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1520 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
1521 ]);
1522 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1523 assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee");
1524
1525 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1526 buffer.edit([(Point::new(2, 2)..Point::new(3, 1), "")], None, cx);
1527 buffer.snapshot(cx)
1528 });
1529 let (inlay_snapshot, inlay_edits) =
1530 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1531 let (snapshot, _) = map.read(inlay_snapshot, inlay_edits);
1532 assert_eq!(snapshot.text(), "aa⋯eeeee");
1533 }
1534
1535 #[gpui::test]
1536 fn test_folds_in_range(cx: &mut gpui::AppContext) {
1537 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1538 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1539 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1540 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1541
1542 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1543 writer.fold(vec![
1544 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1545 (Point::new(0, 4)..Point::new(1, 0), FoldPlaceholder::test()),
1546 (Point::new(1, 2)..Point::new(3, 2), FoldPlaceholder::test()),
1547 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
1548 ]);
1549 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1550 let fold_ranges = snapshot
1551 .folds_in_range(Point::new(1, 0)..Point::new(1, 3))
1552 .map(|fold| {
1553 fold.range.start.to_point(&buffer_snapshot)
1554 ..fold.range.end.to_point(&buffer_snapshot)
1555 })
1556 .collect::<Vec<_>>();
1557 assert_eq!(
1558 fold_ranges,
1559 vec![
1560 Point::new(0, 2)..Point::new(2, 2),
1561 Point::new(1, 2)..Point::new(3, 2)
1562 ]
1563 );
1564 }
1565
1566 #[gpui::test(iterations = 100)]
1567 fn test_random_folds(cx: &mut gpui::AppContext, mut rng: StdRng) {
1568 init_test(cx);
1569 let operations = env::var("OPERATIONS")
1570 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1571 .unwrap_or(10);
1572
1573 let len = rng.gen_range(0..10);
1574 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
1575 let buffer = if rng.gen() {
1576 MultiBuffer::build_simple(&text, cx)
1577 } else {
1578 MultiBuffer::build_random(&mut rng, cx)
1579 };
1580 let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1581 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1582 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1583
1584 let (mut initial_snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1585 let mut snapshot_edits = Vec::new();
1586
1587 let mut next_inlay_id = 0;
1588 for _ in 0..operations {
1589 log::info!("text: {:?}", buffer_snapshot.text());
1590 let mut buffer_edits = Vec::new();
1591 let mut inlay_edits = Vec::new();
1592 match rng.gen_range(0..=100) {
1593 0..=39 => {
1594 snapshot_edits.extend(map.randomly_mutate(&mut rng));
1595 }
1596 40..=59 => {
1597 let (_, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1598 inlay_edits = edits;
1599 }
1600 _ => buffer.update(cx, |buffer, cx| {
1601 let subscription = buffer.subscribe();
1602 let edit_count = rng.gen_range(1..=5);
1603 buffer.randomly_mutate(&mut rng, edit_count, cx);
1604 buffer_snapshot = buffer.snapshot(cx);
1605 let edits = subscription.consume().into_inner();
1606 log::info!("editing {:?}", edits);
1607 buffer_edits.extend(edits);
1608 }),
1609 };
1610
1611 let (inlay_snapshot, new_inlay_edits) =
1612 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
1613 log::info!("inlay text {:?}", inlay_snapshot.text());
1614
1615 let inlay_edits = Patch::new(inlay_edits)
1616 .compose(new_inlay_edits)
1617 .into_inner();
1618 let (snapshot, edits) = map.read(inlay_snapshot.clone(), inlay_edits);
1619 snapshot_edits.push((snapshot.clone(), edits));
1620
1621 let mut expected_text: String = inlay_snapshot.text().to_string();
1622 for fold_range in map.merged_folds().into_iter().rev() {
1623 let fold_inlay_start = inlay_snapshot.to_inlay_offset(fold_range.start);
1624 let fold_inlay_end = inlay_snapshot.to_inlay_offset(fold_range.end);
1625 expected_text.replace_range(fold_inlay_start.0..fold_inlay_end.0, "⋯");
1626 }
1627
1628 assert_eq!(snapshot.text(), expected_text);
1629 log::info!(
1630 "fold text {:?} ({} lines)",
1631 expected_text,
1632 expected_text.matches('\n').count() + 1
1633 );
1634
1635 let mut prev_row = 0;
1636 let mut expected_buffer_rows = Vec::new();
1637 for fold_range in map.merged_folds() {
1638 let fold_start = inlay_snapshot
1639 .to_point(inlay_snapshot.to_inlay_offset(fold_range.start))
1640 .row();
1641 let fold_end = inlay_snapshot
1642 .to_point(inlay_snapshot.to_inlay_offset(fold_range.end))
1643 .row();
1644 expected_buffer_rows.extend(
1645 inlay_snapshot
1646 .buffer_rows(prev_row)
1647 .take((1 + fold_start - prev_row) as usize),
1648 );
1649 prev_row = 1 + fold_end;
1650 }
1651 expected_buffer_rows.extend(inlay_snapshot.buffer_rows(prev_row));
1652
1653 assert_eq!(
1654 expected_buffer_rows.len(),
1655 expected_text.matches('\n').count() + 1,
1656 "wrong expected buffer rows {:?}. text: {:?}",
1657 expected_buffer_rows,
1658 expected_text
1659 );
1660
1661 for (output_row, line) in expected_text.lines().enumerate() {
1662 let line_len = snapshot.line_len(output_row as u32);
1663 assert_eq!(line_len, line.len() as u32);
1664 }
1665
1666 let longest_row = snapshot.longest_row();
1667 let longest_char_column = expected_text
1668 .split('\n')
1669 .nth(longest_row as usize)
1670 .unwrap()
1671 .chars()
1672 .count();
1673 let mut fold_point = FoldPoint::new(0, 0);
1674 let mut fold_offset = FoldOffset(0);
1675 let mut char_column = 0;
1676 for c in expected_text.chars() {
1677 let inlay_point = fold_point.to_inlay_point(&snapshot);
1678 let inlay_offset = fold_offset.to_inlay_offset(&snapshot);
1679 assert_eq!(
1680 snapshot.to_fold_point(inlay_point, Right),
1681 fold_point,
1682 "{:?} -> fold point",
1683 inlay_point,
1684 );
1685 assert_eq!(
1686 inlay_snapshot.to_offset(inlay_point),
1687 inlay_offset,
1688 "inlay_snapshot.to_offset({:?})",
1689 inlay_point,
1690 );
1691 assert_eq!(
1692 fold_point.to_offset(&snapshot),
1693 fold_offset,
1694 "fold_point.to_offset({:?})",
1695 fold_point,
1696 );
1697
1698 if c == '\n' {
1699 *fold_point.row_mut() += 1;
1700 *fold_point.column_mut() = 0;
1701 char_column = 0;
1702 } else {
1703 *fold_point.column_mut() += c.len_utf8() as u32;
1704 char_column += 1;
1705 }
1706 fold_offset.0 += c.len_utf8();
1707 if char_column > longest_char_column {
1708 panic!(
1709 "invalid longest row {:?} (chars {}), found row {:?} (chars: {})",
1710 longest_row,
1711 longest_char_column,
1712 fold_point.row(),
1713 char_column
1714 );
1715 }
1716 }
1717
1718 for _ in 0..5 {
1719 let mut start = snapshot
1720 .clip_offset(FoldOffset(rng.gen_range(0..=snapshot.len().0)), Bias::Left);
1721 let mut end = snapshot
1722 .clip_offset(FoldOffset(rng.gen_range(0..=snapshot.len().0)), Bias::Right);
1723 if start > end {
1724 mem::swap(&mut start, &mut end);
1725 }
1726
1727 let text = &expected_text[start.0..end.0];
1728 assert_eq!(
1729 snapshot
1730 .chunks(start..end, false, Highlights::default())
1731 .map(|c| c.text)
1732 .collect::<String>(),
1733 text,
1734 );
1735 }
1736
1737 let mut fold_row = 0;
1738 while fold_row < expected_buffer_rows.len() as u32 {
1739 assert_eq!(
1740 snapshot.buffer_rows(fold_row).collect::<Vec<_>>(),
1741 expected_buffer_rows[(fold_row as usize)..],
1742 "wrong buffer rows starting at fold row {}",
1743 fold_row,
1744 );
1745 fold_row += 1;
1746 }
1747
1748 let folded_buffer_rows = map
1749 .merged_folds()
1750 .iter()
1751 .flat_map(|fold_range| {
1752 let start_row = fold_range.start.to_point(&buffer_snapshot).row;
1753 let end = fold_range.end.to_point(&buffer_snapshot);
1754 if end.column == 0 {
1755 start_row..end.row
1756 } else {
1757 start_row..end.row + 1
1758 }
1759 })
1760 .collect::<HashSet<_>>();
1761 for row in 0..=buffer_snapshot.max_point().row {
1762 assert_eq!(
1763 snapshot.is_line_folded(MultiBufferRow(row)),
1764 folded_buffer_rows.contains(&row),
1765 "expected buffer row {}{} to be folded",
1766 row,
1767 if folded_buffer_rows.contains(&row) {
1768 ""
1769 } else {
1770 " not"
1771 }
1772 );
1773 }
1774
1775 for _ in 0..5 {
1776 let end =
1777 buffer_snapshot.clip_offset(rng.gen_range(0..=buffer_snapshot.len()), Right);
1778 let start = buffer_snapshot.clip_offset(rng.gen_range(0..=end), Left);
1779 let expected_folds = map
1780 .snapshot
1781 .folds
1782 .items(&buffer_snapshot)
1783 .into_iter()
1784 .filter(|fold| {
1785 let start = buffer_snapshot.anchor_before(start);
1786 let end = buffer_snapshot.anchor_after(end);
1787 start.cmp(&fold.range.end, &buffer_snapshot) == Ordering::Less
1788 && end.cmp(&fold.range.start, &buffer_snapshot) == Ordering::Greater
1789 })
1790 .collect::<Vec<_>>();
1791
1792 assert_eq!(
1793 snapshot
1794 .folds_in_range(start..end)
1795 .cloned()
1796 .collect::<Vec<_>>(),
1797 expected_folds
1798 );
1799 }
1800
1801 let text = snapshot.text();
1802 for _ in 0..5 {
1803 let start_row = rng.gen_range(0..=snapshot.max_point().row());
1804 let start_column = rng.gen_range(0..=snapshot.line_len(start_row));
1805 let end_row = rng.gen_range(0..=snapshot.max_point().row());
1806 let end_column = rng.gen_range(0..=snapshot.line_len(end_row));
1807 let mut start =
1808 snapshot.clip_point(FoldPoint::new(start_row, start_column), Bias::Left);
1809 let mut end = snapshot.clip_point(FoldPoint::new(end_row, end_column), Bias::Right);
1810 if start > end {
1811 mem::swap(&mut start, &mut end);
1812 }
1813
1814 let lines = start..end;
1815 let bytes = start.to_offset(&snapshot)..end.to_offset(&snapshot);
1816 assert_eq!(
1817 snapshot.text_summary_for_range(lines),
1818 TextSummary::from(&text[bytes.start.0..bytes.end.0])
1819 )
1820 }
1821
1822 let mut text = initial_snapshot.text();
1823 for (snapshot, edits) in snapshot_edits.drain(..) {
1824 let new_text = snapshot.text();
1825 for edit in edits {
1826 let old_bytes = edit.new.start.0..edit.new.start.0 + edit.old_len().0;
1827 let new_bytes = edit.new.start.0..edit.new.end.0;
1828 text.replace_range(old_bytes, &new_text[new_bytes]);
1829 }
1830
1831 assert_eq!(text, new_text);
1832 initial_snapshot = snapshot;
1833 }
1834 }
1835 }
1836
1837 #[gpui::test]
1838 fn test_buffer_rows(cx: &mut gpui::AppContext) {
1839 let text = sample_text(6, 6, 'a') + "\n";
1840 let buffer = MultiBuffer::build_simple(&text, cx);
1841
1842 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1843 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
1844 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1845
1846 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1847 writer.fold(vec![
1848 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1849 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
1850 ]);
1851
1852 let (snapshot, _) = map.read(inlay_snapshot, vec![]);
1853 assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee\nffffff\n");
1854 assert_eq!(
1855 snapshot.buffer_rows(0).collect::<Vec<_>>(),
1856 [Some(0), Some(3), Some(5), Some(6)]
1857 );
1858 assert_eq!(snapshot.buffer_rows(3).collect::<Vec<_>>(), [Some(6)]);
1859 }
1860
1861 fn init_test(cx: &mut gpui::AppContext) {
1862 let store = SettingsStore::test(cx);
1863 cx.set_global(store);
1864 }
1865
1866 impl FoldMap {
1867 fn merged_folds(&self) -> Vec<Range<usize>> {
1868 let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
1869 let buffer = &inlay_snapshot.buffer;
1870 let mut folds = self.snapshot.folds.items(buffer);
1871 // Ensure sorting doesn't change how folds get merged and displayed.
1872 folds.sort_by(|a, b| a.range.cmp(&b.range, buffer));
1873 let mut folds = folds
1874 .iter()
1875 .map(|fold| fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer))
1876 .peekable();
1877
1878 let mut merged_folds = Vec::new();
1879 while let Some(mut fold_range) = folds.next() {
1880 while let Some(next_range) = folds.peek() {
1881 if fold_range.end >= next_range.start {
1882 if next_range.end > fold_range.end {
1883 fold_range.end = next_range.end;
1884 }
1885 folds.next();
1886 } else {
1887 break;
1888 }
1889 }
1890 if fold_range.end > fold_range.start {
1891 merged_folds.push(fold_range);
1892 }
1893 }
1894 merged_folds
1895 }
1896
1897 pub fn randomly_mutate(
1898 &mut self,
1899 rng: &mut impl Rng,
1900 ) -> Vec<(FoldSnapshot, Vec<FoldEdit>)> {
1901 let mut snapshot_edits = Vec::new();
1902 match rng.gen_range(0..=100) {
1903 0..=39 if !self.snapshot.folds.is_empty() => {
1904 let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
1905 let buffer = &inlay_snapshot.buffer;
1906 let mut to_unfold = Vec::new();
1907 for _ in 0..rng.gen_range(1..=3) {
1908 let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
1909 let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
1910 to_unfold.push(start..end);
1911 }
1912 let inclusive = rng.gen();
1913 log::info!("unfolding {:?} (inclusive: {})", to_unfold, inclusive);
1914 let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
1915 snapshot_edits.push((snapshot, edits));
1916 let (snapshot, edits) = writer.unfold(to_unfold, inclusive);
1917 snapshot_edits.push((snapshot, edits));
1918 }
1919 _ => {
1920 let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
1921 let buffer = &inlay_snapshot.buffer;
1922 let mut to_fold = Vec::new();
1923 for _ in 0..rng.gen_range(1..=2) {
1924 let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
1925 let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
1926 to_fold.push((start..end, FoldPlaceholder::test()));
1927 }
1928 log::info!("folding {:?}", to_fold);
1929 let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
1930 snapshot_edits.push((snapshot, edits));
1931 let (snapshot, edits) = writer.fold(to_fold);
1932 snapshot_edits.push((snapshot, edits));
1933 }
1934 }
1935 snapshot_edits
1936 }
1937 }
1938}