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::Buffer {
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 set_folds(&mut self, folds: &[Fold]) {
400 {
401 let mut c = self.content_lock_mut();
402 if c.folds.as_slice() == folds {
403 return; }
405 c.folds = folds.to_vec();
406 }
407 self.dirty_gen_bump();
408 }
409}
410
411pub fn invalidate_folds(folds: &mut Vec<Fold>, start_row: usize, end_row: usize) {
418 folds.retain(|f| f.end_row < start_row || f.start_row > end_row);
419}
420
421#[cfg(test)]
422mod tests {
423 use crate::Buffer;
424
425 fn b() -> Buffer {
426 Buffer::from_str("a\nb\nc\nd\ne")
427 }
428
429 #[test]
430 fn add_keeps_folds_in_start_row_order() {
431 let mut buf = b();
432 buf.add_fold(2, 3, true);
433 buf.add_fold(0, 1, false);
434 let starts: Vec<usize> = buf.folds().iter().map(|f| f.start_row).collect();
435 assert_eq!(starts, vec![0, 2]);
436 }
437
438 #[test]
439 fn set_folds_replaces_wholesale() {
440 let mut buf = b();
441 buf.add_fold(0, 1, false);
442 let snapshot = vec![super::Fold {
444 start_row: 2,
445 end_row: 3,
446 closed: true,
447 auto_generated: false,
448 }];
449 buf.set_folds(&snapshot);
450 assert_eq!(buf.folds(), snapshot);
451 let dg = buf.dirty_gen();
453 buf.set_folds(&snapshot);
454 assert_eq!(buf.dirty_gen(), dg);
455 }
456
457 #[test]
458 fn invalidate_folds_helper_drops_overlapping() {
459 let f = |s, e| super::Fold {
460 start_row: s,
461 end_row: e,
462 closed: true,
463 auto_generated: false,
464 };
465 let mut folds = vec![f(0, 2), f(4, 6), f(8, 10)];
466 super::invalidate_folds(&mut folds, 5, 5);
468 let starts: Vec<usize> = folds.iter().map(|x| x.start_row).collect();
469 assert_eq!(starts, vec![0, 8]);
470 }
471
472 #[test]
473 fn add_replaces_existing_with_same_start_row() {
474 let mut buf = b();
475 buf.add_fold(1, 2, true);
476 buf.add_fold(1, 4, false);
477 assert_eq!(buf.folds().len(), 1);
478 assert_eq!(buf.folds()[0].end_row, 4);
479 assert!(!buf.folds()[0].closed);
480 }
481
482 #[test]
483 fn add_clamps_end_row_to_buffer_bounds() {
484 let mut buf = b();
485 buf.add_fold(2, 99, true);
486 assert_eq!(buf.folds()[0].end_row, 4);
487 }
488
489 #[test]
490 fn add_rejects_inverted_range() {
491 let mut buf = b();
492 buf.add_fold(3, 1, true);
493 assert!(buf.folds().is_empty());
494 }
495
496 #[test]
497 fn toggle_flips_state() {
498 let mut buf = b();
499 buf.add_fold(1, 3, false);
500 assert!(!buf.folds()[0].closed);
501 assert!(buf.toggle_fold_at(2));
502 assert!(buf.folds()[0].closed);
503 assert!(buf.toggle_fold_at(2));
504 assert!(!buf.folds()[0].closed);
505 }
506
507 #[test]
508 fn is_row_hidden_excludes_start_row() {
509 let mut buf = b();
510 buf.add_fold(1, 3, true);
511 assert!(!buf.is_row_hidden(0));
512 assert!(!buf.is_row_hidden(1)); assert!(buf.is_row_hidden(2));
514 assert!(buf.is_row_hidden(3));
515 assert!(!buf.is_row_hidden(4));
516 }
517
518 #[test]
519 fn open_close_all_changes_every_fold() {
520 let mut buf = b();
521 buf.add_fold(0, 1, false);
522 buf.add_fold(2, 3, true);
523 buf.close_all_folds();
524 assert!(buf.folds().iter().all(|f| f.closed));
525 buf.open_all_folds();
526 assert!(buf.folds().iter().all(|f| !f.closed));
527 }
528
529 #[test]
530 fn invalidate_drops_overlapping_folds() {
531 let mut buf = b();
532 buf.add_fold(0, 1, true);
533 buf.add_fold(2, 3, true);
534 buf.add_fold(4, 4, true);
535 buf.invalidate_folds_in_range(2, 3);
536 let starts: Vec<usize> = buf.folds().iter().map(|f| f.start_row).collect();
537 assert_eq!(starts, vec![0, 4]);
538 }
539
540 #[test]
543 fn add_fold_sets_auto_generated_false() {
544 let mut buf = b();
545 buf.add_fold(1, 3, false);
546 assert!(
547 !buf.folds()[0].auto_generated,
548 "manual add_fold must have auto_generated=false"
549 );
550 }
551
552 #[test]
553 fn set_auto_folds_adds_auto_folds() {
554 let mut buf = b();
555 buf.set_auto_folds(&[(0, 2), (3, 4)], false);
556 let folds = buf.folds();
557 assert_eq!(folds.len(), 2);
558 assert!(folds[0].auto_generated);
559 assert!(folds[1].auto_generated);
560 assert_eq!(folds[0].start_row, 0);
561 assert_eq!(folds[1].start_row, 3);
562 }
563
564 #[test]
565 fn set_auto_folds_second_call_replaces_first() {
566 let mut buf = b();
567 buf.set_auto_folds(&[(0, 2), (3, 4)], false);
568 assert_eq!(buf.folds().len(), 2);
569 buf.set_auto_folds(&[(1, 4)], false);
571 let folds = buf.folds();
572 assert_eq!(folds.len(), 1, "second call must replace first set");
573 assert_eq!(folds[0].start_row, 1);
574 assert!(folds[0].auto_generated);
575 }
576
577 #[test]
578 fn set_auto_folds_preserves_manual_folds() {
579 let mut buf = b();
580 buf.add_fold(0, 1, true);
582 buf.set_auto_folds(&[(2, 4)], false);
584 let folds = buf.folds();
585 assert_eq!(folds.len(), 2, "manual fold must survive set_auto_folds");
586 let manual = folds.iter().find(|f| f.start_row == 0).unwrap();
587 assert!(!manual.auto_generated, "manual fold flag must stay false");
588 let auto = folds.iter().find(|f| f.start_row == 2).unwrap();
589 assert!(auto.auto_generated);
590 }
591
592 #[test]
593 fn set_auto_folds_preserves_open_closed_state_by_start_row() {
594 let mut buf = b();
595 buf.set_auto_folds(&[(0, 2)], true); assert!(buf.folds()[0].closed, "fold must start closed per default");
598
599 buf.toggle_fold_at(0);
601 assert!(!buf.folds()[0].closed, "fold must now be open");
602
603 buf.set_auto_folds(&[(0, 2)], true); assert!(
606 !buf.folds()[0].closed,
607 "open/closed state must be preserved across set_auto_folds"
608 );
609 }
610
611 #[test]
612 fn set_auto_folds_skips_single_row_and_inverted_ranges() {
613 let mut buf = b();
614 buf.set_auto_folds(&[(1, 1), (3, 2)], false);
615 assert!(
616 buf.folds().is_empty(),
617 "single-row and inverted ranges must be skipped"
618 );
619 }
620
621 #[test]
622 fn set_auto_folds_new_folds_use_default_closed() {
623 let mut buf = b();
624 buf.set_auto_folds(&[(0, 4)], true);
625 assert!(
626 buf.folds()[0].closed,
627 "new auto fold must use default_closed=true"
628 );
629
630 buf.set_auto_folds(&[(0, 4)], false);
632 let mut buf2 = b();
638 buf2.set_auto_folds(&[(2, 4)], false);
639 assert!(
640 !buf2.folds()[0].closed,
641 "brand-new auto fold must start open when default_closed=false"
642 );
643 }
644}