1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct Fold {
33 pub start_row: usize,
35 pub end_row: usize,
37 pub closed: bool,
39 pub auto_generated: bool,
47}
48
49impl Fold {
50 pub fn contains(&self, row: usize) -> bool {
51 row >= self.start_row && row <= self.end_row
52 }
53
54 pub fn hides(&self, row: usize) -> bool {
57 self.closed && row > self.start_row && row <= self.end_row
58 }
59
60 pub fn line_count(&self) -> usize {
62 self.end_row.saturating_sub(self.start_row) + 1
63 }
64}
65
66impl crate::View {
67 pub fn folds(&self) -> Vec<Fold> {
73 self.content_lock().folds.clone()
74 }
75
76 pub fn add_fold(&mut self, start_row: usize, end_row: usize, closed: bool) {
80 if end_row < start_row {
81 return;
82 }
83 let last = self.row_count().saturating_sub(1);
84 if start_row > last {
85 return;
86 }
87 let end_row = end_row.min(last);
88 let fold = Fold {
89 start_row,
90 end_row,
91 closed,
92 auto_generated: false,
93 };
94 {
95 let mut c = self.content_lock_mut();
96 if let Some(idx) = c.folds.iter().position(|f| f.start_row == start_row) {
97 c.folds[idx] = fold;
98 } else {
99 let pos = c
100 .folds
101 .iter()
102 .position(|f| f.start_row > start_row)
103 .unwrap_or(c.folds.len());
104 c.folds.insert(pos, fold);
105 }
106 }
107 self.dirty_gen_bump();
108 }
109
110 pub fn set_auto_folds(&mut self, ranges: &[(usize, usize)], default_closed: bool) {
129 let prev_closed: std::collections::HashMap<usize, bool> = self
131 .content_lock()
132 .folds
133 .iter()
134 .filter(|f| f.auto_generated)
135 .map(|f| (f.start_row, f.closed))
136 .collect();
137
138 {
140 let mut c = self.content_lock_mut();
141 c.folds.retain(|f| !f.auto_generated);
142 }
143
144 let last = self.row_count().saturating_sub(1);
146 for &(start_row, end_row) in ranges {
147 if end_row < start_row || start_row > last {
149 continue;
150 }
151 let end_row = end_row.min(last);
152 if end_row == start_row {
154 continue;
155 }
156 let closed = prev_closed
157 .get(&start_row)
158 .copied()
159 .unwrap_or(default_closed);
160 let fold = Fold {
161 start_row,
162 end_row,
163 closed,
164 auto_generated: true,
165 };
166 let mut c = self.content_lock_mut();
167 if let Some(idx) = c.folds.iter().position(|f| f.start_row == start_row) {
169 c.folds[idx] = fold;
170 } else {
171 let pos = c
172 .folds
173 .iter()
174 .position(|f| f.start_row > start_row)
175 .unwrap_or(c.folds.len());
176 c.folds.insert(pos, fold);
177 }
178 }
179
180 self.dirty_gen_bump();
181 }
182
183 pub fn remove_fold_at(&mut self, row: usize) -> bool {
186 let idx = self
189 .content_lock()
190 .folds
191 .iter()
192 .enumerate()
193 .filter(|(_, f)| f.contains(row))
194 .max_by_key(|(_, f)| f.start_row)
195 .map(|(i, _)| i);
196 let Some(idx) = idx else {
197 return false;
198 };
199 self.content_lock_mut().folds.remove(idx);
200 self.dirty_gen_bump();
201 true
202 }
203
204 pub fn open_fold_at(&mut self, row: usize) -> bool {
206 let changed = {
207 let mut c = self.content_lock_mut();
208 let Some(f) = c
209 .folds
210 .iter_mut()
211 .filter(|f| f.contains(row))
212 .max_by_key(|f| f.start_row)
213 else {
214 return false;
215 };
216 if !f.closed {
217 return false;
218 }
219 f.closed = false;
220 true
221 };
222 if changed {
223 self.dirty_gen_bump();
224 }
225 changed
226 }
227
228 pub fn close_fold_at(&mut self, row: usize) -> bool {
230 let changed = {
231 let mut c = self.content_lock_mut();
232 let Some(f) = c
233 .folds
234 .iter_mut()
235 .filter(|f| f.contains(row))
236 .max_by_key(|f| f.start_row)
237 else {
238 return false;
239 };
240 if f.closed {
241 return false;
242 }
243 f.closed = true;
244 true
245 };
246 if changed {
247 self.dirty_gen_bump();
248 }
249 changed
250 }
251
252 pub fn toggle_fold_at(&mut self, row: usize) -> bool {
254 let changed = {
255 let mut c = self.content_lock_mut();
256 let Some(f) = c
257 .folds
258 .iter_mut()
259 .filter(|f| f.contains(row))
260 .max_by_key(|f| f.start_row)
261 else {
262 return false;
263 };
264 f.closed = !f.closed;
265 true
266 };
267 if changed {
268 self.dirty_gen_bump();
269 }
270 changed
271 }
272
273 pub fn open_all_folds(&mut self) {
275 let changed = {
276 let mut c = self.content_lock_mut();
277 let mut any = false;
278 for f in c.folds.iter_mut() {
279 if f.closed {
280 f.closed = false;
281 any = true;
282 }
283 }
284 any
285 };
286 if changed {
287 self.dirty_gen_bump();
288 }
289 }
290
291 pub fn clear_all_folds(&mut self) {
293 let was_nonempty = !self.content_lock().folds.is_empty();
294 if was_nonempty {
295 self.content_lock_mut().folds.clear();
296 self.dirty_gen_bump();
297 }
298 }
299
300 pub fn close_all_folds(&mut self) {
302 let changed = {
303 let mut c = self.content_lock_mut();
304 let mut any = false;
305 for f in c.folds.iter_mut() {
306 if !f.closed {
307 f.closed = true;
308 any = true;
309 }
310 }
311 any
312 };
313 if changed {
314 self.dirty_gen_bump();
315 }
316 }
317
318 pub fn fold_at_row(&self, row: usize) -> Option<Fold> {
321 self.content_lock()
326 .folds
327 .iter()
328 .filter(|f| f.contains(row))
329 .max_by_key(|f| f.start_row)
330 .copied()
331 }
332
333 pub fn is_row_hidden(&self, row: usize) -> bool {
335 self.folds().iter().any(|f| f.hides(row))
336 }
337
338 pub fn reveal_row(&mut self, row: usize) -> bool {
346 let changed = {
347 let mut c = self.content_lock_mut();
348 let mut any = false;
349 for f in c.folds.iter_mut() {
350 if f.hides(row) {
351 f.closed = false;
352 any = true;
353 }
354 }
355 any
356 };
357 if changed {
358 self.dirty_gen_bump();
359 }
360 changed
361 }
362
363 pub fn next_visible_row(&self, row: usize) -> Option<usize> {
366 let last = self.row_count().saturating_sub(1);
367 if last == 0 && row == 0 {
368 return None;
369 }
370 let mut r = row.checked_add(1)?;
371 while r <= last && self.is_row_hidden(r) {
372 r += 1;
373 }
374 (r <= last).then_some(r)
375 }
376
377 pub fn prev_visible_row(&self, row: usize) -> Option<usize> {
379 let mut r = row.checked_sub(1)?;
380 while self.is_row_hidden(r) {
381 r = r.checked_sub(1)?;
382 }
383 Some(r)
384 }
385
386 pub fn invalidate_folds_in_range(&mut self, start_row: usize, end_row: usize) {
388 let before = self.content_lock().folds.len();
389 invalidate_folds(&mut self.content_lock_mut().folds, start_row, end_row);
390 if self.content_lock().folds.len() != before {
391 self.dirty_gen_bump();
392 }
393 }
394
395 pub fn rebase_folds(
399 &mut self,
400 edit_start: usize,
401 drop_end: usize,
402 shift_threshold: usize,
403 delta: isize,
404 ) {
405 if delta == 0 {
406 return;
407 }
408 let mut c = self.content_lock_mut();
409 shift_folds_after_edit(&mut c.folds, edit_start, drop_end, shift_threshold, delta);
410 }
411
412 pub fn set_folds(&mut self, folds: &[Fold]) {
417 {
418 let mut c = self.content_lock_mut();
419 if c.folds.as_slice() == folds {
420 return; }
422 c.folds = folds.to_vec();
423 }
424 self.dirty_gen_bump();
425 }
426}
427
428pub fn invalidate_folds(folds: &mut Vec<Fold>, start_row: usize, end_row: usize) {
435 folds.retain(|f| f.end_row < start_row || f.start_row > end_row);
436}
437
438pub fn shift_fold(
476 fold: Fold,
477 edit_start: usize,
478 drop_end: usize,
479 shift_threshold: usize,
480 delta: isize,
481) -> Option<Fold> {
482 if delta == 0 {
483 return Some(fold);
484 }
485 let map_row = |row: usize| -> Option<usize> {
486 if (edit_start..drop_end).contains(&row) {
487 None
488 } else if row >= shift_threshold {
489 Some(((row as isize) + delta).max(0) as usize)
490 } else {
491 Some(row)
492 }
493 };
494 let mapped_start = map_row(fold.start_row);
495 let mapped_end = map_row(fold.end_row);
496 if mapped_start.is_none() && mapped_end.is_none() {
497 return None;
498 }
499 let new_start = mapped_start.unwrap_or(edit_start);
500 let new_end = mapped_end.unwrap_or_else(|| edit_start.saturating_sub(1));
501 if new_end < new_start {
502 return None;
503 }
504 Some(Fold {
505 start_row: new_start,
506 end_row: new_end,
507 closed: fold.closed,
508 auto_generated: fold.auto_generated,
509 })
510}
511
512pub fn shift_folds_after_edit(
520 folds: &mut Vec<Fold>,
521 edit_start: usize,
522 drop_end: usize,
523 shift_threshold: usize,
524 delta: isize,
525) {
526 if delta == 0 {
527 return;
528 }
529 folds.retain_mut(
530 |f| match shift_fold(*f, edit_start, drop_end, shift_threshold, delta) {
531 Some(shifted) => {
532 *f = shifted;
533 true
534 }
535 None => false,
536 },
537 );
538}
539
540#[cfg(test)]
541mod tests {
542 use crate::View;
543
544 fn b() -> View {
545 View::from_str("a\nb\nc\nd\ne")
546 }
547
548 #[test]
549 fn add_keeps_folds_in_start_row_order() {
550 let mut buf = b();
551 buf.add_fold(2, 3, true);
552 buf.add_fold(0, 1, false);
553 let starts: Vec<usize> = buf.folds().iter().map(|f| f.start_row).collect();
554 assert_eq!(starts, vec![0, 2]);
555 }
556
557 #[test]
558 fn set_folds_replaces_wholesale() {
559 let mut buf = b();
560 buf.add_fold(0, 1, false);
561 let snapshot = vec![super::Fold {
563 start_row: 2,
564 end_row: 3,
565 closed: true,
566 auto_generated: false,
567 }];
568 buf.set_folds(&snapshot);
569 assert_eq!(buf.folds(), snapshot);
570 let dg = buf.dirty_gen();
572 buf.set_folds(&snapshot);
573 assert_eq!(buf.dirty_gen(), dg);
574 }
575
576 #[test]
577 fn invalidate_folds_helper_drops_overlapping() {
578 let f = |s, e| super::Fold {
579 start_row: s,
580 end_row: e,
581 closed: true,
582 auto_generated: false,
583 };
584 let mut folds = vec![f(0, 2), f(4, 6), f(8, 10)];
585 super::invalidate_folds(&mut folds, 5, 5);
587 let starts: Vec<usize> = folds.iter().map(|x| x.start_row).collect();
588 assert_eq!(starts, vec![0, 8]);
589 }
590
591 #[test]
592 fn add_replaces_existing_with_same_start_row() {
593 let mut buf = b();
594 buf.add_fold(1, 2, true);
595 buf.add_fold(1, 4, false);
596 assert_eq!(buf.folds().len(), 1);
597 assert_eq!(buf.folds()[0].end_row, 4);
598 assert!(!buf.folds()[0].closed);
599 }
600
601 #[test]
602 fn add_clamps_end_row_to_buffer_bounds() {
603 let mut buf = b();
604 buf.add_fold(2, 99, true);
605 assert_eq!(buf.folds()[0].end_row, 4);
606 }
607
608 #[test]
609 fn add_rejects_inverted_range() {
610 let mut buf = b();
611 buf.add_fold(3, 1, true);
612 assert!(buf.folds().is_empty());
613 }
614
615 #[test]
616 fn toggle_flips_state() {
617 let mut buf = b();
618 buf.add_fold(1, 3, false);
619 assert!(!buf.folds()[0].closed);
620 assert!(buf.toggle_fold_at(2));
621 assert!(buf.folds()[0].closed);
622 assert!(buf.toggle_fold_at(2));
623 assert!(!buf.folds()[0].closed);
624 }
625
626 #[test]
627 fn is_row_hidden_excludes_start_row() {
628 let mut buf = b();
629 buf.add_fold(1, 3, true);
630 assert!(!buf.is_row_hidden(0));
631 assert!(!buf.is_row_hidden(1)); assert!(buf.is_row_hidden(2));
633 assert!(buf.is_row_hidden(3));
634 assert!(!buf.is_row_hidden(4));
635 }
636
637 #[test]
638 fn open_close_all_changes_every_fold() {
639 let mut buf = b();
640 buf.add_fold(0, 1, false);
641 buf.add_fold(2, 3, true);
642 buf.close_all_folds();
643 assert!(buf.folds().iter().all(|f| f.closed));
644 buf.open_all_folds();
645 assert!(buf.folds().iter().all(|f| !f.closed));
646 }
647
648 #[test]
649 fn invalidate_drops_overlapping_folds() {
650 let mut buf = b();
651 buf.add_fold(0, 1, true);
652 buf.add_fold(2, 3, true);
653 buf.add_fold(4, 4, true);
654 buf.invalidate_folds_in_range(2, 3);
655 let starts: Vec<usize> = buf.folds().iter().map(|f| f.start_row).collect();
656 assert_eq!(starts, vec![0, 4]);
657 }
658
659 #[test]
662 fn add_fold_sets_auto_generated_false() {
663 let mut buf = b();
664 buf.add_fold(1, 3, false);
665 assert!(
666 !buf.folds()[0].auto_generated,
667 "manual add_fold must have auto_generated=false"
668 );
669 }
670
671 #[test]
672 fn set_auto_folds_adds_auto_folds() {
673 let mut buf = b();
674 buf.set_auto_folds(&[(0, 2), (3, 4)], false);
675 let folds = buf.folds();
676 assert_eq!(folds.len(), 2);
677 assert!(folds[0].auto_generated);
678 assert!(folds[1].auto_generated);
679 assert_eq!(folds[0].start_row, 0);
680 assert_eq!(folds[1].start_row, 3);
681 }
682
683 #[test]
684 fn set_auto_folds_second_call_replaces_first() {
685 let mut buf = b();
686 buf.set_auto_folds(&[(0, 2), (3, 4)], false);
687 assert_eq!(buf.folds().len(), 2);
688 buf.set_auto_folds(&[(1, 4)], false);
690 let folds = buf.folds();
691 assert_eq!(folds.len(), 1, "second call must replace first set");
692 assert_eq!(folds[0].start_row, 1);
693 assert!(folds[0].auto_generated);
694 }
695
696 #[test]
697 fn set_auto_folds_preserves_manual_folds() {
698 let mut buf = b();
699 buf.add_fold(0, 1, true);
701 buf.set_auto_folds(&[(2, 4)], false);
703 let folds = buf.folds();
704 assert_eq!(folds.len(), 2, "manual fold must survive set_auto_folds");
705 let manual = folds.iter().find(|f| f.start_row == 0).unwrap();
706 assert!(!manual.auto_generated, "manual fold flag must stay false");
707 let auto = folds.iter().find(|f| f.start_row == 2).unwrap();
708 assert!(auto.auto_generated);
709 }
710
711 #[test]
712 fn set_auto_folds_preserves_open_closed_state_by_start_row() {
713 let mut buf = b();
714 buf.set_auto_folds(&[(0, 2)], true); assert!(buf.folds()[0].closed, "fold must start closed per default");
717
718 buf.toggle_fold_at(0);
720 assert!(!buf.folds()[0].closed, "fold must now be open");
721
722 buf.set_auto_folds(&[(0, 2)], true); assert!(
725 !buf.folds()[0].closed,
726 "open/closed state must be preserved across set_auto_folds"
727 );
728 }
729
730 #[test]
731 fn set_auto_folds_skips_single_row_and_inverted_ranges() {
732 let mut buf = b();
733 buf.set_auto_folds(&[(1, 1), (3, 2)], false);
734 assert!(
735 buf.folds().is_empty(),
736 "single-row and inverted ranges must be skipped"
737 );
738 }
739
740 #[test]
741 fn set_auto_folds_new_folds_use_default_closed() {
742 let mut buf = b();
743 buf.set_auto_folds(&[(0, 4)], true);
744 assert!(
745 buf.folds()[0].closed,
746 "new auto fold must use default_closed=true"
747 );
748
749 buf.set_auto_folds(&[(0, 4)], false);
751 let mut buf2 = b();
757 buf2.set_auto_folds(&[(2, 4)], false);
758 assert!(
759 !buf2.folds()[0].closed,
760 "brand-new auto fold must start open when default_closed=false"
761 );
762 }
763
764 fn fold(s: usize, e: usize) -> super::Fold {
767 super::Fold {
768 start_row: s,
769 end_row: e,
770 closed: true,
771 auto_generated: false,
772 }
773 }
774
775 #[test]
776 fn shift_fold_insert_above_shifts_down() {
777 let f = fold(4, 6);
780 let shifted = super::shift_fold(f, 0, 0, 1, 1).unwrap();
781 assert_eq!((shifted.start_row, shifted.end_row), (5, 7));
782 }
783
784 #[test]
785 fn shift_fold_delete_above_shifts_up() {
786 let f = fold(4, 6);
788 let shifted = super::shift_fold(f, 0, 1, 1, -1).unwrap();
789 assert_eq!((shifted.start_row, shifted.end_row), (3, 5));
790 }
791
792 #[test]
793 fn shift_fold_delete_fully_overlapping_drops() {
794 let f = fold(4, 6);
796 assert!(super::shift_fold(f, 4, 7, 7, -3).is_none());
797 }
798
799 #[test]
800 fn shift_fold_delete_overlapping_tail_clips() {
801 let f = fold(4, 6);
804 let shifted = super::shift_fold(f, 6, 9, 9, -3).unwrap();
805 assert_eq!((shifted.start_row, shifted.end_row), (4, 5));
806 }
807
808 #[test]
809 fn shift_fold_delete_overlapping_head_clips() {
810 let f = fold(4, 6);
813 let shifted = super::shift_fold(f, 3, 5, 5, -2).unwrap();
814 assert_eq!((shifted.start_row, shifted.end_row), (3, 4));
815 }
816
817 #[test]
818 fn shift_fold_edit_inside_grows_on_insert() {
819 let f = fold(4, 6);
822 let shifted = super::shift_fold(f, 5, 5, 6, 1).unwrap();
823 assert_eq!((shifted.start_row, shifted.end_row), (4, 7));
824 }
825
826 #[test]
827 fn shift_fold_edit_inside_shrinks_on_delete() {
828 let f = fold(4, 8);
831 let shifted = super::shift_fold(f, 5, 7, 7, -2).unwrap();
832 assert_eq!((shifted.start_row, shifted.end_row), (4, 6));
833 }
834
835 #[test]
836 fn shift_fold_unaffected_when_entirely_before_edit() {
837 let f = fold(1, 2);
838 let shifted = super::shift_fold(f, 10, 11, 11, 1).unwrap();
839 assert_eq!((shifted.start_row, shifted.end_row), (1, 2));
840 }
841
842 #[test]
843 fn shift_fold_zero_delta_is_noop() {
844 let f = fold(4, 6);
845 let shifted = super::shift_fold(f, 0, 0, 0, 0).unwrap();
846 assert_eq!(shifted, f);
847 }
848
849 #[test]
850 fn shift_folds_after_edit_shifts_vec_in_place() {
851 let mut folds = vec![fold(4, 6), fold(1, 2)];
852 super::shift_folds_after_edit(&mut folds, 0, 0, 1, 1);
854 let ranges: Vec<(usize, usize)> = folds.iter().map(|f| (f.start_row, f.end_row)).collect();
855 assert_eq!(ranges, vec![(5, 7), (2, 3)]);
856 }
857
858 #[test]
859 fn shift_folds_after_edit_drops_fully_consumed() {
860 let mut folds = vec![fold(4, 6)];
861 super::shift_folds_after_edit(&mut folds, 4, 7, 7, -3);
862 assert!(folds.is_empty());
863 }
864
865 #[test]
866 fn rebase_folds_shifts_buffer_fold_storage() {
867 let mut buf = View::from_str("0\n1\n2\n3\n4\n5\n6\n7\n8\n9");
868 buf.add_fold(4, 6, true);
869 buf.rebase_folds(0, 0, 1, 1);
870 let folds = buf.folds();
871 assert_eq!(folds.len(), 1);
872 assert_eq!((folds[0].start_row, folds[0].end_row), (5, 7));
873 }
874}