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 mut byte = 0usize;
82 for r in 0..p.row.min(self.row_count()) {
83 byte += self.line(r).map(|s| s.len()).unwrap_or(0) + 1; }
85 if let Some(line) = self.line(p.row) {
86 byte += p.byte_offset(&line);
87 }
88 byte
89 }
90
91 fn pos_at_byte(&self, byte: usize) -> Pos {
92 let mut remaining = byte;
93 for r in 0..self.row_count() {
94 let line = self.line(r).unwrap_or_default();
95 let line_bytes = line.len();
96 if remaining <= line_bytes {
100 let mut end = remaining.min(line_bytes);
104 while end > 0 && !line.is_char_boundary(end) {
105 end -= 1;
106 }
107 let col = line[..end].chars().count();
108 return Pos {
109 line: r as u32,
110 col: col as u32,
111 };
112 }
113 remaining -= line_bytes + 1;
114 }
115 let last = self.row_count().saturating_sub(1);
117 let line = self.line(last).unwrap_or_default();
118 Pos {
119 line: last as u32,
120 col: line.chars().count() as u32,
121 }
122 }
123}
124
125impl Query for RopeBuffer {
128 fn line_count(&self) -> u32 {
129 self.row_count() as u32
130 }
131
132 fn line(&self, idx: u32) -> String {
133 match RopeBuffer::line(self, idx as usize) {
135 Some(s) => s,
136 None => panic!(
137 "Query::line: index {idx} out of bounds (line_count = {})",
138 self.row_count()
139 ),
140 }
141 }
142
143 fn len_bytes(&self) -> usize {
144 let n = self.row_count();
147 let mut total = 0usize;
148 for r in 0..n {
149 total += RopeBuffer::line(self, r).map(|s| s.len()).unwrap_or(0);
150 }
151 total + n.saturating_sub(1)
153 }
154
155 fn dirty_gen(&self) -> u64 {
156 RopeBuffer::dirty_gen(self)
157 }
158
159 fn slice(&self, range: core::ops::Range<Pos>) -> Cow<'_, str> {
160 let start = pos_to_position(range.start);
161 let end = pos_to_position(range.end);
162 if start >= end {
163 return Cow::Borrowed("");
164 }
165 if start.row == end.row {
167 if let Some(line) = RopeBuffer::line(self, start.row) {
168 let lo = start.byte_offset(&line).min(line.len());
169 let hi = end.byte_offset(&line).min(line.len());
170 return Cow::Owned(line[lo..hi].to_owned());
171 }
172 return Cow::Borrowed("");
173 }
174 let mut out = String::new();
176 for r in start.row..=end.row.min(self.row_count().saturating_sub(1)) {
177 let line = RopeBuffer::line(self, r).unwrap_or_default();
178 if r == start.row {
179 let lo = start.byte_offset(&line).min(line.len());
180 out.push_str(&line[lo..]);
181 out.push('\n');
182 } else if r == end.row {
183 let hi = end.byte_offset(&line).min(line.len());
184 out.push_str(&line[..hi]);
185 } else {
186 out.push_str(&line);
187 out.push('\n');
188 }
189 }
190 Cow::Owned(out)
191 }
192}
193
194impl BufferEdit for RopeBuffer {
197 fn insert_at(&mut self, pos: Pos, text: &str) {
198 let at = clamp_to_buf(self, pos_to_position(pos));
199 let _ = self.apply_edit(hjkl_buffer::Edit::InsertStr {
200 at,
201 text: text.to_string(),
202 });
203 }
204
205 fn delete_range(&mut self, range: core::ops::Range<Pos>) {
206 let start = clamp_to_buf(self, pos_to_position(range.start));
207 let end = clamp_to_buf(self, pos_to_position(range.end));
208 if start >= end {
209 return;
210 }
211 let _ = self.apply_edit(hjkl_buffer::Edit::DeleteRange {
212 start,
213 end,
214 kind: hjkl_buffer::MotionKind::Char,
215 });
216 }
217
218 fn replace_range(&mut self, range: core::ops::Range<Pos>, replacement: &str) {
219 let start = clamp_to_buf(self, pos_to_position(range.start));
220 let end = clamp_to_buf(self, pos_to_position(range.end));
221 if start >= end {
222 let _ = self.apply_edit(hjkl_buffer::Edit::InsertStr {
224 at: start,
225 text: replacement.to_string(),
226 });
227 return;
228 }
229 let _ = self.apply_edit(hjkl_buffer::Edit::Replace {
230 start,
231 end,
232 with: replacement.to_string(),
233 });
234 }
235
236 fn replace_all(&mut self, text: &str) {
237 RopeBuffer::replace_all(self, text);
240 }
241}
242
243#[inline]
244fn clamp_to_buf(buf: &RopeBuffer, p: Position) -> Position {
245 buf.clamp_position(p)
246}
247
248impl Search for RopeBuffer {
251 fn find_next(&self, from: Pos, pat: &Regex) -> Option<core::ops::Range<Pos>> {
252 let start = pos_to_position(from);
253 let total = self.row_count();
254 if total == 0 {
255 return None;
256 }
257 let wrap = true;
266 let from_line = RopeBuffer::line(self, start.row).unwrap_or_default();
267 let from_byte = start.byte_offset(&from_line).min(from_line.len());
268 if let Some(m) = pat.find_at(&from_line, from_byte) {
269 return Some(byte_range_to_pos_range(
270 start.row,
271 m.start(),
272 start.row,
273 m.end(),
274 &from_line,
275 ));
276 }
277 for offset in 1..total {
278 let row = start.row + offset;
279 if row >= total && !wrap {
280 break;
281 }
282 let row = row % total;
283 if !wrap && row <= start.row {
284 break;
285 }
286 let line = RopeBuffer::line(self, row).unwrap_or_default();
287 if let Some(m) = pat.find(&line) {
288 return Some(byte_range_to_pos_range(row, m.start(), row, m.end(), &line));
289 }
290 if row == start.row {
291 break;
292 }
293 }
294 None
295 }
296
297 fn find_prev(&self, from: Pos, pat: &Regex) -> Option<core::ops::Range<Pos>> {
298 let start = pos_to_position(from);
299 let total = self.row_count();
300 if total == 0 {
301 return None;
302 }
303 let wrap = true;
305 let from_line = RopeBuffer::line(self, start.row).unwrap_or_default();
310 let from_byte = start.byte_offset(&from_line).min(from_line.len());
311 let mut best: Option<(usize, usize)> = None;
312 for m in pat.find_iter(&from_line) {
313 if m.start() <= from_byte {
314 best = Some((m.start(), m.end()));
315 } else {
316 break;
317 }
318 }
319 if let Some((s, e)) = best {
320 return Some(byte_range_to_pos_range(
321 start.row, s, start.row, e, &from_line,
322 ));
323 }
324 for offset in 1..total {
325 let row = if offset > start.row {
327 if !wrap {
328 break;
329 }
330 total - (offset - start.row)
331 } else {
332 start.row - offset
333 };
334 if !wrap && row >= start.row {
335 break;
336 }
337 let line = RopeBuffer::line(self, row).unwrap_or_default();
338 let last = pat.find_iter(&line).last();
339 if let Some(m) = last {
340 return Some(byte_range_to_pos_range(row, m.start(), row, m.end(), &line));
341 }
342 if row == start.row {
343 break;
344 }
345 }
346 None
347 }
348}
349
350#[inline]
351fn byte_range_to_pos_range(
352 s_row: usize,
353 s_byte: usize,
354 e_row: usize,
355 e_byte: usize,
356 line: &str,
357) -> core::ops::Range<Pos> {
358 let s_col = line[..s_byte.min(line.len())].chars().count();
359 let e_col = line[..e_byte.min(line.len())].chars().count();
360 Pos {
361 line: s_row as u32,
362 col: s_col as u32,
363 }..Pos {
364 line: e_row as u32,
365 col: e_col as u32,
366 }
367}
368
369impl Buffer for RopeBuffer {}
372
373pub struct BufferFoldProvider<'a> {
388 buffer: &'a RopeBuffer,
389}
390
391impl<'a> BufferFoldProvider<'a> {
392 pub fn new(buffer: &'a RopeBuffer) -> Self {
393 Self { buffer }
394 }
395}
396
397impl FoldProvider for BufferFoldProvider<'_> {
398 fn next_visible_row(&self, row: usize, _row_count: usize) -> Option<usize> {
399 RopeBuffer::next_visible_row(self.buffer, row)
401 }
402
403 fn prev_visible_row(&self, row: usize) -> Option<usize> {
404 RopeBuffer::prev_visible_row(self.buffer, row)
405 }
406
407 fn is_row_hidden(&self, row: usize) -> bool {
408 RopeBuffer::is_row_hidden(self.buffer, row)
409 }
410
411 fn fold_at_row(&self, row: usize) -> Option<(usize, usize, bool)> {
412 let f = self.buffer.fold_at_row(row)?;
413 Some((f.start_row, f.end_row, f.closed))
414 }
415
416 }
420
421pub struct BufferFoldProviderMut<'a> {
432 buffer: &'a mut RopeBuffer,
433}
434
435impl<'a> BufferFoldProviderMut<'a> {
436 pub fn new(buffer: &'a mut RopeBuffer) -> Self {
437 Self { buffer }
438 }
439}
440
441impl FoldProvider for BufferFoldProviderMut<'_> {
442 fn next_visible_row(&self, row: usize, _row_count: usize) -> Option<usize> {
443 RopeBuffer::next_visible_row(self.buffer, row)
444 }
445
446 fn prev_visible_row(&self, row: usize) -> Option<usize> {
447 RopeBuffer::prev_visible_row(self.buffer, row)
448 }
449
450 fn is_row_hidden(&self, row: usize) -> bool {
451 RopeBuffer::is_row_hidden(self.buffer, row)
452 }
453
454 fn fold_at_row(&self, row: usize) -> Option<(usize, usize, bool)> {
455 let f = self.buffer.fold_at_row(row)?;
456 Some((f.start_row, f.end_row, f.closed))
457 }
458
459 fn apply(&mut self, op: FoldOp) {
460 match op {
461 FoldOp::Add {
462 start_row,
463 end_row,
464 closed,
465 } => {
466 self.buffer.add_fold(start_row, end_row, closed);
467 }
468 FoldOp::RemoveAt(row) => {
469 self.buffer.remove_fold_at(row);
470 }
471 FoldOp::OpenAt(row) => {
472 self.buffer.open_fold_at(row);
473 }
474 FoldOp::CloseAt(row) => {
475 self.buffer.close_fold_at(row);
476 }
477 FoldOp::ToggleAt(row) => {
478 self.buffer.toggle_fold_at(row);
479 }
480 FoldOp::OpenAll => {
481 self.buffer.open_all_folds();
482 }
483 FoldOp::CloseAll => {
484 self.buffer.close_all_folds();
485 }
486 FoldOp::ClearAll => {
487 self.buffer.clear_all_folds();
488 }
489 FoldOp::Invalidate { start_row, end_row } => {
490 self.buffer.invalidate_folds_in_range(start_row, end_row);
491 }
492 }
493 }
494
495 fn invalidate_range(&mut self, start_row: usize, end_row: usize) {
496 self.buffer.invalidate_folds_in_range(start_row, end_row);
497 }
498}
499
500pub struct SnapshotFoldProvider {
516 folds: Vec<hjkl_buffer::Fold>,
517 row_count: usize,
518}
519
520impl SnapshotFoldProvider {
521 pub fn from_buffer(buffer: &RopeBuffer) -> Self {
525 Self {
526 folds: buffer.folds().to_vec(),
527 row_count: buffer.row_count(),
528 }
529 }
530
531 fn snapshot_is_row_hidden(&self, row: usize) -> bool {
535 self.folds.iter().any(|f| f.hides(row))
536 }
537}
538
539impl FoldProvider for SnapshotFoldProvider {
540 fn next_visible_row(&self, row: usize, _row_count: usize) -> Option<usize> {
541 let last = self.row_count.saturating_sub(1);
544 if last == 0 && row == 0 {
545 return None;
546 }
547 let mut r = row.checked_add(1)?;
548 while r <= last && self.snapshot_is_row_hidden(r) {
549 r += 1;
550 }
551 (r <= last).then_some(r)
552 }
553
554 fn prev_visible_row(&self, row: usize) -> Option<usize> {
555 let mut r = row.checked_sub(1)?;
557 while self.snapshot_is_row_hidden(r) {
558 r = r.checked_sub(1)?;
559 }
560 Some(r)
561 }
562
563 fn is_row_hidden(&self, row: usize) -> bool {
564 self.snapshot_is_row_hidden(row)
565 }
566
567 fn fold_at_row(&self, row: usize) -> Option<(usize, usize, bool)> {
568 self.folds
569 .iter()
570 .find(|f| f.contains(row))
571 .map(|f| (f.start_row, f.end_row, f.closed))
572 }
573
574 }
576
577#[cfg(test)]
580mod tests {
581 use super::*;
582
583 #[test]
588 fn rope_buffer_implements_spec_buffer() {
589 fn assert_buffer<B: Buffer>() {}
590 fn assert_cursor<B: Cursor>() {}
591 fn assert_query<B: Query>() {}
592 fn assert_edit<B: BufferEdit>() {}
593 fn assert_search<B: Search>() {}
594 assert_buffer::<RopeBuffer>();
595 assert_cursor::<RopeBuffer>();
596 assert_query::<RopeBuffer>();
597 assert_edit::<RopeBuffer>();
598 assert_search::<RopeBuffer>();
599 }
600
601 #[test]
602 fn cursor_roundtrip() {
603 let mut b = RopeBuffer::from_str("hello\nworld");
604 Cursor::set_cursor(&mut b, Pos::new(1, 3));
605 assert_eq!(Cursor::cursor(&b), Pos::new(1, 3));
606 }
607
608 #[test]
609 fn query_line_count_and_line() {
610 let b = RopeBuffer::from_str("a\nb\nc");
611 assert_eq!(Query::line_count(&b), 3);
612 assert_eq!(Query::line(&b, 0), "a");
613 assert_eq!(Query::line(&b, 2), "c");
614 }
615
616 #[test]
617 fn query_len_bytes_matches_join() {
618 let b = RopeBuffer::from_str("foo\nbar\nbaz");
619 assert_eq!(Query::len_bytes(&b), b.as_string().len());
620 }
621
622 #[test]
623 fn query_slice_single_line_borrows() {
624 let b = RopeBuffer::from_str("hello world");
625 let s = Query::slice(&b, Pos::new(0, 0)..Pos::new(0, 5));
626 assert_eq!(&*s, "hello");
627 assert!(matches!(s, Cow::Owned(_)));
629 }
630
631 #[test]
632 fn query_slice_multiline_allocates() {
633 let b = RopeBuffer::from_str("ab\ncd\nef");
634 let s = Query::slice(&b, Pos::new(0, 1)..Pos::new(2, 1));
635 assert_eq!(&*s, "b\ncd\ne");
636 assert!(matches!(s, Cow::Owned(_)));
637 }
638
639 #[test]
640 fn cursor_byte_offset_and_inverse() {
641 let b = RopeBuffer::from_str("hello\nworld");
642 let p = Pos::new(1, 0);
644 assert_eq!(Cursor::byte_offset(&b, p), 6);
645 assert_eq!(Cursor::pos_at_byte(&b, 6), p);
646 let p2 = Pos::new(1, 3);
648 let off = Cursor::byte_offset(&b, p2);
649 assert_eq!(Cursor::pos_at_byte(&b, off), p2);
650 }
651
652 #[test]
653 fn buffer_edit_insert_delete_replace() {
654 let mut b = RopeBuffer::from_str("hello");
655 BufferEdit::insert_at(&mut b, Pos::new(0, 5), " world");
656 assert_eq!(b.as_string(), "hello world");
657 BufferEdit::delete_range(&mut b, Pos::new(0, 5)..Pos::new(0, 11));
658 assert_eq!(b.as_string(), "hello");
659 BufferEdit::replace_range(&mut b, Pos::new(0, 0)..Pos::new(0, 5), "HI");
660 assert_eq!(b.as_string(), "HI");
661 }
662
663 #[test]
668 fn buffer_edit_default_replace_all_routes_through_replace_range() {
669 struct MockBuf {
670 cursor: Pos,
671 lines: Vec<String>,
672 last_replace_range: Option<core::ops::Range<Pos>>,
673 }
674 impl Sealed for MockBuf {}
675 impl Cursor for MockBuf {
676 fn cursor(&self) -> Pos {
677 self.cursor
678 }
679 fn set_cursor(&mut self, p: Pos) {
680 self.cursor = p;
681 }
682 fn byte_offset(&self, _p: Pos) -> usize {
683 0
684 }
685 fn pos_at_byte(&self, _b: usize) -> Pos {
686 Pos::ORIGIN
687 }
688 }
689 impl Query for MockBuf {
690 fn line_count(&self) -> u32 {
691 self.lines.len() as u32
692 }
693 fn line(&self, idx: u32) -> String {
694 self.lines[idx as usize].clone()
695 }
696 fn len_bytes(&self) -> usize {
697 0
698 }
699 fn slice(&self, _r: core::ops::Range<Pos>) -> Cow<'_, str> {
700 Cow::Borrowed("")
701 }
702 }
703 impl BufferEdit for MockBuf {
704 fn insert_at(&mut self, _p: Pos, _t: &str) {}
705 fn delete_range(&mut self, _r: core::ops::Range<Pos>) {}
706 fn replace_range(&mut self, range: core::ops::Range<Pos>, _t: &str) {
707 self.last_replace_range = Some(range);
708 }
709 }
710 impl Search for MockBuf {
711 fn find_next(&self, _f: Pos, _p: &Regex) -> Option<core::ops::Range<Pos>> {
712 None
713 }
714 fn find_prev(&self, _f: Pos, _p: &Regex) -> Option<core::ops::Range<Pos>> {
715 None
716 }
717 }
718 impl Buffer for MockBuf {}
719
720 let mut m = MockBuf {
721 cursor: Pos::ORIGIN,
722 lines: vec!["hi".into()],
723 last_replace_range: None,
724 };
725 BufferEdit::replace_all(&mut m, "new content");
726 let r = m
727 .last_replace_range
728 .expect("default impl must hit replace_range");
729 assert_eq!(r.start, Pos::ORIGIN);
730 assert_eq!(r.end.line, u32::MAX);
731 assert_eq!(r.end.col, u32::MAX);
732 }
733
734 #[test]
735 fn buffer_edit_replace_all_rebuilds_content() {
736 let mut b = RopeBuffer::from_str("hello\nworld");
737 Cursor::set_cursor(&mut b, Pos::new(1, 3));
738 BufferEdit::replace_all(&mut b, "alpha\nbeta\ngamma");
739 assert_eq!(b.as_string(), "alpha\nbeta\ngamma");
740 assert_eq!(Query::line_count(&b), 3);
741 let c = Cursor::cursor(&b);
743 assert!((c.line as usize) < Query::line_count(&b) as usize);
744 }
745
746 #[test]
747 fn search_find_next_same_row() {
748 let b = RopeBuffer::from_str("abc def abc");
749 let pat = Regex::new("abc").unwrap();
750 let r = Search::find_next(&b, Pos::new(0, 0), &pat).unwrap();
751 assert_eq!(r, Pos::new(0, 0)..Pos::new(0, 3));
752 let r2 = Search::find_next(&b, Pos::new(0, 1), &pat).unwrap();
753 assert_eq!(r2, Pos::new(0, 8)..Pos::new(0, 11));
754 }
755
756 #[test]
757 fn search_find_next_wraps() {
758 let b = RopeBuffer::from_str("foo\nbar\nfoo");
759 let pat = Regex::new("foo").unwrap();
763 let r = Search::find_next(&b, Pos::new(1, 0), &pat).unwrap();
765 assert_eq!(r, Pos::new(2, 0)..Pos::new(2, 3));
766 }
767
768 #[test]
769 fn search_find_prev_same_row() {
770 let b = RopeBuffer::from_str("abc def abc");
771 let pat = Regex::new("abc").unwrap();
772 let r = Search::find_prev(&b, Pos::new(0, 11), &pat).unwrap();
773 assert_eq!(r, Pos::new(0, 8)..Pos::new(0, 11));
774 }
775
776 #[test]
777 fn pos_position_roundtrip() {
778 let p = Pos::new(7, 3);
779 assert_eq!(position_to_pos(pos_to_position(p)), p);
780 }
781
782 #[test]
785 fn fold_provider_mut_apply_add_open_close_toggle() {
786 let mut buf = RopeBuffer::from_str("a\nb\nc\nd\ne");
787 {
788 let mut p = BufferFoldProviderMut::new(&mut buf);
789 p.apply(FoldOp::Add {
790 start_row: 1,
791 end_row: 3,
792 closed: true,
793 });
794 assert_eq!(p.fold_at_row(2), Some((1, 3, true)));
795 p.apply(FoldOp::OpenAt(2));
796 assert_eq!(p.fold_at_row(2), Some((1, 3, false)));
797 p.apply(FoldOp::CloseAt(2));
798 assert_eq!(p.fold_at_row(2), Some((1, 3, true)));
799 p.apply(FoldOp::ToggleAt(2));
800 assert_eq!(p.fold_at_row(2), Some((1, 3, false)));
801 }
802 assert_eq!(buf.folds().len(), 1);
803 }
804
805 #[test]
806 fn fold_provider_mut_apply_open_close_clear_all() {
807 let mut buf = RopeBuffer::from_str("a\nb\nc\nd\ne");
808 buf.add_fold(0, 1, false);
809 buf.add_fold(2, 3, true);
810 {
811 let mut p = BufferFoldProviderMut::new(&mut buf);
812 p.apply(FoldOp::CloseAll);
813 }
814 assert!(buf.folds().iter().all(|f| f.closed));
815 {
816 let mut p = BufferFoldProviderMut::new(&mut buf);
817 p.apply(FoldOp::OpenAll);
818 }
819 assert!(buf.folds().iter().all(|f| !f.closed));
820 {
821 let mut p = BufferFoldProviderMut::new(&mut buf);
822 p.apply(FoldOp::ClearAll);
823 }
824 assert!(buf.folds().is_empty());
825 }
826
827 #[test]
828 fn fold_provider_mut_invalidate_range_drops_overlapping() {
829 let mut buf = RopeBuffer::from_str("a\nb\nc\nd\ne");
830 buf.add_fold(0, 1, true);
831 buf.add_fold(2, 3, true);
832 buf.add_fold(4, 4, true);
833 {
834 let mut p = BufferFoldProviderMut::new(&mut buf);
835 p.invalidate_range(2, 3);
836 }
837 let starts: Vec<usize> = buf.folds().iter().map(|f| f.start_row).collect();
838 assert_eq!(starts, vec![0, 4]);
839 }
840
841 #[test]
842 fn fold_provider_mut_apply_remove_at() {
843 let mut buf = RopeBuffer::from_str("a\nb\nc\nd\ne");
844 buf.add_fold(1, 3, true);
845 {
846 let mut p = BufferFoldProviderMut::new(&mut buf);
847 p.apply(FoldOp::RemoveAt(2));
848 }
849 assert!(buf.folds().is_empty());
850 }
851
852 #[test]
853 fn noop_fold_provider_apply_is_noop() {
854 let mut p = crate::types::NoopFoldProvider;
857 FoldProvider::apply(&mut p, FoldOp::OpenAll);
858 FoldProvider::invalidate_range(&mut p, 0, 5);
859 assert!(!FoldProvider::is_row_hidden(&p, 3));
861 }
862}