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