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 fn last_content_row(&self) -> usize {
373 let raw_last = self.row_count().saturating_sub(1);
374 if raw_last > 0 {
375 let c = self.content_lock();
376 if crate::buffer::rope_line_str(&c.text, raw_last).is_empty() {
377 return raw_last - 1;
378 }
379 }
380 raw_last
381 }
382
383 pub fn next_visible_row(&self, row: usize) -> Option<usize> {
386 let last = self.last_content_row();
387 if last == 0 && row == 0 {
388 return None;
389 }
390 let mut r = row.checked_add(1)?;
391 while r <= last && self.is_row_hidden(r) {
392 r += 1;
393 }
394 (r <= last).then_some(r)
395 }
396
397 pub fn prev_visible_row(&self, row: usize) -> Option<usize> {
399 let mut r = row.checked_sub(1)?;
400 while self.is_row_hidden(r) {
401 r = r.checked_sub(1)?;
402 }
403 Some(r)
404 }
405
406 pub fn invalidate_folds_in_range(&mut self, start_row: usize, end_row: usize) {
408 let before = self.content_lock().folds.len();
409 invalidate_folds(&mut self.content_lock_mut().folds, start_row, end_row);
410 if self.content_lock().folds.len() != before {
411 self.dirty_gen_bump();
412 }
413 }
414
415 pub fn rebase_folds(
419 &mut self,
420 edit_start: usize,
421 drop_end: usize,
422 shift_threshold: usize,
423 delta: isize,
424 ) {
425 if delta == 0 {
426 return;
427 }
428 let mut c = self.content_lock_mut();
429 shift_folds_after_edit(&mut c.folds, edit_start, drop_end, shift_threshold, delta);
430 }
431
432 pub fn set_folds(&mut self, folds: &[Fold]) {
437 {
438 let mut c = self.content_lock_mut();
439 if c.folds.as_slice() == folds {
440 return; }
442 c.folds = folds.to_vec();
443 }
444 self.dirty_gen_bump();
445 }
446}
447
448pub fn invalidate_folds(folds: &mut Vec<Fold>, start_row: usize, end_row: usize) {
455 folds.retain(|f| f.end_row < start_row || f.start_row > end_row);
456}
457
458pub fn shift_fold(
496 fold: Fold,
497 edit_start: usize,
498 drop_end: usize,
499 shift_threshold: usize,
500 delta: isize,
501) -> Option<Fold> {
502 if delta == 0 {
503 return Some(fold);
504 }
505 let map_row = |row: usize| -> Option<usize> {
506 if (edit_start..drop_end).contains(&row) {
507 None
508 } else if row >= shift_threshold {
509 Some(((row as isize) + delta).max(0) as usize)
510 } else {
511 Some(row)
512 }
513 };
514 let mapped_start = map_row(fold.start_row);
515 let mapped_end = map_row(fold.end_row);
516 if mapped_start.is_none() && mapped_end.is_none() {
517 return None;
518 }
519 let new_start = mapped_start.unwrap_or(edit_start);
520 let new_end = mapped_end.unwrap_or_else(|| edit_start.saturating_sub(1));
521 if new_end < new_start {
522 return None;
523 }
524 Some(Fold {
525 start_row: new_start,
526 end_row: new_end,
527 closed: fold.closed,
528 auto_generated: fold.auto_generated,
529 })
530}
531
532pub fn shift_folds_after_edit(
540 folds: &mut Vec<Fold>,
541 edit_start: usize,
542 drop_end: usize,
543 shift_threshold: usize,
544 delta: isize,
545) {
546 if delta == 0 {
547 return;
548 }
549 folds.retain_mut(
550 |f| match shift_fold(*f, edit_start, drop_end, shift_threshold, delta) {
551 Some(shifted) => {
552 *f = shifted;
553 true
554 }
555 None => false,
556 },
557 );
558}
559
560#[cfg(test)]
561mod tests {
562 use crate::View;
563
564 fn b() -> View {
565 View::from_str("a\nb\nc\nd\ne")
566 }
567
568 #[test]
569 fn add_keeps_folds_in_start_row_order() {
570 let mut buf = b();
571 buf.add_fold(2, 3, true);
572 buf.add_fold(0, 1, false);
573 let starts: Vec<usize> = buf.folds().iter().map(|f| f.start_row).collect();
574 assert_eq!(starts, vec![0, 2]);
575 }
576
577 #[test]
578 fn set_folds_replaces_wholesale() {
579 let mut buf = b();
580 buf.add_fold(0, 1, false);
581 let snapshot = vec![super::Fold {
583 start_row: 2,
584 end_row: 3,
585 closed: true,
586 auto_generated: false,
587 }];
588 buf.set_folds(&snapshot);
589 assert_eq!(buf.folds(), snapshot);
590 let dg = buf.dirty_gen();
592 buf.set_folds(&snapshot);
593 assert_eq!(buf.dirty_gen(), dg);
594 }
595
596 #[test]
597 fn invalidate_folds_helper_drops_overlapping() {
598 let f = |s, e| super::Fold {
599 start_row: s,
600 end_row: e,
601 closed: true,
602 auto_generated: false,
603 };
604 let mut folds = vec![f(0, 2), f(4, 6), f(8, 10)];
605 super::invalidate_folds(&mut folds, 5, 5);
607 let starts: Vec<usize> = folds.iter().map(|x| x.start_row).collect();
608 assert_eq!(starts, vec![0, 8]);
609 }
610
611 #[test]
612 fn add_replaces_existing_with_same_start_row() {
613 let mut buf = b();
614 buf.add_fold(1, 2, true);
615 buf.add_fold(1, 4, false);
616 assert_eq!(buf.folds().len(), 1);
617 assert_eq!(buf.folds()[0].end_row, 4);
618 assert!(!buf.folds()[0].closed);
619 }
620
621 #[test]
622 fn add_clamps_end_row_to_buffer_bounds() {
623 let mut buf = b();
624 buf.add_fold(2, 99, true);
625 assert_eq!(buf.folds()[0].end_row, 4);
626 }
627
628 #[test]
629 fn add_rejects_inverted_range() {
630 let mut buf = b();
631 buf.add_fold(3, 1, true);
632 assert!(buf.folds().is_empty());
633 }
634
635 #[test]
636 fn toggle_flips_state() {
637 let mut buf = b();
638 buf.add_fold(1, 3, false);
639 assert!(!buf.folds()[0].closed);
640 assert!(buf.toggle_fold_at(2));
641 assert!(buf.folds()[0].closed);
642 assert!(buf.toggle_fold_at(2));
643 assert!(!buf.folds()[0].closed);
644 }
645
646 #[test]
647 fn is_row_hidden_excludes_start_row() {
648 let mut buf = b();
649 buf.add_fold(1, 3, true);
650 assert!(!buf.is_row_hidden(0));
651 assert!(!buf.is_row_hidden(1)); assert!(buf.is_row_hidden(2));
653 assert!(buf.is_row_hidden(3));
654 assert!(!buf.is_row_hidden(4));
655 }
656
657 #[test]
658 fn open_close_all_changes_every_fold() {
659 let mut buf = b();
660 buf.add_fold(0, 1, false);
661 buf.add_fold(2, 3, true);
662 buf.close_all_folds();
663 assert!(buf.folds().iter().all(|f| f.closed));
664 buf.open_all_folds();
665 assert!(buf.folds().iter().all(|f| !f.closed));
666 }
667
668 #[test]
669 fn invalidate_drops_overlapping_folds() {
670 let mut buf = b();
671 buf.add_fold(0, 1, true);
672 buf.add_fold(2, 3, true);
673 buf.add_fold(4, 4, true);
674 buf.invalidate_folds_in_range(2, 3);
675 let starts: Vec<usize> = buf.folds().iter().map(|f| f.start_row).collect();
676 assert_eq!(starts, vec![0, 4]);
677 }
678
679 #[test]
682 fn add_fold_sets_auto_generated_false() {
683 let mut buf = b();
684 buf.add_fold(1, 3, false);
685 assert!(
686 !buf.folds()[0].auto_generated,
687 "manual add_fold must have auto_generated=false"
688 );
689 }
690
691 #[test]
692 fn set_auto_folds_adds_auto_folds() {
693 let mut buf = b();
694 buf.set_auto_folds(&[(0, 2), (3, 4)], false);
695 let folds = buf.folds();
696 assert_eq!(folds.len(), 2);
697 assert!(folds[0].auto_generated);
698 assert!(folds[1].auto_generated);
699 assert_eq!(folds[0].start_row, 0);
700 assert_eq!(folds[1].start_row, 3);
701 }
702
703 #[test]
704 fn set_auto_folds_second_call_replaces_first() {
705 let mut buf = b();
706 buf.set_auto_folds(&[(0, 2), (3, 4)], false);
707 assert_eq!(buf.folds().len(), 2);
708 buf.set_auto_folds(&[(1, 4)], false);
710 let folds = buf.folds();
711 assert_eq!(folds.len(), 1, "second call must replace first set");
712 assert_eq!(folds[0].start_row, 1);
713 assert!(folds[0].auto_generated);
714 }
715
716 #[test]
717 fn set_auto_folds_preserves_manual_folds() {
718 let mut buf = b();
719 buf.add_fold(0, 1, true);
721 buf.set_auto_folds(&[(2, 4)], false);
723 let folds = buf.folds();
724 assert_eq!(folds.len(), 2, "manual fold must survive set_auto_folds");
725 let manual = folds.iter().find(|f| f.start_row == 0).unwrap();
726 assert!(!manual.auto_generated, "manual fold flag must stay false");
727 let auto = folds.iter().find(|f| f.start_row == 2).unwrap();
728 assert!(auto.auto_generated);
729 }
730
731 #[test]
732 fn set_auto_folds_preserves_open_closed_state_by_start_row() {
733 let mut buf = b();
734 buf.set_auto_folds(&[(0, 2)], true); assert!(buf.folds()[0].closed, "fold must start closed per default");
737
738 buf.toggle_fold_at(0);
740 assert!(!buf.folds()[0].closed, "fold must now be open");
741
742 buf.set_auto_folds(&[(0, 2)], true); assert!(
745 !buf.folds()[0].closed,
746 "open/closed state must be preserved across set_auto_folds"
747 );
748 }
749
750 #[test]
751 fn set_auto_folds_skips_single_row_and_inverted_ranges() {
752 let mut buf = b();
753 buf.set_auto_folds(&[(1, 1), (3, 2)], false);
754 assert!(
755 buf.folds().is_empty(),
756 "single-row and inverted ranges must be skipped"
757 );
758 }
759
760 #[test]
761 fn set_auto_folds_new_folds_use_default_closed() {
762 let mut buf = b();
763 buf.set_auto_folds(&[(0, 4)], true);
764 assert!(
765 buf.folds()[0].closed,
766 "new auto fold must use default_closed=true"
767 );
768
769 buf.set_auto_folds(&[(0, 4)], false);
771 let mut buf2 = b();
777 buf2.set_auto_folds(&[(2, 4)], false);
778 assert!(
779 !buf2.folds()[0].closed,
780 "brand-new auto fold must start open when default_closed=false"
781 );
782 }
783
784 fn fold(s: usize, e: usize) -> super::Fold {
787 super::Fold {
788 start_row: s,
789 end_row: e,
790 closed: true,
791 auto_generated: false,
792 }
793 }
794
795 #[test]
796 fn shift_fold_insert_above_shifts_down() {
797 let f = fold(4, 6);
800 let shifted = super::shift_fold(f, 0, 0, 1, 1).unwrap();
801 assert_eq!((shifted.start_row, shifted.end_row), (5, 7));
802 }
803
804 #[test]
805 fn shift_fold_delete_above_shifts_up() {
806 let f = fold(4, 6);
808 let shifted = super::shift_fold(f, 0, 1, 1, -1).unwrap();
809 assert_eq!((shifted.start_row, shifted.end_row), (3, 5));
810 }
811
812 #[test]
813 fn shift_fold_delete_fully_overlapping_drops() {
814 let f = fold(4, 6);
816 assert!(super::shift_fold(f, 4, 7, 7, -3).is_none());
817 }
818
819 #[test]
820 fn shift_fold_delete_overlapping_tail_clips() {
821 let f = fold(4, 6);
824 let shifted = super::shift_fold(f, 6, 9, 9, -3).unwrap();
825 assert_eq!((shifted.start_row, shifted.end_row), (4, 5));
826 }
827
828 #[test]
829 fn shift_fold_delete_overlapping_head_clips() {
830 let f = fold(4, 6);
833 let shifted = super::shift_fold(f, 3, 5, 5, -2).unwrap();
834 assert_eq!((shifted.start_row, shifted.end_row), (3, 4));
835 }
836
837 #[test]
838 fn shift_fold_edit_inside_grows_on_insert() {
839 let f = fold(4, 6);
842 let shifted = super::shift_fold(f, 5, 5, 6, 1).unwrap();
843 assert_eq!((shifted.start_row, shifted.end_row), (4, 7));
844 }
845
846 #[test]
847 fn shift_fold_edit_inside_shrinks_on_delete() {
848 let f = fold(4, 8);
851 let shifted = super::shift_fold(f, 5, 7, 7, -2).unwrap();
852 assert_eq!((shifted.start_row, shifted.end_row), (4, 6));
853 }
854
855 #[test]
856 fn shift_fold_unaffected_when_entirely_before_edit() {
857 let f = fold(1, 2);
858 let shifted = super::shift_fold(f, 10, 11, 11, 1).unwrap();
859 assert_eq!((shifted.start_row, shifted.end_row), (1, 2));
860 }
861
862 #[test]
863 fn shift_fold_zero_delta_is_noop() {
864 let f = fold(4, 6);
865 let shifted = super::shift_fold(f, 0, 0, 0, 0).unwrap();
866 assert_eq!(shifted, f);
867 }
868
869 #[test]
870 fn shift_folds_after_edit_shifts_vec_in_place() {
871 let mut folds = vec![fold(4, 6), fold(1, 2)];
872 super::shift_folds_after_edit(&mut folds, 0, 0, 1, 1);
874 let ranges: Vec<(usize, usize)> = folds.iter().map(|f| (f.start_row, f.end_row)).collect();
875 assert_eq!(ranges, vec![(5, 7), (2, 3)]);
876 }
877
878 #[test]
879 fn shift_folds_after_edit_drops_fully_consumed() {
880 let mut folds = vec![fold(4, 6)];
881 super::shift_folds_after_edit(&mut folds, 4, 7, 7, -3);
882 assert!(folds.is_empty());
883 }
884
885 #[test]
886 fn rebase_folds_shifts_buffer_fold_storage() {
887 let mut buf = View::from_str("0\n1\n2\n3\n4\n5\n6\n7\n8\n9");
888 buf.add_fold(4, 6, true);
889 buf.rebase_folds(0, 0, 1, 1);
890 let folds = buf.folds();
891 assert_eq!(folds.len(), 1);
892 assert_eq!((folds[0].start_row, folds[0].end_row), (5, 7));
893 }
894}