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