1use std::sync::{Arc, Mutex, MutexGuard};
2
3use crate::content::Buffer;
4use crate::folds::FoldIndex;
5use crate::{Position, Viewport};
6
7pub struct View {
62 pub(crate) content: Arc<Mutex<Buffer>>,
64 cursor: Position,
67}
68
69impl Default for View {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75impl View {
76 pub fn new() -> Self {
80 Self {
81 content: Arc::new(Mutex::new(Buffer::new())),
82 cursor: Position::default(),
83 }
84 }
85
86 #[allow(clippy::should_implement_trait)]
91 pub fn from_str(text: &str) -> Self {
92 Self {
93 content: Arc::new(Mutex::new(Buffer::from_str(text))),
94 cursor: Position::default(),
95 }
96 }
97
98 pub fn new_view(content: Arc<Mutex<Buffer>>) -> Self {
118 Self {
119 content,
120 cursor: Position::default(),
121 }
122 }
123
124 pub fn content_arc(&self) -> Arc<Mutex<Buffer>> {
127 Arc::clone(&self.content)
128 }
129
130 pub fn cursor(&self) -> Position {
133 self.cursor
134 }
135
136 pub fn last_cursor(&self) -> (usize, usize) {
141 self.content.lock().unwrap().last_cursor
142 }
143
144 pub fn dirty_gen(&self) -> u64 {
145 self.content.lock().unwrap().dirty_gen
146 }
147
148 pub fn row_count(&self) -> usize {
150 self.content.lock().unwrap().text.len_lines()
151 }
152
153 pub fn as_string(&self) -> String {
158 self.content.lock().unwrap().text.to_string()
159 }
160
161 pub fn set_cursor(&mut self, pos: Position) {
168 let mut c = self.content.lock().unwrap();
169 let n = c.text.len_lines();
170 let last_row = n.saturating_sub(1);
171 let row = pos.row.min(last_row);
172 let line_chars = rope_line_char_count(&c.text, row);
173 let col = pos.col.min(line_chars);
174 c.last_cursor = (row, col);
178 drop(c);
179 self.cursor = Position::new(row, col);
180 }
181
182 pub fn ensure_cursor_visible(&mut self, viewport: &mut Viewport) {
185 let cursor = self.cursor;
186 let v = *viewport;
187 let wrap_active = !matches!(v.wrap, crate::Wrap::None) && v.text_width > 0;
188 if !wrap_active {
189 viewport.ensure_visible(cursor);
190 return;
191 }
192 if v.height == 0 {
193 return;
194 }
195 if cursor.row < v.top_row {
197 viewport.top_row = cursor.row;
198 viewport.top_col = 0;
199 return;
200 }
201 let height = v.height as usize;
202 let cursor_row = {
213 let c = self.content.lock().unwrap();
214 cursor.row.min(c.text.len_lines().saturating_sub(1))
215 };
216 let Some(mut screen) = self.cursor_screen_row_from(viewport, viewport.top_row) else {
217 let last_row = {
228 let c = self.content.lock().unwrap();
229 c.text.len_lines().saturating_sub(1)
230 };
231 viewport.top_row = cursor.row.min(last_row);
232 viewport.top_col = 0;
233 return;
234 };
235 while screen >= height {
236 let c = self.content.lock().unwrap();
237 let folds = FoldIndex::new(&c.folds);
238 let mut next = viewport.top_row + 1;
239 while next <= cursor_row && folds.hides_row(next) {
240 next += 1;
241 }
242 if next > cursor_row {
243 drop(c);
244 viewport.top_row = cursor_row;
245 break;
246 }
247 for r in viewport.top_row..next {
251 if folds.hides_row(r) {
252 continue;
253 }
254 let line = rope_line_str(&c.text, r);
255 screen -= crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len();
256 }
257 drop(c);
258 viewport.top_row = next;
259 }
260 viewport.top_col = 0;
261 }
262
263 pub fn cursor_screen_row(&self, viewport: &Viewport) -> Option<usize> {
265 if matches!(viewport.wrap, crate::Wrap::None) || viewport.text_width == 0 {
266 return None;
267 }
268 self.cursor_screen_row_from(viewport, viewport.top_row)
269 }
270
271 pub fn screen_rows_between(&self, viewport: &Viewport, start: usize, end: usize) -> usize {
273 if start > end {
274 return 0;
275 }
276 let c = self.content.lock().unwrap();
277 let folds = FoldIndex::new(&c.folds);
278 let n = c.text.len_lines();
279 let last = n.saturating_sub(1);
280 let end = end.min(last);
281 let v = *viewport;
282 let mut total = 0usize;
283 for r in start..=end {
284 if folds.hides_row(r) {
285 continue;
286 }
287 if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
288 total += 1;
289 } else {
290 let line = rope_line_str(&c.text, r);
291 total += crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len();
292 }
293 }
294 total
295 }
296
297 pub fn max_top_for_height(&self, viewport: &Viewport, height: usize) -> usize {
300 if height == 0 {
301 return 0;
302 }
303 let c = self.content.lock().unwrap();
304 let folds = FoldIndex::new(&c.folds);
305 let n = c.text.len_lines();
306 let last = n.saturating_sub(1);
307 let mut total = 0usize;
308 let mut row = last;
309 loop {
310 if !folds.hides_row(row) {
311 let v = *viewport;
312 total += if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
313 1
314 } else {
315 let line = rope_line_str(&c.text, row);
316 crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len()
317 };
318 }
319 if total >= height {
320 return row;
321 }
322 if row == 0 {
323 return 0;
324 }
325 row -= 1;
326 }
327 }
328
329 pub fn clamp_position(&self, pos: Position) -> Position {
331 let c = self.content.lock().unwrap();
332 let n = c.text.len_lines();
333 let last_row = n.saturating_sub(1);
334 let row = pos.row.min(last_row);
335 let line_chars = rope_line_char_count(&c.text, row);
336 let col = pos.col.min(line_chars);
337 Position::new(row, col)
338 }
339
340 pub fn replace_all(&mut self, text: &str) {
343 let new_cursor = {
344 let mut c = self.content.lock().unwrap();
345 c.text = ropey::Rope::from_str(text);
346 let n = c.text.len_lines();
347 let last_row = n.saturating_sub(1);
348 let row = self.cursor.row.min(last_row);
349 let line_chars = rope_line_char_count(&c.text, row);
350 let col = self.cursor.col.min(line_chars);
351 c.dirty_gen = c.dirty_gen.wrapping_add(1);
352 c.cached_joined = None;
353 c.cached_byte_len = None;
354 Position::new(row, col)
355 };
356 self.cursor = new_cursor;
357 }
358
359 pub(crate) fn fold_gen_bump(&mut self) {
367 let mut c = self.content.lock().unwrap();
368 c.fold_gen = c.fold_gen.wrapping_add(1);
369 }
370
371 pub(crate) fn dirty_gen_bump(&mut self) {
373 let mut c = self.content.lock().unwrap();
374 c.dirty_gen = c.dirty_gen.wrapping_add(1);
375 c.cached_joined = None;
376 c.cached_byte_len = None;
377 }
378
379 pub fn byte_len(&self) -> usize {
385 let mut c = self.content.lock().unwrap();
386 let dg = c.dirty_gen;
387 if let Some((cached_dg, len)) = c.cached_byte_len
388 && cached_dg == dg
389 {
390 return len;
391 }
392 let total = c.text.len_bytes();
393 c.cached_byte_len = Some((dg, total));
394 total
395 }
396
397 pub fn content_joined(&self) -> std::sync::Arc<String> {
406 let mut c = self.content.lock().unwrap();
407 let dg = c.dirty_gen;
408 if let Some((cached_dg, ref s)) = c.cached_joined
409 && cached_dg == dg
410 {
411 return std::sync::Arc::clone(s);
412 }
413 let joined = std::sync::Arc::new(c.text.to_string());
414 c.cached_joined = Some((dg, std::sync::Arc::clone(&joined)));
415 joined
416 }
417
418 pub fn rope(&self) -> ropey::Rope {
427 self.content.lock().unwrap().text.clone()
428 }
429
430 pub(crate) fn content_lock(&self) -> MutexGuard<'_, Buffer> {
432 self.content.lock().unwrap()
433 }
434
435 pub(crate) fn content_lock_mut(&mut self) -> MutexGuard<'_, Buffer> {
437 self.content.lock().unwrap()
438 }
439
440 fn cursor_screen_row_from(&self, viewport: &Viewport, top: usize) -> Option<usize> {
443 let cursor = self.cursor;
444 if cursor.row < top {
445 return None;
446 }
447 let c = self.content.lock().unwrap();
448 let folds = FoldIndex::new(&c.folds);
449 let cursor_row = cursor.row.min(c.text.len_lines().saturating_sub(1));
453 if cursor_row < top {
454 return None;
455 }
456 let v = *viewport;
457 let mut screen = 0usize;
458 for r in top..=cursor_row {
459 if folds.hides_row(r) {
460 continue;
461 }
462 let line = rope_line_str(&c.text, r);
463 let segs = crate::wrap::wrap_segments(&line, v.text_width, v.wrap);
464 if r == cursor_row {
465 let seg_idx = crate::wrap::segment_for_col(&segs, cursor.col);
466 return Some(screen + seg_idx);
467 }
468 screen += segs.len();
469 }
470 None
471 }
472
473 pub fn undo_stack_is_empty(&self) -> bool {
483 self.content.lock().unwrap().undo.is_at_root()
484 }
485
486 pub fn redo_stack_is_empty(&self) -> bool {
487 !self.content.lock().unwrap().undo.has_redo()
488 }
489
490 pub fn undo_stack_len(&self) -> usize {
491 self.content.lock().unwrap().undo.depth_from_root()
492 }
493
494 pub fn push_undo_entry(&self, entry: crate::UndoEntry) {
498 self.content.lock().unwrap().undo.push(entry);
499 }
500
501 pub fn undo_step(
505 &self,
506 rope: ropey::Rope,
507 cursor: (usize, usize),
508 marks: crate::MarkSnapshot,
509 ) -> Option<crate::UndoEntry> {
510 self.content
511 .lock()
512 .unwrap()
513 .undo
514 .undo_step(rope, cursor, marks)
515 }
516
517 pub fn redo_change_start(&self) -> (usize, usize) {
520 self.content.lock().unwrap().undo.current_cursor()
521 }
522
523 pub fn redo_step(
526 &self,
527 rope: ropey::Rope,
528 cursor: (usize, usize),
529 marks: crate::MarkSnapshot,
530 ) -> Option<crate::UndoEntry> {
531 self.content
532 .lock()
533 .unwrap()
534 .undo
535 .redo_step(rope, cursor, marks)
536 }
537
538 pub fn pop_committed(&self) -> bool {
542 self.content.lock().unwrap().undo.pop_committed()
543 }
544
545 pub fn seq_earlier_step(
550 &self,
551 rope: ropey::Rope,
552 cursor: (usize, usize),
553 marks: crate::MarkSnapshot,
554 ) -> Option<crate::UndoEntry> {
555 self.content
556 .lock()
557 .unwrap()
558 .undo
559 .seq_earlier_step(rope, cursor, marks)
560 }
561
562 pub fn seq_later_step(
565 &self,
566 rope: ropey::Rope,
567 cursor: (usize, usize),
568 marks: crate::MarkSnapshot,
569 ) -> Option<crate::UndoEntry> {
570 self.content
571 .lock()
572 .unwrap()
573 .undo
574 .seq_later_step(rope, cursor, marks)
575 }
576
577 pub fn seq_earlier_timestamp(&self) -> Option<std::time::SystemTime> {
579 self.content.lock().unwrap().undo.seq_earlier_timestamp()
580 }
581
582 pub fn seq_later_timestamp(&self) -> Option<std::time::SystemTime> {
584 self.content.lock().unwrap().undo.seq_later_timestamp()
585 }
586
587 pub fn undo_leaves(&self) -> Vec<(u64, usize, std::time::SystemTime, bool)> {
590 self.content.lock().unwrap().undo.leaves()
591 }
592
593 pub fn peek_undo_timestamp(&self) -> Option<std::time::SystemTime> {
594 self.content.lock().unwrap().undo.parent_timestamp()
595 }
596
597 pub fn peek_redo_timestamp(&self) -> Option<std::time::SystemTime> {
598 self.content.lock().unwrap().undo.child_timestamp()
599 }
600
601 pub fn clear_undo_redo(&self) {
602 self.content.lock().unwrap().undo.clear_all();
603 }
604
605 pub fn undo_to_serializable(&self) -> (crate::SerTree, u64) {
612 let mut c = self.content.lock().unwrap();
613 let rope = c.text.clone();
614 c.undo.sync_current(rope);
615 let seq = c.undo.current_node_seq();
616 (c.undo.to_serializable(), seq)
617 }
618
619 pub fn install_undo_tree(&self, ser: &crate::SerTree) -> bool {
625 match crate::UndoTree::from_serializable(ser) {
626 Some(tree) => {
627 self.content.lock().unwrap().undo = tree;
628 true
629 }
630 None => false,
631 }
632 }
633
634 pub fn install_recovered_undo_tree(&self, ser: &crate::SerTree, current_seq: u64) -> bool {
648 let Some(mut tree) = crate::UndoTree::from_serializable(ser) else {
649 return false;
650 };
651 if tree.current_node_seq() != current_seq {
652 return false;
653 }
654 let mut c = self.content.lock().unwrap();
655 let materialized = tree.current_content();
656 if materialized.to_string().trim_end_matches('\n')
657 != c.text.to_string().trim_end_matches('\n')
658 {
659 return false;
660 }
661 c.undo = tree;
662 true
663 }
664
665 pub fn clear_redo(&self) {
666 self.content.lock().unwrap().undo.clear_redo();
667 }
668
669 pub fn undo_group_active(&self) -> bool {
672 self.content.lock().unwrap().undo_group_active()
673 }
674
675 pub fn undo_group_arm(&self) -> bool {
677 self.content.lock().unwrap().undo_group_arm()
678 }
679
680 pub fn cap_undo(&self, cap: usize) {
681 self.content.lock().unwrap().undo.cap(cap);
682 }
683
684 pub fn content_dirty(&self) -> bool {
685 self.content.lock().unwrap().content_dirty
686 }
687
688 pub fn set_content_dirty(&self, v: bool) {
689 self.content.lock().unwrap().content_dirty = v;
690 }
691
692 pub fn mark_content_dirty(&self) {
693 let mut c = self.content.lock().unwrap();
694 c.content_dirty = true;
695 c.cached_editor_content = None;
696 }
697
698 pub fn take_dirty(&self) -> bool {
699 let mut c = self.content.lock().unwrap();
700 let v = c.content_dirty;
701 c.content_dirty = false;
702 v
703 }
704
705 pub fn cached_editor_content(&self) -> Option<std::sync::Arc<String>> {
706 self.content.lock().unwrap().cached_editor_content.clone()
707 }
708
709 pub fn set_cached_editor_content(&self, arc: std::sync::Arc<String>) {
710 self.content.lock().unwrap().cached_editor_content = Some(arc);
711 }
712
713 pub fn push_fold_op(&self, op: crate::FoldOp) {
714 self.content.lock().unwrap().pending_fold_ops.push(op);
715 }
716
717 pub fn take_fold_ops(&self) -> Vec<crate::FoldOp> {
718 std::mem::take(&mut self.content.lock().unwrap().pending_fold_ops)
719 }
720
721 pub fn extend_change_log(&self, edits: impl IntoIterator<Item = crate::EngineEdit>) {
728 let mut content = self.content.lock().unwrap();
729 if !content.change_log_enabled {
730 return;
731 }
732 content.change_log.extend(edits);
733 }
734
735 pub fn set_change_log_enabled(&self, enabled: bool) {
738 self.content.lock().unwrap().change_log_enabled = enabled;
739 }
740
741 pub fn change_log_enabled(&self) -> bool {
744 self.content.lock().unwrap().change_log_enabled
745 }
746
747 pub fn take_change_log(&self) -> Vec<crate::EngineEdit> {
748 std::mem::take(&mut self.content.lock().unwrap().change_log)
749 }
750
751 pub fn extend_pending_content_edits(
752 &self,
753 edits: impl IntoIterator<Item = crate::ContentEdit>,
754 ) {
755 self.content
756 .lock()
757 .unwrap()
758 .pending_content_edits
759 .extend(edits);
760 }
761
762 pub fn push_pending_content_edit(&self, edit: crate::ContentEdit) {
763 self.content
764 .lock()
765 .unwrap()
766 .pending_content_edits
767 .push(edit);
768 }
769
770 pub fn take_pending_content_edits(&self) -> Vec<crate::ContentEdit> {
771 std::mem::take(&mut self.content.lock().unwrap().pending_content_edits)
772 }
773
774 pub fn clear_pending_content_edits(&self) {
775 self.content.lock().unwrap().pending_content_edits.clear();
776 }
777
778 pub fn pending_content_reset(&self) -> bool {
779 self.content.lock().unwrap().pending_content_reset
780 }
781
782 pub fn set_pending_content_reset(&self, v: bool) {
783 self.content.lock().unwrap().pending_content_reset = v;
784 }
785
786 pub fn take_pending_content_reset(&self) -> bool {
787 let mut c = self.content.lock().unwrap();
788 let v = c.pending_content_reset;
789 c.pending_content_reset = false;
790 v
791 }
792
793 pub fn mark(&self, c: char) -> Option<(usize, usize)> {
794 self.content_lock().marks.get(&c).copied()
795 }
796 pub fn set_mark(&mut self, c: char, pos: (usize, usize)) {
797 self.content_lock_mut().marks.insert(c, pos);
798 }
799 pub fn clear_mark(&mut self, c: char) {
800 self.content_lock_mut().marks.remove(&c);
801 }
802 pub fn marks_cloned(&self) -> std::collections::BTreeMap<char, (usize, usize)> {
803 self.content_lock().marks.clone()
804 }
805 pub fn set_marks(&mut self, marks: std::collections::BTreeMap<char, (usize, usize)>) {
806 self.content_lock_mut().marks = marks;
807 }
808 pub fn rebase_marks(
812 &mut self,
813 edit_start: usize,
814 drop_end: usize,
815 shift_threshold: usize,
816 delta: isize,
817 ) {
818 let mut c = self.content_lock_mut();
819 let mut to_drop: Vec<char> = Vec::new();
820 for (ch, (row, _col)) in c.marks.iter_mut() {
821 if (edit_start..drop_end).contains(row) {
822 to_drop.push(*ch);
823 } else if *row >= shift_threshold {
824 *row = ((*row as isize) + delta).max(0) as usize;
825 }
826 }
827 for ch in to_drop {
828 c.marks.remove(&ch);
829 }
830 }
831 pub fn join_row_marks(&mut self, from: usize, to: usize, col: impl Fn(usize) -> usize) {
834 for (row, c) in self.content_lock_mut().marks.values_mut() {
835 if *row == from {
836 *row = to;
837 *c = col(*c);
838 }
839 }
840 }
841 pub fn syntax_fold_ranges_cloned(&self) -> Vec<(usize, usize)> {
842 self.content_lock().syntax_fold_ranges.clone()
843 }
844 pub fn set_syntax_fold_ranges(&mut self, ranges: Vec<(usize, usize)>) {
845 self.content_lock_mut().syntax_fold_ranges = ranges;
846 }
847}
848
849pub fn floor_char_boundary(rope: &ropey::Rope, byte_idx: usize) -> usize {
862 let byte_idx = byte_idx.min(rope.len_bytes());
863 rope.char_to_byte(rope.byte_to_char(byte_idx))
864}
865
866fn rope_line_content_end(rope: &ropey::Rope, row: usize) -> usize {
878 if row + 1 >= rope.len_lines() {
879 return rope.len_bytes();
880 }
881 let step_back = rope.line_to_byte(row + 1).saturating_sub(1);
882 floor_char_boundary(rope, step_back)
883}
884
885pub fn rope_line_str(rope: &ropey::Rope, row: usize) -> String {
888 let start = rope.line_to_byte(row);
890 rope.byte_slice(start..rope_line_content_end(rope, row))
891 .to_string()
892}
893
894pub fn rope_line_bytes(rope: &ropey::Rope, row: usize) -> usize {
900 if row >= rope.len_lines() {
901 return 0;
902 }
903 rope_line_content_end(rope, row).saturating_sub(rope.line_to_byte(row))
904}
905
906pub fn rope_line_char_count(rope: &ropey::Rope, row: usize) -> usize {
911 if row >= rope.len_lines() {
912 return 0;
913 }
914 let end = rope_line_content_end(rope, row);
915 rope.byte_to_char(end)
916 .saturating_sub(rope.byte_to_char(rope.line_to_byte(row)))
917}
918
919pub fn pos_to_char_idx(rope: &ropey::Rope, row: usize, col: usize) -> usize {
924 let row = row.min(rope.len_lines().saturating_sub(1));
925 let line_start = rope.line_to_char(row);
926 let line_char_count = rope_line_char_count(rope, row);
927 line_start + col.min(line_char_count)
928}
929
930#[cfg(test)]
931mod tests {
932 use super::*;
933
934 #[test]
935 fn new_has_one_empty_row() {
936 let b = View::new();
937 assert_eq!(b.row_count(), 1);
938 assert_eq!(rope_line_str(&b.rope(), 0), "");
939 assert_eq!(b.cursor(), Position::default());
940 }
941
942 #[test]
943 fn from_str_splits_on_newline() {
944 let b = View::from_str("foo\nbar\nbaz");
945 assert_eq!(b.row_count(), 3);
946 assert_eq!(rope_line_str(&b.rope(), 0), "foo");
947 assert_eq!(rope_line_str(&b.rope(), 2), "baz");
948 }
949
950 #[test]
951 fn from_str_trailing_newline_keeps_empty_row() {
952 let b = View::from_str("foo\n");
953 assert_eq!(b.row_count(), 2);
954 assert_eq!(rope_line_str(&b.rope(), 1), "");
955 }
956
957 #[test]
963 fn line_helpers_strip_every_line_separator() {
964 for sep in ["\r", "\u{0b}", "\u{0c}", "\u{85}", "\u{2028}", "\u{2029}"] {
967 let rope = ropey::Rope::from_str(&format!("ab{sep}cd{sep}ef"));
968 assert_eq!(rope.len_lines(), 3, "separator {sep:?} must split rows");
969 for row in 0..3 {
970 let expect = ["ab", "cd", "ef"][row];
971 assert_eq!(
972 rope_line_str(&rope, row),
973 expect,
974 "rope_line_str kept separator {sep:?} in row {row}"
975 );
976 assert_eq!(
977 rope_line_bytes(&rope, row),
978 expect.len(),
979 "rope_line_bytes counted separator {sep:?} in row {row}"
980 );
981 assert_eq!(
982 rope_line_char_count(&rope, row),
983 expect.chars().count(),
984 "rope_line_char_count counted separator {sep:?} in row {row}"
985 );
986 }
987 }
988 }
989
990 #[test]
994 fn line_helpers_agree_with_each_other() {
995 let rope = ropey::Rope::from_str("ab\r\ncd\re\u{2028}f\u{85}\u{0c}g\nh");
996 for row in 0..rope.len_lines() {
997 let s = rope_line_str(&rope, row);
998 assert_eq!(
999 rope_line_bytes(&rope, row),
1000 s.len(),
1001 "bytes differ on {row}"
1002 );
1003 assert_eq!(
1004 rope_line_char_count(&rope, row),
1005 s.chars().count(),
1006 "char count differs on row {row}"
1007 );
1008 }
1009 }
1010
1011 #[test]
1012 fn from_str_empty_input_keeps_one_row() {
1013 let b = View::from_str("");
1014 assert_eq!(b.row_count(), 1);
1015 assert_eq!(rope_line_str(&b.rope(), 0), "");
1016 }
1017
1018 #[test]
1019 fn as_string_round_trips() {
1020 let b = View::from_str("a\nb\nc");
1021 assert_eq!(b.as_string(), "a\nb\nc");
1022 }
1023
1024 #[test]
1025 fn dirty_gen_starts_at_zero() {
1026 assert_eq!(View::new().dirty_gen(), 0);
1027 }
1028
1029 fn single_node_tree(base: &str, seq: u64) -> crate::SerTree {
1032 crate::SerTree {
1033 base: Some(base.to_string()),
1034 nodes: vec![crate::SerNode {
1035 parent: None,
1036 children: Vec::new(),
1037 last_child: None,
1038 delta: None,
1039 cursor: (0, 0),
1040 timestamp_unix_ms: 0,
1041 marks: crate::MarkSnapshot::default(),
1042 seq,
1043 }],
1044 root: 0,
1045 current: 0,
1046 next_seq: seq + 1,
1047 }
1048 }
1049
1050 #[test]
1054 fn install_recovered_undo_tree_guards_seq_and_content() {
1055 let v = View::from_str("alpha\nhello");
1057 assert!(v.install_recovered_undo_tree(&single_node_tree("alpha\nhello", 3), 3));
1058
1059 let v = View::from_str("alpha\nhello");
1061 assert!(!v.install_recovered_undo_tree(&single_node_tree("alpha\nhello", 3), 4));
1062
1063 let v = View::from_str("alpha\nhello");
1065 assert!(!v.install_recovered_undo_tree(&single_node_tree("something else", 3), 3));
1066
1067 let v = View::from_str("alpha\nhello");
1069 assert!(v.install_recovered_undo_tree(&single_node_tree("alpha\nhello\n", 3), 3));
1070 }
1071
1072 fn vp_wrap(width: u16, height: u16) -> Viewport {
1073 Viewport {
1074 top_row: 0,
1075 top_col: 0,
1076 width,
1077 height,
1078 wrap: crate::Wrap::Char,
1079 text_width: width,
1080 tab_width: 0,
1081 }
1082 }
1083
1084 #[test]
1085 fn ensure_cursor_visible_wrap_scrolls_when_cursor_below_screen() {
1086 let mut b = View::from_str("aaaaaaaaaa\nb\nc");
1087 let mut v = vp_wrap(4, 3);
1088 b.set_cursor(Position::new(2, 0));
1089 b.ensure_cursor_visible(&mut v);
1090 assert_eq!(v.top_row, 1);
1091 }
1092
1093 #[test]
1094 fn ensure_cursor_visible_wrap_no_scroll_when_visible() {
1095 let mut b = View::from_str("aaaaaaaaaa\nb");
1096 let mut v = vp_wrap(4, 4);
1097 b.set_cursor(Position::new(0, 5));
1098 b.ensure_cursor_visible(&mut v);
1099 assert_eq!(v.top_row, 0);
1100 }
1101
1102 #[test]
1103 fn ensure_cursor_visible_wrap_snaps_top_when_cursor_above() {
1104 let mut b = View::from_str("a\nb\nc\nd\ne");
1105 let mut v = vp_wrap(4, 2);
1106 v.top_row = 3;
1107 b.set_cursor(Position::new(1, 0));
1108 b.ensure_cursor_visible(&mut v);
1109 assert_eq!(v.top_row, 1);
1110 }
1111
1112 #[test]
1113 fn screen_rows_between_sums_segments_under_wrap() {
1114 let b = View::from_str("aaaaaaaaa\nb\n");
1115 let v = vp_wrap(4, 0);
1116 assert_eq!(b.screen_rows_between(&v, 0, 0), 3);
1117 assert_eq!(b.screen_rows_between(&v, 0, 1), 4);
1118 assert_eq!(b.screen_rows_between(&v, 0, 2), 5);
1119 assert_eq!(b.screen_rows_between(&v, 1, 2), 2);
1120 }
1121
1122 #[test]
1123 fn screen_rows_between_one_per_doc_row_when_wrap_off() {
1124 let b = View::from_str("aaaaa\nb\nc");
1125 let v = Viewport::default();
1126 assert_eq!(b.screen_rows_between(&v, 0, 2), 3);
1127 }
1128
1129 #[test]
1130 fn max_top_for_height_walks_back_until_height_reached() {
1131 let b = View::from_str("a\nb\nc\nd\neeeeeeee");
1132 let v = vp_wrap(4, 0);
1133 assert_eq!(b.max_top_for_height(&v, 4), 2);
1134 assert_eq!(b.max_top_for_height(&v, 99), 0);
1135 }
1136
1137 #[test]
1138 fn cursor_screen_row_returns_none_when_wrap_off() {
1139 let b = View::from_str("a");
1140 let v = Viewport::default();
1141 assert!(b.cursor_screen_row(&v).is_none());
1142 }
1143
1144 #[test]
1145 fn cursor_screen_row_under_wrap() {
1146 let mut b = View::from_str("aaaaaaaaaa\nb");
1147 let v = vp_wrap(4, 0);
1148 b.set_cursor(Position::new(0, 5));
1149 assert_eq!(b.cursor_screen_row(&v), Some(1));
1150 b.set_cursor(Position::new(1, 0));
1151 assert_eq!(b.cursor_screen_row(&v), Some(3));
1152 }
1153
1154 #[test]
1158 fn cursor_screen_row_survives_shrink_from_other_view() {
1159 let a = View::from_str("a\nb\nc\nd\ne");
1160 let arc = a.content_arc();
1161 let mut view_a = View::new_view(Arc::clone(&arc));
1162 let mut view_b = View::new_view(Arc::clone(&arc));
1163 view_b.set_cursor(Position::new(4, 0));
1164 view_a.replace_all("a");
1166 let v = vp_wrap(4, 3);
1167 assert_eq!(view_b.cursor_screen_row(&v), Some(0));
1168 let mut v2 = vp_wrap(4, 3);
1169 view_b.ensure_cursor_visible(&mut v2); }
1171
1172 #[test]
1177 fn ensure_cursor_visible_clamps_stale_top_row_after_shrink() {
1178 let a = View::from_str("a\nb\nc\nd\ne");
1179 let arc = a.content_arc();
1180 let mut view_a = View::new_view(Arc::clone(&arc));
1181 let mut view_b = View::new_view(Arc::clone(&arc));
1182 view_b.set_cursor(Position::new(4, 0));
1183 view_a.replace_all("a");
1184 let mut v = vp_wrap(4, 3);
1185 v.top_row = 3;
1188 view_b.ensure_cursor_visible(&mut v);
1189 assert_eq!(v.top_row, 0, "top_row must clamp into the live rope");
1190 assert_eq!(v.top_col, 0);
1191 }
1192
1193 #[test]
1200 fn ensure_cursor_visible_survives_stale_cursor_row_after_shrink() {
1201 let a = View::from_str(
1203 "aa\nbb\nxxxxxxxxxxxxxxxxxxxxxxxxxxxx\ncc\ndd\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
1204 );
1205 let arc = a.content_arc();
1206 let mut view_a = View::new_view(Arc::clone(&arc));
1207 let mut view_b = View::new_view(Arc::clone(&arc));
1208 view_b.set_cursor(Position::new(5, 26));
1209 view_a.replace_all("aa\nbb\nxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
1212 let mut v = vp_wrap(4, 6);
1213 view_b.ensure_cursor_visible(&mut v); assert_eq!(
1215 v.top_row, 2,
1216 "top_row must scroll to the clamped cursor row, not past the rope"
1217 );
1218 assert_eq!(v.top_col, 0);
1219 }
1220
1221 #[test]
1225 fn ensure_cursor_visible_snaps_when_cursor_row_folded() {
1226 let mut b = View::from_str("a\nb\nc\nd\ne");
1227 b.set_folds(&[crate::Fold {
1228 start_row: 2,
1229 end_row: 4,
1230 closed: true,
1231 auto_generated: false,
1232 }]);
1233 let mut v = vp_wrap(4, 2);
1234 v.top_row = 1;
1235 b.set_cursor(Position::new(3, 0));
1236 b.ensure_cursor_visible(&mut v);
1237 assert_eq!(v.top_row, 3);
1238 }
1239
1240 #[test]
1241 fn ensure_cursor_visible_falls_back_when_wrap_disabled() {
1242 let mut b = View::from_str("a\nb\nc\nd\ne");
1243 let mut v = Viewport {
1244 top_row: 0,
1245 top_col: 0,
1246 width: 4,
1247 height: 2,
1248 wrap: crate::Wrap::None,
1249 text_width: 4,
1250 tab_width: 0,
1251 };
1252 b.set_cursor(Position::new(4, 0));
1253 b.ensure_cursor_visible(&mut v);
1254 assert_eq!(v.top_row, 3);
1255 }
1256
1257 #[test]
1263 fn undo_stack_shared_across_views() {
1264 use crate::UndoEntry;
1265 use std::time::SystemTime;
1266
1267 let a = View::from_str("hello");
1268 let arc = a.content_arc();
1269 let view_a = View::new_view(Arc::clone(&arc));
1270 let view_b = View::new_view(Arc::clone(&arc));
1271
1272 assert!(view_a.undo_stack_is_empty());
1273 assert_eq!(view_a.undo_stack_len(), 0);
1274
1275 view_a.push_undo_entry(UndoEntry {
1276 rope: view_a.rope(),
1277 cursor: (0, 0),
1278 timestamp: SystemTime::UNIX_EPOCH,
1279 marks: Default::default(),
1280 });
1281
1282 assert_eq!(view_b.undo_stack_len(), 1);
1284 assert!(!view_b.undo_stack_is_empty());
1285 }
1286
1287 #[test]
1292 fn redo_stack_shared_across_views() {
1293 use crate::UndoEntry;
1294 use std::time::SystemTime;
1295
1296 let a = View::from_str("world");
1297 let arc = a.content_arc();
1298 let view_a = View::new_view(Arc::clone(&arc));
1299 let view_b = View::new_view(Arc::clone(&arc));
1300
1301 assert!(view_a.redo_stack_is_empty());
1302
1303 view_b.push_undo_entry(UndoEntry {
1307 rope: view_b.rope(),
1308 cursor: (0, 0),
1309 timestamp: SystemTime::UNIX_EPOCH,
1310 marks: Default::default(),
1311 });
1312 view_b.undo_step(view_b.rope(), (0, 2), Default::default());
1313
1314 assert!(!view_a.redo_stack_is_empty());
1316 let entry = view_a.redo_step(view_a.rope(), (0, 0), Default::default());
1317 assert!(entry.is_some());
1318 assert_eq!(entry.unwrap().cursor, (0, 2));
1319 }
1320
1321 #[test]
1326 fn clear_undo_redo_shared_across_views() {
1327 use crate::UndoEntry;
1328 use std::time::SystemTime;
1329
1330 let a = View::from_str("abc");
1331 let arc = a.content_arc();
1332 let view_a = View::new_view(Arc::clone(&arc));
1333 let view_b = View::new_view(Arc::clone(&arc));
1334
1335 for _ in 0..2 {
1337 view_a.push_undo_entry(UndoEntry {
1338 rope: view_a.rope(),
1339 cursor: (0, 0),
1340 timestamp: SystemTime::UNIX_EPOCH,
1341 marks: Default::default(),
1342 });
1343 }
1344 view_a.undo_step(view_a.rope(), (0, 1), Default::default());
1345 assert!(!view_a.undo_stack_is_empty());
1346 assert!(!view_a.redo_stack_is_empty());
1347
1348 view_b.clear_undo_redo();
1349 assert!(view_a.undo_stack_is_empty());
1350 assert!(view_a.redo_stack_is_empty());
1351 }
1352
1353 #[test]
1355 fn content_dirty_shared_across_views() {
1356 let a = View::from_str("test");
1357 let arc = a.content_arc();
1358 let view_a = View::new_view(Arc::clone(&arc));
1359 let view_b = View::new_view(Arc::clone(&arc));
1360
1361 assert!(!view_a.content_dirty());
1362
1363 view_b.mark_content_dirty();
1364 assert!(view_a.content_dirty());
1365
1366 let taken = view_a.take_dirty();
1367 assert!(taken);
1368 assert!(!view_b.content_dirty());
1369 }
1370
1371 #[test]
1373 fn pending_fold_ops_shared_across_views() {
1374 let a = View::from_str("a\nb\nc");
1375 let arc = a.content_arc();
1376 let view_a = View::new_view(Arc::clone(&arc));
1377 let view_b = View::new_view(Arc::clone(&arc));
1378
1379 view_a.push_fold_op(crate::FoldOp::Add {
1380 start_row: 0,
1381 end_row: 1,
1382 closed: true,
1383 });
1384
1385 let ops = view_b.take_fold_ops();
1386 assert_eq!(ops.len(), 1);
1387 assert!(matches!(
1388 ops[0],
1389 crate::FoldOp::Add {
1390 start_row: 0,
1391 end_row: 1,
1392 closed: true
1393 }
1394 ));
1395 }
1396
1397 #[test]
1399 fn pending_content_reset_shared_across_views() {
1400 let a = View::from_str("x");
1401 let arc = a.content_arc();
1402 let view_a = View::new_view(Arc::clone(&arc));
1403 let view_b = View::new_view(Arc::clone(&arc));
1404
1405 assert!(!view_a.pending_content_reset());
1406 view_b.set_pending_content_reset(true);
1407 assert!(view_a.pending_content_reset());
1408 let taken = view_a.take_pending_content_reset();
1409 assert!(taken);
1410 assert!(!view_b.pending_content_reset());
1411 }
1412
1413 #[test]
1418 fn buffer_views_independent_cursors() {
1419 let a = View::from_str("hello\nworld");
1420 let arc = a.content_arc();
1421 let mut view_a = View::new_view(Arc::clone(&arc));
1422 let mut view_b = View::new_view(Arc::clone(&arc));
1423
1424 view_a.set_cursor(Position::new(1, 3));
1425 assert_eq!(view_b.cursor(), Position::new(0, 0));
1427
1428 view_b.set_cursor(Position::new(0, 2));
1429 assert_eq!(view_a.cursor(), Position::new(1, 3));
1431 }
1432
1433 #[test]
1437 fn last_cursor_reflects_most_recent_move_across_views() {
1438 let a = View::from_str("aaaa\nbbbb\ncccc\ndddd");
1439 let arc = a.content_arc();
1440 let mut view_a = View::new_view(Arc::clone(&arc));
1441 let mut view_b = View::new_view(Arc::clone(&arc));
1442
1443 view_a.set_cursor(Position::new(1, 2));
1444 assert_eq!(view_a.last_cursor(), (1, 2));
1445 assert_eq!(view_b.last_cursor(), (1, 2));
1447
1448 view_b.set_cursor(Position::new(3, 1));
1450 assert_eq!(view_a.last_cursor(), (3, 1));
1451 assert_eq!(view_b.last_cursor(), (3, 1));
1452
1453 view_a.set_cursor(Position::new(99, 99));
1455 assert_eq!(view_a.last_cursor(), (3, 4));
1456 }
1457
1458 #[test]
1462 fn clamp_position_restore_semantics() {
1463 let b = View::from_str("ab\ncdef\ng");
1464 assert_eq!(b.clamp_position(Position::new(99, 99)), Position::new(2, 1));
1466 assert_eq!(b.clamp_position(Position::new(1, 99)), Position::new(1, 4));
1468 assert_eq!(b.clamp_position(Position::new(1, 2)), Position::new(1, 2));
1470 }
1471
1472 #[test]
1474 fn buffer_views_share_content() {
1475 use crate::edit::Edit;
1476
1477 let a = View::from_str("foo");
1478 let arc = a.content_arc();
1479 let mut view_a = View::new_view(Arc::clone(&arc));
1480 let view_b = View::new_view(Arc::clone(&arc));
1481
1482 view_a.apply_edit(Edit::InsertStr {
1483 at: Position::new(0, 3),
1484 text: "bar".into(),
1485 });
1486
1487 assert_eq!(rope_line_str(&view_a.rope(), 0), "foobar");
1488 assert_eq!(rope_line_str(&view_b.rope(), 0), "foobar");
1489 }
1490
1491 #[test]
1496 fn line_measures_answer_zero_past_the_last_row() {
1497 let rope = ropey::Rope::from_str("café\nx");
1498 assert_eq!(rope.len_lines(), 2);
1499
1500 assert_eq!(rope_line_bytes(&rope, 0), 5);
1502 assert_eq!(rope_line_char_count(&rope, 0), 4);
1503
1504 assert_eq!(rope_line_bytes(&rope, 1), 1);
1506 assert_eq!(rope_line_char_count(&rope, 1), 1);
1507
1508 assert_eq!(rope_line_bytes(&rope, 2), 0);
1510 assert_eq!(rope_line_char_count(&rope, 2), 0);
1511 assert_eq!(rope_line_bytes(&rope, usize::MAX), 0);
1512 assert_eq!(rope_line_char_count(&rope, usize::MAX), 0);
1513 }
1514}
1515
1516#[cfg(test)]
1517mod marks_shared_content_tests {
1518 use super::*;
1519
1520 #[test]
1521 fn marks_shared_across_views() {
1522 let a = View::from_str("hello\nworld");
1524 let content = a.content_arc();
1525 let mut view_a = View::new_view(std::sync::Arc::clone(&content));
1526 let view_b = View::new_view(std::sync::Arc::clone(&content));
1527
1528 view_a.set_mark('x', (1, 3));
1530
1531 assert_eq!(view_b.mark('x'), Some((1, 3)));
1533 }
1534
1535 #[test]
1536 fn syntax_fold_ranges_shared_across_views() {
1537 let a = View::from_str("fn foo() {\n bar();\n}");
1538 let content = a.content_arc();
1539 let mut view_a = View::new_view(std::sync::Arc::clone(&content));
1540 let view_b = View::new_view(std::sync::Arc::clone(&content));
1541
1542 view_a.set_syntax_fold_ranges(vec![(0, 2)]);
1543
1544 assert_eq!(view_b.syntax_fold_ranges_cloned(), vec![(0, 2)]);
1545 }
1546}