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