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 self.content_lock_mut()
390 .folds
391 .retain(|f| f.end_row < start_row || f.start_row > end_row);
392 if self.content_lock().folds.len() != before {
393 self.dirty_gen_bump();
394 }
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use crate::Buffer;
401
402 fn b() -> Buffer {
403 Buffer::from_str("a\nb\nc\nd\ne")
404 }
405
406 #[test]
407 fn add_keeps_folds_in_start_row_order() {
408 let mut buf = b();
409 buf.add_fold(2, 3, true);
410 buf.add_fold(0, 1, false);
411 let starts: Vec<usize> = buf.folds().iter().map(|f| f.start_row).collect();
412 assert_eq!(starts, vec![0, 2]);
413 }
414
415 #[test]
416 fn add_replaces_existing_with_same_start_row() {
417 let mut buf = b();
418 buf.add_fold(1, 2, true);
419 buf.add_fold(1, 4, false);
420 assert_eq!(buf.folds().len(), 1);
421 assert_eq!(buf.folds()[0].end_row, 4);
422 assert!(!buf.folds()[0].closed);
423 }
424
425 #[test]
426 fn add_clamps_end_row_to_buffer_bounds() {
427 let mut buf = b();
428 buf.add_fold(2, 99, true);
429 assert_eq!(buf.folds()[0].end_row, 4);
430 }
431
432 #[test]
433 fn add_rejects_inverted_range() {
434 let mut buf = b();
435 buf.add_fold(3, 1, true);
436 assert!(buf.folds().is_empty());
437 }
438
439 #[test]
440 fn toggle_flips_state() {
441 let mut buf = b();
442 buf.add_fold(1, 3, false);
443 assert!(!buf.folds()[0].closed);
444 assert!(buf.toggle_fold_at(2));
445 assert!(buf.folds()[0].closed);
446 assert!(buf.toggle_fold_at(2));
447 assert!(!buf.folds()[0].closed);
448 }
449
450 #[test]
451 fn is_row_hidden_excludes_start_row() {
452 let mut buf = b();
453 buf.add_fold(1, 3, true);
454 assert!(!buf.is_row_hidden(0));
455 assert!(!buf.is_row_hidden(1)); assert!(buf.is_row_hidden(2));
457 assert!(buf.is_row_hidden(3));
458 assert!(!buf.is_row_hidden(4));
459 }
460
461 #[test]
462 fn open_close_all_changes_every_fold() {
463 let mut buf = b();
464 buf.add_fold(0, 1, false);
465 buf.add_fold(2, 3, true);
466 buf.close_all_folds();
467 assert!(buf.folds().iter().all(|f| f.closed));
468 buf.open_all_folds();
469 assert!(buf.folds().iter().all(|f| !f.closed));
470 }
471
472 #[test]
473 fn invalidate_drops_overlapping_folds() {
474 let mut buf = b();
475 buf.add_fold(0, 1, true);
476 buf.add_fold(2, 3, true);
477 buf.add_fold(4, 4, true);
478 buf.invalidate_folds_in_range(2, 3);
479 let starts: Vec<usize> = buf.folds().iter().map(|f| f.start_row).collect();
480 assert_eq!(starts, vec![0, 4]);
481 }
482
483 #[test]
486 fn add_fold_sets_auto_generated_false() {
487 let mut buf = b();
488 buf.add_fold(1, 3, false);
489 assert!(
490 !buf.folds()[0].auto_generated,
491 "manual add_fold must have auto_generated=false"
492 );
493 }
494
495 #[test]
496 fn set_auto_folds_adds_auto_folds() {
497 let mut buf = b();
498 buf.set_auto_folds(&[(0, 2), (3, 4)], false);
499 let folds = buf.folds();
500 assert_eq!(folds.len(), 2);
501 assert!(folds[0].auto_generated);
502 assert!(folds[1].auto_generated);
503 assert_eq!(folds[0].start_row, 0);
504 assert_eq!(folds[1].start_row, 3);
505 }
506
507 #[test]
508 fn set_auto_folds_second_call_replaces_first() {
509 let mut buf = b();
510 buf.set_auto_folds(&[(0, 2), (3, 4)], false);
511 assert_eq!(buf.folds().len(), 2);
512 buf.set_auto_folds(&[(1, 4)], false);
514 let folds = buf.folds();
515 assert_eq!(folds.len(), 1, "second call must replace first set");
516 assert_eq!(folds[0].start_row, 1);
517 assert!(folds[0].auto_generated);
518 }
519
520 #[test]
521 fn set_auto_folds_preserves_manual_folds() {
522 let mut buf = b();
523 buf.add_fold(0, 1, true);
525 buf.set_auto_folds(&[(2, 4)], false);
527 let folds = buf.folds();
528 assert_eq!(folds.len(), 2, "manual fold must survive set_auto_folds");
529 let manual = folds.iter().find(|f| f.start_row == 0).unwrap();
530 assert!(!manual.auto_generated, "manual fold flag must stay false");
531 let auto = folds.iter().find(|f| f.start_row == 2).unwrap();
532 assert!(auto.auto_generated);
533 }
534
535 #[test]
536 fn set_auto_folds_preserves_open_closed_state_by_start_row() {
537 let mut buf = b();
538 buf.set_auto_folds(&[(0, 2)], true); assert!(buf.folds()[0].closed, "fold must start closed per default");
541
542 buf.toggle_fold_at(0);
544 assert!(!buf.folds()[0].closed, "fold must now be open");
545
546 buf.set_auto_folds(&[(0, 2)], true); assert!(
549 !buf.folds()[0].closed,
550 "open/closed state must be preserved across set_auto_folds"
551 );
552 }
553
554 #[test]
555 fn set_auto_folds_skips_single_row_and_inverted_ranges() {
556 let mut buf = b();
557 buf.set_auto_folds(&[(1, 1), (3, 2)], false);
558 assert!(
559 buf.folds().is_empty(),
560 "single-row and inverted ranges must be skipped"
561 );
562 }
563
564 #[test]
565 fn set_auto_folds_new_folds_use_default_closed() {
566 let mut buf = b();
567 buf.set_auto_folds(&[(0, 4)], true);
568 assert!(
569 buf.folds()[0].closed,
570 "new auto fold must use default_closed=true"
571 );
572
573 buf.set_auto_folds(&[(0, 4)], false);
575 let mut buf2 = b();
581 buf2.set_auto_folds(&[(2, 4)], false);
582 assert!(
583 !buf2.folds()[0].closed,
584 "brand-new auto fold must start open when default_closed=false"
585 );
586 }
587}