1use std::borrow::Cow;
28
29use hjkl_buffer::Buffer as RopeBuffer;
30use hjkl_buffer::Position;
31use regex::Regex;
32
33use crate::types::sealed::Sealed;
34use crate::types::{Buffer, BufferEdit, Cursor, FoldOp, FoldProvider, Pos, Query, Search};
35
36#[inline]
46pub(crate) fn pos_to_position(p: Pos) -> Position {
47 Position {
48 row: p.line as usize,
49 col: p.col as usize,
50 }
51}
52
53#[inline]
55pub(crate) fn position_to_pos(p: Position) -> Pos {
56 Pos {
57 line: p.row as u32,
58 col: p.col as u32,
59 }
60}
61
62impl Sealed for RopeBuffer {}
65
66impl Cursor for RopeBuffer {
69 fn cursor(&self) -> Pos {
70 position_to_pos(RopeBuffer::cursor(self))
71 }
72
73 fn set_cursor(&mut self, pos: Pos) {
74 RopeBuffer::set_cursor(self, pos_to_position(pos));
75 }
76
77 fn byte_offset(&self, pos: Pos) -> usize {
78 let p = pos_to_position(pos);
79 let rope = self.rope();
80 let n = rope.len_lines();
81 let row = p.row.min(n);
83 let line_start = rope.line_to_byte(row);
84 if p.row < n {
85 let line = hjkl_buffer::rope_line_str(&rope, p.row);
88 line_start + p.byte_offset(&line)
89 } else {
90 line_start
91 }
92 }
93
94 fn pos_at_byte(&self, byte: usize) -> Pos {
95 let rope = self.rope();
96 let total = rope.len_bytes();
97 if total == 0 {
98 return Pos { line: 0, col: 0 };
99 }
100 let byte_clamped = byte.min(total);
102 let row = rope.byte_to_line(byte_clamped);
104 let line_start = rope.line_to_byte(row);
105 let line = hjkl_buffer::rope_line_str(&rope, row);
106 let mut col_byte = byte_clamped.saturating_sub(line_start);
107 col_byte = col_byte.min(line.len());
109 while col_byte > 0 && !line.is_char_boundary(col_byte) {
113 col_byte -= 1;
114 }
115 let col = line[..col_byte].chars().count();
116 Pos {
117 line: row as u32,
118 col: col as u32,
119 }
120 }
121}
122
123impl Query for RopeBuffer {
126 fn line_count(&self) -> u32 {
127 self.row_count() as u32
128 }
129
130 fn line(&self, idx: u32) -> String {
131 let row = idx as usize;
132 let rope = self.rope();
133 if row >= rope.len_lines() {
135 panic!(
136 "Query::line: index {idx} out of bounds (line_count = {})",
137 self.row_count()
138 );
139 }
140 hjkl_buffer::rope_line_str(&rope, row)
141 }
142
143 fn len_bytes(&self) -> usize {
144 RopeBuffer::byte_len(self)
146 }
147
148 fn dirty_gen(&self) -> u64 {
149 RopeBuffer::dirty_gen(self)
150 }
151
152 fn content_joined(&self) -> std::sync::Arc<String> {
153 RopeBuffer::content_joined(self)
154 }
155
156 fn line_bytes(&self, row: usize) -> usize {
157 let rope = self.rope();
159 hjkl_buffer::rope_line_bytes(&rope, row)
160 }
161
162 fn rope(&self) -> ropey::Rope {
163 RopeBuffer::rope(self)
165 }
166
167 fn slice(&self, range: core::ops::Range<Pos>) -> Cow<'_, str> {
168 let start = pos_to_position(range.start);
169 let end = pos_to_position(range.end);
170 if start >= end {
171 return Cow::Borrowed("");
172 }
173 let rope = self.rope();
174 let n = rope.len_lines();
175 if start.row == end.row {
177 if start.row < n {
178 let line = hjkl_buffer::rope_line_str(&rope, start.row);
179 let lo = start.byte_offset(&line).min(line.len());
180 let hi = end.byte_offset(&line).min(line.len());
181 return Cow::Owned(line[lo..hi].to_owned());
182 }
183 return Cow::Borrowed("");
184 }
185 let mut out = String::new();
187 for r in start.row..=end.row.min(self.row_count().saturating_sub(1)) {
188 let line = if r < n {
189 hjkl_buffer::rope_line_str(&rope, r)
190 } else {
191 String::new()
192 };
193 if r == start.row {
194 let lo = start.byte_offset(&line).min(line.len());
195 out.push_str(&line[lo..]);
196 out.push('\n');
197 } else if r == end.row {
198 let hi = end.byte_offset(&line).min(line.len());
199 out.push_str(&line[..hi]);
200 } else {
201 out.push_str(&line);
202 out.push('\n');
203 }
204 }
205 Cow::Owned(out)
206 }
207}
208
209impl BufferEdit for RopeBuffer {
212 fn insert_at(&mut self, pos: Pos, text: &str) {
213 let at = clamp_to_buf(self, pos_to_position(pos));
214 let _ = self.apply_edit(hjkl_buffer::Edit::InsertStr {
215 at,
216 text: text.to_string(),
217 });
218 }
219
220 fn delete_range(&mut self, range: core::ops::Range<Pos>) {
221 let start = clamp_to_buf(self, pos_to_position(range.start));
222 let end = clamp_to_buf(self, pos_to_position(range.end));
223 if start >= end {
224 return;
225 }
226 let _ = self.apply_edit(hjkl_buffer::Edit::DeleteRange {
227 start,
228 end,
229 kind: hjkl_buffer::MotionKind::Char,
230 });
231 }
232
233 fn replace_range(&mut self, range: core::ops::Range<Pos>, replacement: &str) {
234 let start = clamp_to_buf(self, pos_to_position(range.start));
235 let end = clamp_to_buf(self, pos_to_position(range.end));
236 if start >= end {
237 let _ = self.apply_edit(hjkl_buffer::Edit::InsertStr {
239 at: start,
240 text: replacement.to_string(),
241 });
242 return;
243 }
244 let _ = self.apply_edit(hjkl_buffer::Edit::Replace {
245 start,
246 end,
247 with: replacement.to_string(),
248 });
249 }
250
251 fn replace_all(&mut self, text: &str) {
252 RopeBuffer::replace_all(self, text);
255 }
256}
257
258#[inline]
259fn clamp_to_buf(buf: &RopeBuffer, p: Position) -> Position {
260 buf.clamp_position(p)
261}
262
263impl Search for RopeBuffer {
266 fn find_next(&self, from: Pos, pat: &Regex) -> Option<core::ops::Range<Pos>> {
267 let start = pos_to_position(from);
268 let total = self.row_count();
269 if total == 0 {
270 return None;
271 }
272 let wrap = true;
281 let rope = self.rope();
282 let from_line = hjkl_buffer::rope_line_str(&rope, start.row);
283 let from_byte = start.byte_offset(&from_line).min(from_line.len());
284 if let Some(m) = pat.find_at(&from_line, from_byte) {
285 return Some(byte_range_to_pos_range(
286 start.row,
287 m.start(),
288 start.row,
289 m.end(),
290 &from_line,
291 ));
292 }
293 for offset in 1..total {
294 let row = start.row + offset;
295 if row >= total && !wrap {
296 break;
297 }
298 let row = row % total;
299 if !wrap && row <= start.row {
300 break;
301 }
302 let line = hjkl_buffer::rope_line_str(&rope, row);
303 if let Some(m) = pat.find(&line) {
304 return Some(byte_range_to_pos_range(row, m.start(), row, m.end(), &line));
305 }
306 if row == start.row {
307 break;
308 }
309 }
310 None
311 }
312
313 fn find_prev(&self, from: Pos, pat: &Regex) -> Option<core::ops::Range<Pos>> {
314 let start = pos_to_position(from);
315 let total = self.row_count();
316 if total == 0 {
317 return None;
318 }
319 let wrap = true;
321 let rope = self.rope();
326 let from_line = hjkl_buffer::rope_line_str(&rope, start.row);
327 let from_byte = start.byte_offset(&from_line).min(from_line.len());
328 let mut best: Option<(usize, usize)> = None;
329 for m in pat.find_iter(&from_line) {
330 if m.start() <= from_byte {
331 best = Some((m.start(), m.end()));
332 } else {
333 break;
334 }
335 }
336 if let Some((s, e)) = best {
337 return Some(byte_range_to_pos_range(
338 start.row, s, start.row, e, &from_line,
339 ));
340 }
341 for offset in 1..total {
342 let row = if offset > start.row {
344 if !wrap {
345 break;
346 }
347 total - (offset - start.row)
348 } else {
349 start.row - offset
350 };
351 if !wrap && row >= start.row {
352 break;
353 }
354 let line = hjkl_buffer::rope_line_str(&rope, row);
355 let last = pat.find_iter(&line).last();
356 if let Some(m) = last {
357 return Some(byte_range_to_pos_range(row, m.start(), row, m.end(), &line));
358 }
359 if row == start.row {
360 break;
361 }
362 }
363 None
364 }
365}
366
367#[inline]
368fn byte_range_to_pos_range(
369 s_row: usize,
370 s_byte: usize,
371 e_row: usize,
372 e_byte: usize,
373 line: &str,
374) -> core::ops::Range<Pos> {
375 let s_col = line[..s_byte.min(line.len())].chars().count();
376 let e_col = line[..e_byte.min(line.len())].chars().count();
377 Pos {
378 line: s_row as u32,
379 col: s_col as u32,
380 }..Pos {
381 line: e_row as u32,
382 col: e_col as u32,
383 }
384}
385
386impl Buffer for RopeBuffer {}
389
390pub struct BufferFoldProvider<'a> {
405 buffer: &'a RopeBuffer,
406}
407
408impl<'a> BufferFoldProvider<'a> {
409 pub fn new(buffer: &'a RopeBuffer) -> Self {
410 Self { buffer }
411 }
412}
413
414impl FoldProvider for BufferFoldProvider<'_> {
415 fn next_visible_row(&self, row: usize, _row_count: usize) -> Option<usize> {
416 RopeBuffer::next_visible_row(self.buffer, row)
418 }
419
420 fn prev_visible_row(&self, row: usize) -> Option<usize> {
421 RopeBuffer::prev_visible_row(self.buffer, row)
422 }
423
424 fn is_row_hidden(&self, row: usize) -> bool {
425 RopeBuffer::is_row_hidden(self.buffer, row)
426 }
427
428 fn fold_at_row(&self, row: usize) -> Option<(usize, usize, bool)> {
429 let f = self.buffer.fold_at_row(row)?;
430 Some((f.start_row, f.end_row, f.closed))
431 }
432
433 }
437
438pub struct BufferFoldProviderMut<'a> {
449 buffer: &'a mut RopeBuffer,
450}
451
452impl<'a> BufferFoldProviderMut<'a> {
453 pub fn new(buffer: &'a mut RopeBuffer) -> Self {
454 Self { buffer }
455 }
456}
457
458impl FoldProvider for BufferFoldProviderMut<'_> {
459 fn next_visible_row(&self, row: usize, _row_count: usize) -> Option<usize> {
460 RopeBuffer::next_visible_row(self.buffer, row)
461 }
462
463 fn prev_visible_row(&self, row: usize) -> Option<usize> {
464 RopeBuffer::prev_visible_row(self.buffer, row)
465 }
466
467 fn is_row_hidden(&self, row: usize) -> bool {
468 RopeBuffer::is_row_hidden(self.buffer, row)
469 }
470
471 fn fold_at_row(&self, row: usize) -> Option<(usize, usize, bool)> {
472 let f = self.buffer.fold_at_row(row)?;
473 Some((f.start_row, f.end_row, f.closed))
474 }
475
476 fn apply(&mut self, op: FoldOp) {
477 match op {
478 FoldOp::Add {
479 start_row,
480 end_row,
481 closed,
482 } => {
483 self.buffer.add_fold(start_row, end_row, closed);
484 }
485 FoldOp::RemoveAt(row) => {
486 self.buffer.remove_fold_at(row);
487 }
488 FoldOp::OpenAt(row) => {
489 self.buffer.open_fold_at(row);
490 }
491 FoldOp::CloseAt(row) => {
492 self.buffer.close_fold_at(row);
493 }
494 FoldOp::ToggleAt(row) => {
495 self.buffer.toggle_fold_at(row);
496 }
497 FoldOp::OpenAll => {
498 self.buffer.open_all_folds();
499 }
500 FoldOp::CloseAll => {
501 self.buffer.close_all_folds();
502 }
503 FoldOp::ClearAll => {
504 self.buffer.clear_all_folds();
505 }
506 FoldOp::Invalidate { start_row, end_row } => {
507 self.buffer.invalidate_folds_in_range(start_row, end_row);
508 }
509 }
510 }
511
512 fn invalidate_range(&mut self, start_row: usize, end_row: usize) {
513 self.buffer.invalidate_folds_in_range(start_row, end_row);
514 }
515}
516
517pub struct SnapshotFoldProvider {
533 folds: Vec<hjkl_buffer::Fold>,
534 row_count: usize,
535}
536
537impl SnapshotFoldProvider {
538 pub fn from_buffer(buffer: &RopeBuffer) -> Self {
542 Self {
543 folds: buffer.folds().to_vec(),
544 row_count: buffer.row_count(),
545 }
546 }
547
548 fn snapshot_is_row_hidden(&self, row: usize) -> bool {
552 self.folds.iter().any(|f| f.hides(row))
553 }
554}
555
556impl FoldProvider for SnapshotFoldProvider {
557 fn next_visible_row(&self, row: usize, _row_count: usize) -> Option<usize> {
558 let last = self.row_count.saturating_sub(1);
561 if last == 0 && row == 0 {
562 return None;
563 }
564 let mut r = row.checked_add(1)?;
565 while r <= last && self.snapshot_is_row_hidden(r) {
566 r += 1;
567 }
568 (r <= last).then_some(r)
569 }
570
571 fn prev_visible_row(&self, row: usize) -> Option<usize> {
572 let mut r = row.checked_sub(1)?;
574 while self.snapshot_is_row_hidden(r) {
575 r = r.checked_sub(1)?;
576 }
577 Some(r)
578 }
579
580 fn is_row_hidden(&self, row: usize) -> bool {
581 self.snapshot_is_row_hidden(row)
582 }
583
584 fn fold_at_row(&self, row: usize) -> Option<(usize, usize, bool)> {
585 self.folds
586 .iter()
587 .find(|f| f.contains(row))
588 .map(|f| (f.start_row, f.end_row, f.closed))
589 }
590
591 }
593
594#[cfg(test)]
597mod tests {
598 use super::*;
599
600 #[test]
605 fn rope_buffer_implements_spec_buffer() {
606 fn assert_buffer<B: Buffer>() {}
607 fn assert_cursor<B: Cursor>() {}
608 fn assert_query<B: Query>() {}
609 fn assert_edit<B: BufferEdit>() {}
610 fn assert_search<B: Search>() {}
611 assert_buffer::<RopeBuffer>();
612 assert_cursor::<RopeBuffer>();
613 assert_query::<RopeBuffer>();
614 assert_edit::<RopeBuffer>();
615 assert_search::<RopeBuffer>();
616 }
617
618 #[test]
619 fn cursor_roundtrip() {
620 let mut b = RopeBuffer::from_str("hello\nworld");
621 Cursor::set_cursor(&mut b, Pos::new(1, 3));
622 assert_eq!(Cursor::cursor(&b), Pos::new(1, 3));
623 }
624
625 #[test]
626 fn query_line_count_and_line() {
627 let b = RopeBuffer::from_str("a\nb\nc");
628 assert_eq!(Query::line_count(&b), 3);
629 assert_eq!(Query::line(&b, 0), "a");
630 assert_eq!(Query::line(&b, 2), "c");
631 }
632
633 #[test]
634 fn query_len_bytes_matches_join() {
635 let b = RopeBuffer::from_str("foo\nbar\nbaz");
636 assert_eq!(Query::len_bytes(&b), b.as_string().len());
637 }
638
639 #[test]
640 fn query_slice_single_line_borrows() {
641 let b = RopeBuffer::from_str("hello world");
642 let s = Query::slice(&b, Pos::new(0, 0)..Pos::new(0, 5));
643 assert_eq!(&*s, "hello");
644 assert!(matches!(s, Cow::Owned(_)));
646 }
647
648 #[test]
649 fn query_slice_multiline_allocates() {
650 let b = RopeBuffer::from_str("ab\ncd\nef");
651 let s = Query::slice(&b, Pos::new(0, 1)..Pos::new(2, 1));
652 assert_eq!(&*s, "b\ncd\ne");
653 assert!(matches!(s, Cow::Owned(_)));
654 }
655
656 #[test]
657 fn cursor_byte_offset_and_inverse() {
658 let b = RopeBuffer::from_str("hello\nworld");
659 let p = Pos::new(1, 0);
661 assert_eq!(Cursor::byte_offset(&b, p), 6);
662 assert_eq!(Cursor::pos_at_byte(&b, 6), p);
663 let p2 = Pos::new(1, 3);
665 let off = Cursor::byte_offset(&b, p2);
666 assert_eq!(Cursor::pos_at_byte(&b, off), p2);
667 }
668
669 #[test]
670 fn buffer_edit_insert_delete_replace() {
671 let mut b = RopeBuffer::from_str("hello");
672 BufferEdit::insert_at(&mut b, Pos::new(0, 5), " world");
673 assert_eq!(b.as_string(), "hello world");
674 BufferEdit::delete_range(&mut b, Pos::new(0, 5)..Pos::new(0, 11));
675 assert_eq!(b.as_string(), "hello");
676 BufferEdit::replace_range(&mut b, Pos::new(0, 0)..Pos::new(0, 5), "HI");
677 assert_eq!(b.as_string(), "HI");
678 }
679
680 #[test]
685 fn buffer_edit_default_replace_all_routes_through_replace_range() {
686 struct MockBuf {
687 cursor: Pos,
688 lines: Vec<String>,
689 last_replace_range: Option<core::ops::Range<Pos>>,
690 }
691 impl Sealed for MockBuf {}
692 impl Cursor for MockBuf {
693 fn cursor(&self) -> Pos {
694 self.cursor
695 }
696 fn set_cursor(&mut self, p: Pos) {
697 self.cursor = p;
698 }
699 fn byte_offset(&self, _p: Pos) -> usize {
700 0
701 }
702 fn pos_at_byte(&self, _b: usize) -> Pos {
703 Pos::ORIGIN
704 }
705 }
706 impl Query for MockBuf {
707 fn line_count(&self) -> u32 {
708 self.lines.len() as u32
709 }
710 fn line(&self, idx: u32) -> String {
711 self.lines[idx as usize].clone()
712 }
713 fn len_bytes(&self) -> usize {
714 0
715 }
716 fn slice(&self, _r: core::ops::Range<Pos>) -> Cow<'_, str> {
717 Cow::Borrowed("")
718 }
719 }
720 impl BufferEdit for MockBuf {
721 fn insert_at(&mut self, _p: Pos, _t: &str) {}
722 fn delete_range(&mut self, _r: core::ops::Range<Pos>) {}
723 fn replace_range(&mut self, range: core::ops::Range<Pos>, _t: &str) {
724 self.last_replace_range = Some(range);
725 }
726 }
727 impl Search for MockBuf {
728 fn find_next(&self, _f: Pos, _p: &Regex) -> Option<core::ops::Range<Pos>> {
729 None
730 }
731 fn find_prev(&self, _f: Pos, _p: &Regex) -> Option<core::ops::Range<Pos>> {
732 None
733 }
734 }
735 impl Buffer for MockBuf {}
736
737 let mut m = MockBuf {
738 cursor: Pos::ORIGIN,
739 lines: vec!["hi".into()],
740 last_replace_range: None,
741 };
742 BufferEdit::replace_all(&mut m, "new content");
743 let r = m
744 .last_replace_range
745 .expect("default impl must hit replace_range");
746 assert_eq!(r.start, Pos::ORIGIN);
747 assert_eq!(r.end.line, u32::MAX);
748 assert_eq!(r.end.col, u32::MAX);
749 }
750
751 #[test]
752 fn buffer_edit_replace_all_rebuilds_content() {
753 let mut b = RopeBuffer::from_str("hello\nworld");
754 Cursor::set_cursor(&mut b, Pos::new(1, 3));
755 BufferEdit::replace_all(&mut b, "alpha\nbeta\ngamma");
756 assert_eq!(b.as_string(), "alpha\nbeta\ngamma");
757 assert_eq!(Query::line_count(&b), 3);
758 let c = Cursor::cursor(&b);
760 assert!((c.line as usize) < Query::line_count(&b) as usize);
761 }
762
763 #[test]
764 fn search_find_next_same_row() {
765 let b = RopeBuffer::from_str("abc def abc");
766 let pat = Regex::new("abc").unwrap();
767 let r = Search::find_next(&b, Pos::new(0, 0), &pat).unwrap();
768 assert_eq!(r, Pos::new(0, 0)..Pos::new(0, 3));
769 let r2 = Search::find_next(&b, Pos::new(0, 1), &pat).unwrap();
770 assert_eq!(r2, Pos::new(0, 8)..Pos::new(0, 11));
771 }
772
773 #[test]
774 fn search_find_next_wraps() {
775 let b = RopeBuffer::from_str("foo\nbar\nfoo");
776 let pat = Regex::new("foo").unwrap();
780 let r = Search::find_next(&b, Pos::new(1, 0), &pat).unwrap();
782 assert_eq!(r, Pos::new(2, 0)..Pos::new(2, 3));
783 }
784
785 #[test]
786 fn search_find_prev_same_row() {
787 let b = RopeBuffer::from_str("abc def abc");
788 let pat = Regex::new("abc").unwrap();
789 let r = Search::find_prev(&b, Pos::new(0, 11), &pat).unwrap();
790 assert_eq!(r, Pos::new(0, 8)..Pos::new(0, 11));
791 }
792
793 #[test]
794 fn pos_position_roundtrip() {
795 let p = Pos::new(7, 3);
796 assert_eq!(position_to_pos(pos_to_position(p)), p);
797 }
798
799 #[test]
802 fn fold_provider_mut_apply_add_open_close_toggle() {
803 let mut buf = RopeBuffer::from_str("a\nb\nc\nd\ne");
804 {
805 let mut p = BufferFoldProviderMut::new(&mut buf);
806 p.apply(FoldOp::Add {
807 start_row: 1,
808 end_row: 3,
809 closed: true,
810 });
811 assert_eq!(p.fold_at_row(2), Some((1, 3, true)));
812 p.apply(FoldOp::OpenAt(2));
813 assert_eq!(p.fold_at_row(2), Some((1, 3, false)));
814 p.apply(FoldOp::CloseAt(2));
815 assert_eq!(p.fold_at_row(2), Some((1, 3, true)));
816 p.apply(FoldOp::ToggleAt(2));
817 assert_eq!(p.fold_at_row(2), Some((1, 3, false)));
818 }
819 assert_eq!(buf.folds().len(), 1);
820 }
821
822 #[test]
823 fn fold_provider_mut_apply_open_close_clear_all() {
824 let mut buf = RopeBuffer::from_str("a\nb\nc\nd\ne");
825 buf.add_fold(0, 1, false);
826 buf.add_fold(2, 3, true);
827 {
828 let mut p = BufferFoldProviderMut::new(&mut buf);
829 p.apply(FoldOp::CloseAll);
830 }
831 assert!(buf.folds().iter().all(|f| f.closed));
832 {
833 let mut p = BufferFoldProviderMut::new(&mut buf);
834 p.apply(FoldOp::OpenAll);
835 }
836 assert!(buf.folds().iter().all(|f| !f.closed));
837 {
838 let mut p = BufferFoldProviderMut::new(&mut buf);
839 p.apply(FoldOp::ClearAll);
840 }
841 assert!(buf.folds().is_empty());
842 }
843
844 #[test]
845 fn fold_provider_mut_invalidate_range_drops_overlapping() {
846 let mut buf = RopeBuffer::from_str("a\nb\nc\nd\ne");
847 buf.add_fold(0, 1, true);
848 buf.add_fold(2, 3, true);
849 buf.add_fold(4, 4, true);
850 {
851 let mut p = BufferFoldProviderMut::new(&mut buf);
852 p.invalidate_range(2, 3);
853 }
854 let starts: Vec<usize> = buf.folds().iter().map(|f| f.start_row).collect();
855 assert_eq!(starts, vec![0, 4]);
856 }
857
858 #[test]
859 fn fold_provider_mut_apply_remove_at() {
860 let mut buf = RopeBuffer::from_str("a\nb\nc\nd\ne");
861 buf.add_fold(1, 3, true);
862 {
863 let mut p = BufferFoldProviderMut::new(&mut buf);
864 p.apply(FoldOp::RemoveAt(2));
865 }
866 assert!(buf.folds().is_empty());
867 }
868
869 #[test]
870 fn noop_fold_provider_apply_is_noop() {
871 let mut p = crate::types::NoopFoldProvider;
874 FoldProvider::apply(&mut p, FoldOp::OpenAll);
875 FoldProvider::invalidate_range(&mut p, 0, 5);
876 assert!(!FoldProvider::is_row_hidden(&p, 3));
878 }
879}