1use std::borrow::Cow;
28
29use hjkl_buffer::Position;
30use hjkl_buffer::View as RopeBuffer;
31use regex::Regex;
32
33use crate::types::sealed::Sealed;
34use crate::types::{BufferEdit, Cursor, FoldOp, FoldProvider, Pos, Query, Search, View};
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 let last_row = self.row_count().saturating_sub(1);
191 let end_row = end.row.min(last_row);
199 let end_in_bounds = end.row <= last_row;
200 for r in start.row..=end_row {
201 let line = if r < n {
202 hjkl_buffer::rope_line_str(&rope, r)
203 } else {
204 String::new()
205 };
206 let lo = if r == start.row {
207 start.byte_offset(&line).min(line.len())
208 } else {
209 0
210 };
211 if r == end_row && end_in_bounds {
212 let hi = end.byte_offset(&line).min(line.len()).max(lo);
213 out.push_str(&line[lo..hi]);
214 } else {
215 out.push_str(&line[lo..]);
216 if r < last_row {
217 out.push('\n');
218 }
219 }
220 }
221 Cow::Owned(out)
222 }
223}
224
225impl BufferEdit for RopeBuffer {
228 fn insert_at(&mut self, pos: Pos, text: &str) {
229 let at = clamp_to_buf(self, pos_to_position(pos));
230 let _ = self.apply_edit(hjkl_buffer::Edit::InsertStr {
231 at,
232 text: text.to_string(),
233 });
234 }
235
236 fn delete_range(&mut self, range: core::ops::Range<Pos>) {
237 let start = clamp_to_buf(self, pos_to_position(range.start));
238 let end = clamp_to_buf(self, pos_to_position(range.end));
239 if start >= end {
240 return;
241 }
242 let _ = self.apply_edit(hjkl_buffer::Edit::DeleteRange {
243 start,
244 end,
245 kind: hjkl_buffer::MotionKind::Char,
246 });
247 }
248
249 fn replace_range(&mut self, range: core::ops::Range<Pos>, replacement: &str) {
250 let start = clamp_to_buf(self, pos_to_position(range.start));
251 let end = clamp_to_buf(self, pos_to_position(range.end));
252 if start >= end {
253 let _ = self.apply_edit(hjkl_buffer::Edit::InsertStr {
255 at: start,
256 text: replacement.to_string(),
257 });
258 return;
259 }
260 let _ = self.apply_edit(hjkl_buffer::Edit::Replace {
261 start,
262 end,
263 with: replacement.to_string(),
264 });
265 }
266
267 fn replace_all(&mut self, text: &str) {
268 RopeBuffer::replace_all(self, text);
271 }
272}
273
274#[inline]
275fn clamp_to_buf(buf: &RopeBuffer, p: Position) -> Position {
276 buf.clamp_position(p)
277}
278
279impl Search for RopeBuffer {
282 fn find_next(&self, from: Pos, pat: &Regex) -> Option<core::ops::Range<Pos>> {
283 let start = pos_to_position(from);
284 let total = self.row_count();
285 if total == 0 {
286 return None;
287 }
288 let wrap = true;
297 let rope = self.rope();
298 let from_line = hjkl_buffer::rope_line_str(&rope, start.row);
299 let from_byte = start.byte_offset(&from_line).min(from_line.len());
300 if let Some(m) = pat.find_at(&from_line, from_byte) {
301 return Some(byte_range_to_pos_range(
302 start.row,
303 m.start(),
304 start.row,
305 m.end(),
306 &from_line,
307 ));
308 }
309 for offset in 1..total {
310 let row = start.row + offset;
311 if row >= total && !wrap {
312 break;
313 }
314 let row = row % total;
315 if !wrap && row <= start.row {
316 break;
317 }
318 let line = hjkl_buffer::rope_line_str(&rope, row);
319 if let Some(m) = pat.find(&line) {
320 return Some(byte_range_to_pos_range(row, m.start(), row, m.end(), &line));
321 }
322 if row == start.row {
323 break;
324 }
325 }
326 None
327 }
328
329 fn find_prev(&self, from: Pos, pat: &Regex) -> Option<core::ops::Range<Pos>> {
330 let start = pos_to_position(from);
331 let total = self.row_count();
332 if total == 0 {
333 return None;
334 }
335 let wrap = true;
337 let rope = self.rope();
342 let from_line = hjkl_buffer::rope_line_str(&rope, start.row);
343 let from_byte = start.byte_offset(&from_line).min(from_line.len());
344 let mut best: Option<(usize, usize)> = None;
345 for m in pat.find_iter(&from_line) {
346 if m.start() <= from_byte {
347 best = Some((m.start(), m.end()));
348 } else {
349 break;
350 }
351 }
352 if let Some((s, e)) = best {
353 return Some(byte_range_to_pos_range(
354 start.row, s, start.row, e, &from_line,
355 ));
356 }
357 for offset in 1..total {
358 let row = if offset > start.row {
360 if !wrap {
361 break;
362 }
363 total - (offset - start.row)
364 } else {
365 start.row - offset
366 };
367 if !wrap && row >= start.row {
368 break;
369 }
370 let line = hjkl_buffer::rope_line_str(&rope, row);
371 let last = pat.find_iter(&line).last();
372 if let Some(m) = last {
373 return Some(byte_range_to_pos_range(row, m.start(), row, m.end(), &line));
374 }
375 if row == start.row {
376 break;
377 }
378 }
379 None
380 }
381}
382
383#[inline]
384fn byte_range_to_pos_range(
385 s_row: usize,
386 s_byte: usize,
387 e_row: usize,
388 e_byte: usize,
389 line: &str,
390) -> core::ops::Range<Pos> {
391 let s_col = line[..s_byte.min(line.len())].chars().count();
392 let e_col = line[..e_byte.min(line.len())].chars().count();
393 Pos {
394 line: s_row as u32,
395 col: s_col as u32,
396 }..Pos {
397 line: e_row as u32,
398 col: e_col as u32,
399 }
400}
401
402impl View for RopeBuffer {}
405
406pub struct BufferFoldProvider<'a> {
421 buffer: &'a RopeBuffer,
422}
423
424impl<'a> BufferFoldProvider<'a> {
425 pub fn new(buffer: &'a RopeBuffer) -> Self {
426 Self { buffer }
427 }
428}
429
430impl FoldProvider for BufferFoldProvider<'_> {
431 fn next_visible_row(&self, row: usize, _row_count: usize) -> Option<usize> {
432 RopeBuffer::next_visible_row(self.buffer, row)
434 }
435
436 fn prev_visible_row(&self, row: usize) -> Option<usize> {
437 RopeBuffer::prev_visible_row(self.buffer, row)
438 }
439
440 fn is_row_hidden(&self, row: usize) -> bool {
441 RopeBuffer::is_row_hidden(self.buffer, row)
442 }
443
444 fn fold_at_row(&self, row: usize) -> Option<(usize, usize, bool)> {
445 let f = self.buffer.fold_at_row(row)?;
446 Some((f.start_row, f.end_row, f.closed))
447 }
448
449 }
453
454pub struct BufferFoldProviderMut<'a> {
465 buffer: &'a mut RopeBuffer,
466}
467
468impl<'a> BufferFoldProviderMut<'a> {
469 pub fn new(buffer: &'a mut RopeBuffer) -> Self {
470 Self { buffer }
471 }
472}
473
474impl FoldProvider for BufferFoldProviderMut<'_> {
475 fn next_visible_row(&self, row: usize, _row_count: usize) -> Option<usize> {
476 RopeBuffer::next_visible_row(self.buffer, row)
477 }
478
479 fn prev_visible_row(&self, row: usize) -> Option<usize> {
480 RopeBuffer::prev_visible_row(self.buffer, row)
481 }
482
483 fn is_row_hidden(&self, row: usize) -> bool {
484 RopeBuffer::is_row_hidden(self.buffer, row)
485 }
486
487 fn fold_at_row(&self, row: usize) -> Option<(usize, usize, bool)> {
488 let f = self.buffer.fold_at_row(row)?;
489 Some((f.start_row, f.end_row, f.closed))
490 }
491
492 fn apply(&mut self, op: FoldOp) {
493 match op {
494 FoldOp::Add {
495 start_row,
496 end_row,
497 closed,
498 } => {
499 self.buffer.add_fold(start_row, end_row, closed);
500 }
501 FoldOp::RemoveAt(row) => {
502 self.buffer.remove_fold_at(row);
503 }
504 FoldOp::OpenAt(row) => {
505 self.buffer.open_fold_at(row);
506 }
507 FoldOp::CloseAt(row) => {
508 self.buffer.close_fold_at(row);
509 }
510 FoldOp::ToggleAt(row) => {
511 self.buffer.toggle_fold_at(row);
512 }
513 FoldOp::OpenAll => {
514 self.buffer.open_all_folds();
515 }
516 FoldOp::CloseAll => {
517 self.buffer.close_all_folds();
518 }
519 FoldOp::ClearAll => {
520 self.buffer.clear_all_folds();
521 }
522 FoldOp::Invalidate { start_row, end_row } => {
523 self.buffer.invalidate_folds_in_range(start_row, end_row);
524 }
525 _ => {}
528 }
529 }
530
531 fn invalidate_range(&mut self, start_row: usize, end_row: usize) {
532 self.buffer.invalidate_folds_in_range(start_row, end_row);
533 }
534}
535
536pub struct SnapshotFoldProvider {
552 folds: Vec<hjkl_buffer::Fold>,
553 row_count: usize,
554}
555
556impl SnapshotFoldProvider {
557 pub fn from_buffer(buffer: &RopeBuffer) -> Self {
569 let raw_count = buffer.row_count();
570 let raw_last = raw_count.saturating_sub(1);
571 let row_count =
572 if raw_last > 0 && hjkl_buffer::rope_line_str(&buffer.rope(), raw_last).is_empty() {
573 raw_last
574 } else {
575 raw_count
576 };
577 Self {
578 folds: buffer.folds().to_vec(),
579 row_count,
580 }
581 }
582
583 fn snapshot_is_row_hidden(&self, row: usize) -> bool {
587 self.folds.iter().any(|f| f.hides(row))
588 }
589}
590
591impl FoldProvider for SnapshotFoldProvider {
592 fn next_visible_row(&self, row: usize, _row_count: usize) -> Option<usize> {
593 let last = self.row_count.saturating_sub(1);
596 if last == 0 && row == 0 {
597 return None;
598 }
599 let mut r = row.checked_add(1)?;
600 while r <= last && self.snapshot_is_row_hidden(r) {
601 r += 1;
602 }
603 (r <= last).then_some(r)
604 }
605
606 fn prev_visible_row(&self, row: usize) -> Option<usize> {
607 let mut r = row.checked_sub(1)?;
609 while self.snapshot_is_row_hidden(r) {
610 r = r.checked_sub(1)?;
611 }
612 Some(r)
613 }
614
615 fn is_row_hidden(&self, row: usize) -> bool {
616 self.snapshot_is_row_hidden(row)
617 }
618
619 fn fold_at_row(&self, row: usize) -> Option<(usize, usize, bool)> {
620 self.folds
621 .iter()
622 .find(|f| f.contains(row))
623 .map(|f| (f.start_row, f.end_row, f.closed))
624 }
625
626 }
628
629#[cfg(test)]
632mod tests {
633 use super::*;
634
635 #[test]
640 fn rope_buffer_implements_spec_buffer() {
641 fn assert_buffer<B: View>() {}
642 fn assert_cursor<B: Cursor>() {}
643 fn assert_query<B: Query>() {}
644 fn assert_edit<B: BufferEdit>() {}
645 fn assert_search<B: Search>() {}
646 assert_buffer::<RopeBuffer>();
647 assert_cursor::<RopeBuffer>();
648 assert_query::<RopeBuffer>();
649 assert_edit::<RopeBuffer>();
650 assert_search::<RopeBuffer>();
651 }
652
653 #[test]
654 fn cursor_roundtrip() {
655 let mut b = RopeBuffer::from_str("hello\nworld");
656 Cursor::set_cursor(&mut b, Pos::new(1, 3));
657 assert_eq!(Cursor::cursor(&b), Pos::new(1, 3));
658 }
659
660 #[test]
661 fn query_line_count_and_line() {
662 let b = RopeBuffer::from_str("a\nb\nc");
663 assert_eq!(Query::line_count(&b), 3);
664 assert_eq!(Query::line(&b, 0), "a");
665 assert_eq!(Query::line(&b, 2), "c");
666 }
667
668 #[test]
669 fn query_len_bytes_matches_join() {
670 let b = RopeBuffer::from_str("foo\nbar\nbaz");
671 assert_eq!(Query::len_bytes(&b), b.as_string().len());
672 }
673
674 #[test]
675 fn query_slice_single_line_borrows() {
676 let b = RopeBuffer::from_str("hello world");
677 let s = Query::slice(&b, Pos::new(0, 0)..Pos::new(0, 5));
678 assert_eq!(&*s, "hello");
679 assert!(matches!(s, Cow::Owned(_)));
681 }
682
683 #[test]
684 fn query_slice_multiline_allocates() {
685 let b = RopeBuffer::from_str("ab\ncd\nef");
686 let s = Query::slice(&b, Pos::new(0, 1)..Pos::new(2, 1));
687 assert_eq!(&*s, "b\ncd\ne");
688 assert!(matches!(s, Cow::Owned(_)));
689 }
690
691 #[test]
701 fn query_slice_past_end_sentinel_row_has_no_spurious_trailing_newline() {
702 let b = RopeBuffer::from_str("ab\ncd\nef");
703 let s = Query::slice(&b, Pos::new(0, 1)..Pos::new(u32::MAX, u32::MAX));
704 assert_eq!(&*s, "b\ncd\nef");
705 assert!(!s.ends_with('\n'));
706 }
707
708 #[test]
709 fn cursor_byte_offset_and_inverse() {
710 let b = RopeBuffer::from_str("hello\nworld");
711 let p = Pos::new(1, 0);
713 assert_eq!(Cursor::byte_offset(&b, p), 6);
714 assert_eq!(Cursor::pos_at_byte(&b, 6), p);
715 let p2 = Pos::new(1, 3);
717 let off = Cursor::byte_offset(&b, p2);
718 assert_eq!(Cursor::pos_at_byte(&b, off), p2);
719 }
720
721 #[test]
722 fn buffer_edit_insert_delete_replace() {
723 let mut b = RopeBuffer::from_str("hello");
724 BufferEdit::insert_at(&mut b, Pos::new(0, 5), " world");
725 assert_eq!(b.as_string(), "hello world");
726 BufferEdit::delete_range(&mut b, Pos::new(0, 5)..Pos::new(0, 11));
727 assert_eq!(b.as_string(), "hello");
728 BufferEdit::replace_range(&mut b, Pos::new(0, 0)..Pos::new(0, 5), "HI");
729 assert_eq!(b.as_string(), "HI");
730 }
731
732 #[test]
737 fn buffer_edit_default_replace_all_routes_through_replace_range() {
738 struct MockBuf {
739 cursor: Pos,
740 lines: Vec<String>,
741 last_replace_range: Option<core::ops::Range<Pos>>,
742 }
743 impl Sealed for MockBuf {}
744 impl Cursor for MockBuf {
745 fn cursor(&self) -> Pos {
746 self.cursor
747 }
748 fn set_cursor(&mut self, p: Pos) {
749 self.cursor = p;
750 }
751 fn byte_offset(&self, _p: Pos) -> usize {
752 0
753 }
754 fn pos_at_byte(&self, _b: usize) -> Pos {
755 Pos::ORIGIN
756 }
757 }
758 impl Query for MockBuf {
759 fn line_count(&self) -> u32 {
760 self.lines.len() as u32
761 }
762 fn line(&self, idx: u32) -> String {
763 self.lines[idx as usize].clone()
764 }
765 fn len_bytes(&self) -> usize {
766 0
767 }
768 fn slice(&self, _r: core::ops::Range<Pos>) -> Cow<'_, str> {
769 Cow::Borrowed("")
770 }
771 }
772 impl BufferEdit for MockBuf {
773 fn insert_at(&mut self, _p: Pos, _t: &str) {}
774 fn delete_range(&mut self, _r: core::ops::Range<Pos>) {}
775 fn replace_range(&mut self, range: core::ops::Range<Pos>, _t: &str) {
776 self.last_replace_range = Some(range);
777 }
778 }
779 impl Search for MockBuf {
780 fn find_next(&self, _f: Pos, _p: &Regex) -> Option<core::ops::Range<Pos>> {
781 None
782 }
783 fn find_prev(&self, _f: Pos, _p: &Regex) -> Option<core::ops::Range<Pos>> {
784 None
785 }
786 }
787 impl View for MockBuf {}
788
789 let mut m = MockBuf {
790 cursor: Pos::ORIGIN,
791 lines: vec!["hi".into()],
792 last_replace_range: None,
793 };
794 BufferEdit::replace_all(&mut m, "new content");
795 let r = m
796 .last_replace_range
797 .expect("default impl must hit replace_range");
798 assert_eq!(r.start, Pos::ORIGIN);
799 assert_eq!(r.end.line, u32::MAX);
800 assert_eq!(r.end.col, u32::MAX);
801 }
802
803 #[test]
804 fn buffer_edit_replace_all_rebuilds_content() {
805 let mut b = RopeBuffer::from_str("hello\nworld");
806 Cursor::set_cursor(&mut b, Pos::new(1, 3));
807 BufferEdit::replace_all(&mut b, "alpha\nbeta\ngamma");
808 assert_eq!(b.as_string(), "alpha\nbeta\ngamma");
809 assert_eq!(Query::line_count(&b), 3);
810 let c = Cursor::cursor(&b);
812 assert!((c.line as usize) < Query::line_count(&b) as usize);
813 }
814
815 #[test]
816 fn search_find_next_same_row() {
817 let b = RopeBuffer::from_str("abc def abc");
818 let pat = Regex::new("abc").unwrap();
819 let r = Search::find_next(&b, Pos::new(0, 0), &pat).unwrap();
820 assert_eq!(r, Pos::new(0, 0)..Pos::new(0, 3));
821 let r2 = Search::find_next(&b, Pos::new(0, 1), &pat).unwrap();
822 assert_eq!(r2, Pos::new(0, 8)..Pos::new(0, 11));
823 }
824
825 #[test]
826 fn search_find_next_wraps() {
827 let b = RopeBuffer::from_str("foo\nbar\nfoo");
828 let pat = Regex::new("foo").unwrap();
832 let r = Search::find_next(&b, Pos::new(1, 0), &pat).unwrap();
834 assert_eq!(r, Pos::new(2, 0)..Pos::new(2, 3));
835 }
836
837 #[test]
838 fn search_find_prev_same_row() {
839 let b = RopeBuffer::from_str("abc def abc");
840 let pat = Regex::new("abc").unwrap();
841 let r = Search::find_prev(&b, Pos::new(0, 11), &pat).unwrap();
842 assert_eq!(r, Pos::new(0, 8)..Pos::new(0, 11));
843 }
844
845 #[test]
846 fn pos_position_roundtrip() {
847 let p = Pos::new(7, 3);
848 assert_eq!(position_to_pos(pos_to_position(p)), p);
849 }
850
851 #[test]
854 fn fold_provider_mut_apply_add_open_close_toggle() {
855 let mut buf = RopeBuffer::from_str("a\nb\nc\nd\ne");
856 {
857 let mut p = BufferFoldProviderMut::new(&mut buf);
858 p.apply(FoldOp::Add {
859 start_row: 1,
860 end_row: 3,
861 closed: true,
862 });
863 assert_eq!(p.fold_at_row(2), Some((1, 3, true)));
864 p.apply(FoldOp::OpenAt(2));
865 assert_eq!(p.fold_at_row(2), Some((1, 3, false)));
866 p.apply(FoldOp::CloseAt(2));
867 assert_eq!(p.fold_at_row(2), Some((1, 3, true)));
868 p.apply(FoldOp::ToggleAt(2));
869 assert_eq!(p.fold_at_row(2), Some((1, 3, false)));
870 }
871 assert_eq!(buf.folds().len(), 1);
872 }
873
874 #[test]
875 fn fold_provider_mut_apply_open_close_clear_all() {
876 let mut buf = RopeBuffer::from_str("a\nb\nc\nd\ne");
877 buf.add_fold(0, 1, false);
878 buf.add_fold(2, 3, true);
879 {
880 let mut p = BufferFoldProviderMut::new(&mut buf);
881 p.apply(FoldOp::CloseAll);
882 }
883 assert!(buf.folds().iter().all(|f| f.closed));
884 {
885 let mut p = BufferFoldProviderMut::new(&mut buf);
886 p.apply(FoldOp::OpenAll);
887 }
888 assert!(buf.folds().iter().all(|f| !f.closed));
889 {
890 let mut p = BufferFoldProviderMut::new(&mut buf);
891 p.apply(FoldOp::ClearAll);
892 }
893 assert!(buf.folds().is_empty());
894 }
895
896 #[test]
897 fn fold_provider_mut_invalidate_range_drops_overlapping() {
898 let mut buf = RopeBuffer::from_str("a\nb\nc\nd\ne");
899 buf.add_fold(0, 1, true);
900 buf.add_fold(2, 3, true);
901 buf.add_fold(4, 4, true);
902 {
903 let mut p = BufferFoldProviderMut::new(&mut buf);
904 p.invalidate_range(2, 3);
905 }
906 let starts: Vec<usize> = buf.folds().iter().map(|f| f.start_row).collect();
907 assert_eq!(starts, vec![0, 4]);
908 }
909
910 #[test]
911 fn fold_provider_mut_apply_remove_at() {
912 let mut buf = RopeBuffer::from_str("a\nb\nc\nd\ne");
913 buf.add_fold(1, 3, true);
914 {
915 let mut p = BufferFoldProviderMut::new(&mut buf);
916 p.apply(FoldOp::RemoveAt(2));
917 }
918 assert!(buf.folds().is_empty());
919 }
920
921 #[test]
922 fn noop_fold_provider_apply_is_noop() {
923 let mut p = crate::types::NoopFoldProvider;
926 FoldProvider::apply(&mut p, FoldOp::OpenAll);
927 FoldProvider::invalidate_range(&mut p, 0, 5);
928 assert!(!FoldProvider::is_row_hidden(&p, 3));
930 }
931}