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 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 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(Self::cursor(self))
71 }
72
73 fn set_cursor(&mut self, pos: Pos) {
74 Self::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 Self::byte_len(self)
146 }
147
148 fn dirty_gen(&self) -> u64 {
149 Self::dirty_gen(self)
150 }
151
152 fn content_joined(&self) -> std::sync::Arc<String> {
153 Self::content_joined(self)
154 }
155
156 fn line_bytes(&self, row: usize) -> usize {
157 let rope = self.rope();
159 if row >= rope.len_lines() {
164 return 0;
165 }
166 hjkl_buffer::rope_line_bytes(&rope, row)
167 }
168
169 fn rope(&self) -> ropey::Rope {
170 Self::rope(self)
172 }
173
174 fn slice(&self, range: core::ops::Range<Pos>) -> Cow<'_, str> {
175 let start = pos_to_position(range.start);
176 let end = pos_to_position(range.end);
177 if start >= end {
178 return Cow::Borrowed("");
179 }
180 let rope = self.rope();
181 let n = rope.len_lines();
182 if start.row == end.row {
184 if start.row < n {
185 let line = hjkl_buffer::rope_line_str(&rope, start.row);
186 let lo = start.byte_offset(&line).min(line.len());
187 let hi = end.byte_offset(&line).min(line.len());
188 return Cow::Owned(line[lo..hi].to_owned());
189 }
190 return Cow::Borrowed("");
191 }
192 let mut out = String::new();
194 let last_row = self.row_count().saturating_sub(1);
198 let end_row = end.row.min(last_row);
206 let end_in_bounds = end.row <= last_row;
207 for r in start.row..=end_row {
208 let line = if r < n {
209 hjkl_buffer::rope_line_str(&rope, r)
210 } else {
211 String::new()
212 };
213 let lo = if r == start.row {
214 start.byte_offset(&line).min(line.len())
215 } else {
216 0
217 };
218 if r == end_row && end_in_bounds {
219 let hi = end.byte_offset(&line).min(line.len()).max(lo);
220 out.push_str(&line[lo..hi]);
221 } else {
222 out.push_str(&line[lo..]);
223 if r < last_row {
224 out.push('\n');
225 }
226 }
227 }
228 Cow::Owned(out)
229 }
230}
231
232impl BufferEdit for RopeBuffer {
235 fn insert_at(&mut self, pos: Pos, text: &str) {
236 let at = clamp_to_buf(self, pos_to_position(pos));
237 let _ = self.apply_edit(hjkl_buffer::Edit::InsertStr {
238 at,
239 text: text.to_string(),
240 });
241 }
242
243 fn delete_range(&mut self, range: core::ops::Range<Pos>) {
244 let start = clamp_to_buf(self, pos_to_position(range.start));
245 let end = clamp_to_buf(self, pos_to_position(range.end));
246 if start >= end {
247 return;
248 }
249 let _ = self.apply_edit(hjkl_buffer::Edit::DeleteRange {
250 start,
251 end,
252 kind: hjkl_buffer::MotionKind::Char,
253 });
254 }
255
256 fn replace_range(&mut self, range: core::ops::Range<Pos>, replacement: &str) {
257 let start = clamp_to_buf(self, pos_to_position(range.start));
258 let end = clamp_to_buf(self, pos_to_position(range.end));
259 if start >= end {
260 let _ = self.apply_edit(hjkl_buffer::Edit::InsertStr {
262 at: start,
263 text: replacement.to_string(),
264 });
265 return;
266 }
267 let _ = self.apply_edit(hjkl_buffer::Edit::Replace {
268 start,
269 end,
270 with: replacement.to_string(),
271 });
272 }
273
274 fn replace_all(&mut self, text: &str) {
275 Self::replace_all(self, text);
278 }
279}
280
281#[inline]
282fn clamp_to_buf(buf: &RopeBuffer, p: Position) -> Position {
283 buf.clamp_position(p)
284}
285
286impl Search for RopeBuffer {
289 fn find_next(&self, from: Pos, pat: &Regex) -> Option<core::ops::Range<Pos>> {
290 let start = pos_to_position(from);
291 let total = self.row_count();
292 if total == 0 {
293 return None;
294 }
295 let wrap = true;
304 let rope = self.rope();
305 let from_line = crate::viewport_math::rope_line_slice(&rope, start.row);
306 let from_byte = start.byte_offset(&from_line).min(from_line.len());
307 if let Some(m) = pat.find_at(&from_line, from_byte) {
308 return Some(byte_range_to_pos_range(
309 start.row,
310 m.start(),
311 start.row,
312 m.end(),
313 &from_line,
314 ));
315 }
316 for offset in 1..=total {
320 let row = start.row + offset;
321 if row >= total && !wrap {
322 break;
323 }
324 let row = row % total;
325 if !wrap && row <= start.row {
326 break;
327 }
328 let line = crate::viewport_math::rope_line_slice(&rope, row);
329 if let Some(m) = pat.find(&line) {
330 return Some(byte_range_to_pos_range(row, m.start(), row, m.end(), &line));
331 }
332 if row == start.row {
333 break;
334 }
335 }
336 None
337 }
338
339 fn find_prev(&self, from: Pos, pat: &Regex) -> Option<core::ops::Range<Pos>> {
340 let start = pos_to_position(from);
341 let total = self.row_count();
342 if total == 0 {
343 return None;
344 }
345 let wrap = true;
347 let rope = self.rope();
352 let from_line = crate::viewport_math::rope_line_slice(&rope, start.row);
353 let from_byte = start.byte_offset(&from_line).min(from_line.len());
354 let mut best: Option<(usize, usize)> = None;
355 for m in pat.find_iter(&from_line) {
356 if m.start() <= from_byte {
357 best = Some((m.start(), m.end()));
358 } else {
359 break;
360 }
361 }
362 if let Some((s, e)) = best {
363 return Some(byte_range_to_pos_range(
364 start.row, s, start.row, e, &from_line,
365 ));
366 }
367 for offset in 1..=total {
370 let row = if offset > start.row {
372 if !wrap {
373 break;
374 }
375 total - (offset - start.row)
376 } else {
377 start.row - offset
378 };
379 if !wrap && row >= start.row {
380 break;
381 }
382 let line = crate::viewport_math::rope_line_slice(&rope, row);
383 let last = pat.find_iter(&line).last();
384 if let Some(m) = last {
385 return Some(byte_range_to_pos_range(row, m.start(), row, m.end(), &line));
386 }
387 if row == start.row {
388 break;
389 }
390 }
391 None
392 }
393}
394
395#[inline]
396fn byte_range_to_pos_range(
397 s_row: usize,
398 s_byte: usize,
399 e_row: usize,
400 e_byte: usize,
401 line: &str,
402) -> core::ops::Range<Pos> {
403 let s_col = line[..s_byte.min(line.len())].chars().count();
404 let e_col = line[..e_byte.min(line.len())].chars().count();
405 Pos {
406 line: s_row as u32,
407 col: s_col as u32,
408 }..Pos {
409 line: e_row as u32,
410 col: e_col as u32,
411 }
412}
413
414impl View for RopeBuffer {}
417
418pub struct BufferFoldProvider<'a> {
433 buffer: &'a RopeBuffer,
434}
435
436impl<'a> BufferFoldProvider<'a> {
437 pub fn new(buffer: &'a RopeBuffer) -> Self {
438 Self { buffer }
439 }
440}
441
442impl FoldProvider for BufferFoldProvider<'_> {
443 fn next_visible_row(&self, row: usize, _row_count: usize) -> Option<usize> {
444 RopeBuffer::next_visible_row(self.buffer, row)
446 }
447
448 fn prev_visible_row(&self, row: usize) -> Option<usize> {
449 RopeBuffer::prev_visible_row(self.buffer, row)
450 }
451
452 fn is_row_hidden(&self, row: usize) -> bool {
453 RopeBuffer::is_row_hidden(self.buffer, row)
454 }
455
456 fn fold_at_row(&self, row: usize) -> Option<(usize, usize, bool)> {
457 let f = self.buffer.fold_at_row(row)?;
458 Some((f.start_row, f.end_row, f.closed))
459 }
460
461 }
465
466pub struct BufferFoldProviderMut<'a> {
477 buffer: &'a mut RopeBuffer,
478}
479
480impl<'a> BufferFoldProviderMut<'a> {
481 pub fn new(buffer: &'a mut RopeBuffer) -> Self {
482 Self { buffer }
483 }
484}
485
486impl FoldProvider for BufferFoldProviderMut<'_> {
487 fn next_visible_row(&self, row: usize, _row_count: usize) -> Option<usize> {
488 RopeBuffer::next_visible_row(self.buffer, row)
489 }
490
491 fn prev_visible_row(&self, row: usize) -> Option<usize> {
492 RopeBuffer::prev_visible_row(self.buffer, row)
493 }
494
495 fn is_row_hidden(&self, row: usize) -> bool {
496 RopeBuffer::is_row_hidden(self.buffer, row)
497 }
498
499 fn fold_at_row(&self, row: usize) -> Option<(usize, usize, bool)> {
500 let f = self.buffer.fold_at_row(row)?;
501 Some((f.start_row, f.end_row, f.closed))
502 }
503
504 fn apply(&mut self, op: FoldOp) {
505 match op {
506 FoldOp::Add {
507 start_row,
508 end_row,
509 closed,
510 } => {
511 self.buffer.add_fold(start_row, end_row, closed);
512 }
513 FoldOp::RemoveAt(row) => {
514 self.buffer.remove_fold_at(row);
515 }
516 FoldOp::OpenAt(row) => {
517 self.buffer.open_fold_at(row);
518 }
519 FoldOp::CloseAt(row) => {
520 self.buffer.close_fold_at(row);
521 }
522 FoldOp::ToggleAt(row) => {
523 self.buffer.toggle_fold_at(row);
524 }
525 FoldOp::OpenRecursivelyAt(row) => {
526 self.buffer.open_folds_recursively_at(row);
527 }
528 FoldOp::CloseRecursivelyAt(row) => {
529 self.buffer.close_folds_recursively_at(row);
530 }
531 FoldOp::ToggleRecursivelyAt(row) => {
532 self.buffer.toggle_folds_recursively_at(row);
533 }
534 FoldOp::OpenAll => {
535 self.buffer.open_all_folds();
536 }
537 FoldOp::CloseAll => {
538 self.buffer.close_all_folds();
539 }
540 FoldOp::ClearAll => {
541 self.buffer.clear_all_folds();
542 }
543 FoldOp::Invalidate { start_row, end_row } => {
544 self.buffer.invalidate_folds_in_range(start_row, end_row);
545 }
546 _ => {}
549 }
550 }
551
552 fn invalidate_range(&mut self, start_row: usize, end_row: usize) {
553 self.buffer.invalidate_folds_in_range(start_row, end_row);
554 }
555}
556
557pub struct SnapshotFoldProvider {
573 folds: Vec<hjkl_buffer::Fold>,
574 fold_index: hjkl_buffer::FoldIndex,
580 row_count: usize,
581}
582
583impl SnapshotFoldProvider {
584 pub fn from_buffer(buffer: &RopeBuffer) -> Self {
602 let folds = buffer.folds();
603 let fold_index = hjkl_buffer::FoldIndex::new(&folds);
604 Self {
605 folds,
606 fold_index,
607 row_count: buffer.last_content_row() + 1,
608 }
609 }
610
611 fn snapshot_is_row_hidden(&self, row: usize) -> bool {
615 self.fold_index.hides_row(row)
616 }
617}
618
619impl FoldProvider for SnapshotFoldProvider {
620 fn next_visible_row(&self, row: usize, _row_count: usize) -> Option<usize> {
621 let last = self.row_count.saturating_sub(1);
624 if last == 0 && row == 0 {
625 return None;
626 }
627 let mut r = row.checked_add(1)?;
628 while r <= last && self.snapshot_is_row_hidden(r) {
629 r += 1;
630 }
631 (r <= last).then_some(r)
632 }
633
634 fn prev_visible_row(&self, row: usize) -> Option<usize> {
635 let mut r = row.checked_sub(1)?;
637 while self.snapshot_is_row_hidden(r) {
638 r = r.checked_sub(1)?;
639 }
640 Some(r)
641 }
642
643 fn is_row_hidden(&self, row: usize) -> bool {
644 self.snapshot_is_row_hidden(row)
645 }
646
647 fn fold_at_row(&self, row: usize) -> Option<(usize, usize, bool)> {
648 self.folds
649 .iter()
650 .find(|f| f.contains(row))
651 .map(|f| (f.start_row, f.end_row, f.closed))
652 }
653
654 }
656
657#[cfg(test)]
660mod tests {
661 use super::*;
662
663 #[test]
668 fn rope_buffer_implements_spec_buffer() {
669 fn assert_buffer<B: View>() {}
670 fn assert_cursor<B: Cursor>() {}
671 fn assert_query<B: Query>() {}
672 fn assert_edit<B: BufferEdit>() {}
673 fn assert_search<B: Search>() {}
674 assert_buffer::<RopeBuffer>();
675 assert_cursor::<RopeBuffer>();
676 assert_query::<RopeBuffer>();
677 assert_edit::<RopeBuffer>();
678 assert_search::<RopeBuffer>();
679 }
680
681 #[test]
682 fn cursor_roundtrip() {
683 let mut b = RopeBuffer::from_str("hello\nworld");
684 Cursor::set_cursor(&mut b, Pos::new(1, 3));
685 assert_eq!(Cursor::cursor(&b), Pos::new(1, 3));
686 }
687
688 #[test]
689 fn query_line_count_and_line() {
690 let b = RopeBuffer::from_str("a\nb\nc");
691 assert_eq!(Query::line_count(&b), 3);
692 assert_eq!(Query::line(&b, 0), "a");
693 assert_eq!(Query::line(&b, 2), "c");
694 }
695
696 #[test]
703 fn line_bytes_matches_line_len() {
704 let corpus = [
705 "",
706 "a",
707 "a\n",
708 "a\nbb\nccc",
709 "héllo\nwörld",
710 "emoji 🎉 tail\nnext",
711 "tabs\there\nx",
712 "a\r\nb\r\nc",
713 "trailing\n\n",
714 ];
715 for text in corpus {
716 let b = RopeBuffer::from_str(text);
717 let n = Query::line_count(&b) as usize;
718 for row in 0..n {
719 assert_eq!(
720 Query::line_bytes(&b, row),
721 Query::line(&b, row as u32).len(),
722 "row {row} of {text:?}"
723 );
724 }
725 for row in n..n + 3 {
728 assert_eq!(Query::line_bytes(&b, row), 0, "oob row {row} of {text:?}");
729 }
730 }
731 }
732
733 #[test]
746 fn line_helpers_exclude_a_non_lf_line_break() {
747 for text in ["a\rb", "a\u{2028}b", "a\u{0085}b", "a\u{0b}b", "a\u{0c}b"] {
748 let b = RopeBuffer::from_str(text);
749 assert_eq!(Query::line(&b, 0), "a", "{text:?}");
750 assert_eq!(Query::line_bytes(&b, 0), 1, "{text:?}");
751 }
752 }
753
754 #[test]
758 fn crlf_keeps_its_carriage_return() {
759 let b = RopeBuffer::from_str("a\r\nb");
760 assert_eq!(Query::line(&b, 0), "a\r");
761 assert_eq!(Query::line_bytes(&b, 0), 2);
762 }
763
764 #[test]
765 fn query_len_bytes_matches_join() {
766 let b = RopeBuffer::from_str("foo\nbar\nbaz");
767 assert_eq!(Query::len_bytes(&b), b.as_string().len());
768 }
769
770 #[test]
771 fn query_slice_single_line_borrows() {
772 let b = RopeBuffer::from_str("hello world");
773 let s = Query::slice(&b, Pos::new(0, 0)..Pos::new(0, 5));
774 assert_eq!(&*s, "hello");
775 assert!(matches!(s, Cow::Owned(_)));
777 }
778
779 #[test]
780 fn query_slice_multiline_allocates() {
781 let b = RopeBuffer::from_str("ab\ncd\nef");
782 let s = Query::slice(&b, Pos::new(0, 1)..Pos::new(2, 1));
783 assert_eq!(&*s, "b\ncd\ne");
784 assert!(matches!(s, Cow::Owned(_)));
785 }
786
787 #[test]
797 fn query_slice_past_end_sentinel_row_has_no_spurious_trailing_newline() {
798 let b = RopeBuffer::from_str("ab\ncd\nef");
799 let s = Query::slice(&b, Pos::new(0, 1)..Pos::new(u32::MAX, u32::MAX));
800 assert_eq!(&*s, "b\ncd\nef");
801 assert!(!s.ends_with('\n'));
802 }
803
804 #[test]
805 fn cursor_byte_offset_and_inverse() {
806 let b = RopeBuffer::from_str("hello\nworld");
807 let p = Pos::new(1, 0);
809 assert_eq!(Cursor::byte_offset(&b, p), 6);
810 assert_eq!(Cursor::pos_at_byte(&b, 6), p);
811 let p2 = Pos::new(1, 3);
813 let off = Cursor::byte_offset(&b, p2);
814 assert_eq!(Cursor::pos_at_byte(&b, off), p2);
815 }
816
817 #[test]
818 fn buffer_edit_insert_delete_replace() {
819 let mut b = RopeBuffer::from_str("hello");
820 BufferEdit::insert_at(&mut b, Pos::new(0, 5), " world");
821 assert_eq!(b.as_string(), "hello world");
822 BufferEdit::delete_range(&mut b, Pos::new(0, 5)..Pos::new(0, 11));
823 assert_eq!(b.as_string(), "hello");
824 BufferEdit::replace_range(&mut b, Pos::new(0, 0)..Pos::new(0, 5), "HI");
825 assert_eq!(b.as_string(), "HI");
826 }
827
828 #[test]
833 fn buffer_edit_default_replace_all_routes_through_replace_range() {
834 struct MockBuf {
835 cursor: Pos,
836 lines: Vec<String>,
837 last_replace_range: Option<core::ops::Range<Pos>>,
838 }
839 impl Sealed for MockBuf {}
840 impl Cursor for MockBuf {
841 fn cursor(&self) -> Pos {
842 self.cursor
843 }
844 fn set_cursor(&mut self, p: Pos) {
845 self.cursor = p;
846 }
847 fn byte_offset(&self, _p: Pos) -> usize {
848 0
849 }
850 fn pos_at_byte(&self, _b: usize) -> Pos {
851 Pos::ORIGIN
852 }
853 }
854 impl Query for MockBuf {
855 fn line_count(&self) -> u32 {
856 self.lines.len() as u32
857 }
858 fn line(&self, idx: u32) -> String {
859 self.lines[idx as usize].clone()
860 }
861 fn len_bytes(&self) -> usize {
862 0
863 }
864 fn slice(&self, _r: core::ops::Range<Pos>) -> Cow<'_, str> {
865 Cow::Borrowed("")
866 }
867 }
868 impl BufferEdit for MockBuf {
869 fn insert_at(&mut self, _p: Pos, _t: &str) {}
870 fn delete_range(&mut self, _r: core::ops::Range<Pos>) {}
871 fn replace_range(&mut self, range: core::ops::Range<Pos>, _t: &str) {
872 self.last_replace_range = Some(range);
873 }
874 }
875 impl Search for MockBuf {
876 fn find_next(&self, _f: Pos, _p: &Regex) -> Option<core::ops::Range<Pos>> {
877 None
878 }
879 fn find_prev(&self, _f: Pos, _p: &Regex) -> Option<core::ops::Range<Pos>> {
880 None
881 }
882 }
883 impl View for MockBuf {}
884
885 let mut m = MockBuf {
886 cursor: Pos::ORIGIN,
887 lines: vec!["hi".into()],
888 last_replace_range: None,
889 };
890 BufferEdit::replace_all(&mut m, "new content");
891 let r = m
892 .last_replace_range
893 .expect("default impl must hit replace_range");
894 assert_eq!(r.start, Pos::ORIGIN);
895 assert_eq!(r.end.line, u32::MAX);
896 assert_eq!(r.end.col, u32::MAX);
897 }
898
899 #[test]
900 fn buffer_edit_replace_all_rebuilds_content() {
901 let mut b = RopeBuffer::from_str("hello\nworld");
902 Cursor::set_cursor(&mut b, Pos::new(1, 3));
903 BufferEdit::replace_all(&mut b, "alpha\nbeta\ngamma");
904 assert_eq!(b.as_string(), "alpha\nbeta\ngamma");
905 assert_eq!(Query::line_count(&b), 3);
906 let c = Cursor::cursor(&b);
908 assert!((c.line as usize) < Query::line_count(&b) as usize);
909 }
910
911 #[test]
912 fn search_find_next_same_row() {
913 let b = RopeBuffer::from_str("abc def abc");
914 let pat = Regex::new("abc").unwrap();
915 let r = Search::find_next(&b, Pos::new(0, 0), &pat).unwrap();
916 assert_eq!(r, Pos::new(0, 0)..Pos::new(0, 3));
917 let r2 = Search::find_next(&b, Pos::new(0, 1), &pat).unwrap();
918 assert_eq!(r2, Pos::new(0, 8)..Pos::new(0, 11));
919 }
920
921 #[test]
922 fn search_find_next_wraps() {
923 let b = RopeBuffer::from_str("foo\nbar\nfoo");
924 let pat = Regex::new("foo").unwrap();
928 let r = Search::find_next(&b, Pos::new(1, 0), &pat).unwrap();
930 assert_eq!(r, Pos::new(2, 0)..Pos::new(2, 3));
931 }
932
933 #[test]
934 fn search_find_prev_same_row() {
935 let b = RopeBuffer::from_str("abc def abc");
936 let pat = Regex::new("abc").unwrap();
937 let r = Search::find_prev(&b, Pos::new(0, 11), &pat).unwrap();
938 assert_eq!(r, Pos::new(0, 8)..Pos::new(0, 11));
939 }
940
941 #[test]
950 fn search_cross_row_multibyte_borrow_swap() {
951 let b = RopeBuffer::from_str("héllo\nwörld\n🎉 fóo\nbar");
952 let pat = Regex::new("fóo").unwrap();
953 let r = Search::find_next(&b, Pos::new(0, 0), &pat).unwrap();
957 assert_eq!(r, Pos::new(2, 2)..Pos::new(2, 5));
958 let r = Search::find_prev(&b, Pos::new(3, 0), &pat).unwrap();
960 assert_eq!(r, Pos::new(2, 2)..Pos::new(2, 5));
961 }
962
963 #[test]
964 fn pos_position_roundtrip() {
965 let p = Pos::new(7, 3);
966 assert_eq!(position_to_pos(pos_to_position(p)), p);
967 }
968
969 #[test]
972 fn fold_provider_mut_apply_add_open_close_toggle() {
973 let mut buf = RopeBuffer::from_str("a\nb\nc\nd\ne");
974 {
975 let mut p = BufferFoldProviderMut::new(&mut buf);
976 p.apply(FoldOp::Add {
977 start_row: 1,
978 end_row: 3,
979 closed: true,
980 });
981 assert_eq!(p.fold_at_row(2), Some((1, 3, true)));
982 p.apply(FoldOp::OpenAt(2));
983 assert_eq!(p.fold_at_row(2), Some((1, 3, false)));
984 p.apply(FoldOp::CloseAt(2));
985 assert_eq!(p.fold_at_row(2), Some((1, 3, true)));
986 p.apply(FoldOp::ToggleAt(2));
987 assert_eq!(p.fold_at_row(2), Some((1, 3, false)));
988 }
989 assert_eq!(buf.folds().len(), 1);
990 }
991
992 #[test]
993 fn fold_provider_mut_apply_recursive_operations() {
994 let mut buf = RopeBuffer::from_str("a\nb\nc\nd\ne");
995 buf.add_fold(0, 4, false);
996 buf.add_fold(0, 2, false);
997 {
998 let mut p = BufferFoldProviderMut::new(&mut buf);
999 p.apply(FoldOp::CloseRecursivelyAt(0));
1000 }
1001 assert!(buf.folds().iter().all(|fold| fold.closed));
1002 {
1003 let mut p = BufferFoldProviderMut::new(&mut buf);
1004 p.apply(FoldOp::OpenRecursivelyAt(0));
1005 p.apply(FoldOp::ToggleRecursivelyAt(0));
1006 }
1007 assert!(buf.folds().iter().all(|fold| fold.closed));
1008 }
1009
1010 #[test]
1011 fn fold_provider_mut_apply_open_close_clear_all() {
1012 let mut buf = RopeBuffer::from_str("a\nb\nc\nd\ne");
1013 buf.add_fold(0, 1, false);
1014 buf.add_fold(2, 3, true);
1015 {
1016 let mut p = BufferFoldProviderMut::new(&mut buf);
1017 p.apply(FoldOp::CloseAll);
1018 }
1019 assert!(buf.folds().iter().all(|f| f.closed));
1020 {
1021 let mut p = BufferFoldProviderMut::new(&mut buf);
1022 p.apply(FoldOp::OpenAll);
1023 }
1024 assert!(buf.folds().iter().all(|f| !f.closed));
1025 {
1026 let mut p = BufferFoldProviderMut::new(&mut buf);
1027 p.apply(FoldOp::ClearAll);
1028 }
1029 assert!(buf.folds().is_empty());
1030 }
1031
1032 #[test]
1033 fn fold_provider_mut_invalidate_range_drops_overlapping() {
1034 let mut buf = RopeBuffer::from_str("a\nb\nc\nd\ne");
1035 buf.add_fold(0, 1, true);
1036 buf.add_fold(2, 3, true);
1037 buf.add_fold(4, 4, true);
1038 {
1039 let mut p = BufferFoldProviderMut::new(&mut buf);
1040 p.invalidate_range(2, 3);
1041 }
1042 let starts: Vec<usize> = buf.folds().iter().map(|f| f.start_row).collect();
1043 assert_eq!(starts, vec![0, 4]);
1044 }
1045
1046 #[test]
1047 fn fold_provider_mut_apply_remove_at() {
1048 let mut buf = RopeBuffer::from_str("a\nb\nc\nd\ne");
1049 buf.add_fold(1, 3, true);
1050 {
1051 let mut p = BufferFoldProviderMut::new(&mut buf);
1052 p.apply(FoldOp::RemoveAt(2));
1053 }
1054 assert!(buf.folds().is_empty());
1055 }
1056
1057 #[test]
1058 fn noop_fold_provider_apply_is_noop() {
1059 let mut p = crate::types::NoopFoldProvider;
1062 FoldProvider::apply(&mut p, FoldOp::OpenAll);
1063 FoldProvider::invalidate_range(&mut p, 0, 5);
1064 assert!(!FoldProvider::is_row_hidden(&p, 3));
1066 }
1067}