1use std::sync::{Arc, Mutex, MutexGuard};
2
3use crate::content::Buffer;
4use crate::{Position, Viewport};
5
6pub struct View {
61 pub(crate) content: Arc<Mutex<Buffer>>,
63 cursor: Position,
66}
67
68impl Default for View {
69 fn default() -> Self {
70 Self::new()
71 }
72}
73
74impl View {
75 pub fn new() -> Self {
79 Self {
80 content: Arc::new(Mutex::new(Buffer::new())),
81 cursor: Position::default(),
82 }
83 }
84
85 #[allow(clippy::should_implement_trait)]
90 pub fn from_str(text: &str) -> Self {
91 Self {
92 content: Arc::new(Mutex::new(Buffer::from_str(text))),
93 cursor: Position::default(),
94 }
95 }
96
97 pub fn new_view(content: Arc<Mutex<Buffer>>) -> Self {
117 Self {
118 content,
119 cursor: Position::default(),
120 }
121 }
122
123 pub fn content_arc(&self) -> Arc<Mutex<Buffer>> {
126 Arc::clone(&self.content)
127 }
128
129 pub fn cursor(&self) -> Position {
132 self.cursor
133 }
134
135 pub fn last_cursor(&self) -> (usize, usize) {
140 self.content.lock().unwrap().last_cursor
141 }
142
143 pub fn dirty_gen(&self) -> u64 {
144 self.content.lock().unwrap().dirty_gen
145 }
146
147 pub fn row_count(&self) -> usize {
149 self.content.lock().unwrap().text.len_lines()
150 }
151
152 pub fn as_string(&self) -> String {
157 self.content.lock().unwrap().text.to_string()
158 }
159
160 pub fn set_cursor(&mut self, pos: Position) {
167 let mut c = self.content.lock().unwrap();
168 let n = c.text.len_lines();
169 let last_row = n.saturating_sub(1);
170 let row = pos.row.min(last_row);
171 let line_chars = rope_line_char_count(&c.text, row);
172 let col = pos.col.min(line_chars);
173 c.last_cursor = (row, col);
177 drop(c);
178 self.cursor = Position::new(row, col);
179 }
180
181 pub fn ensure_cursor_visible(&mut self, viewport: &mut Viewport) {
184 let cursor = self.cursor;
185 let v = *viewport;
186 let wrap_active = !matches!(v.wrap, crate::Wrap::None) && v.text_width > 0;
187 if !wrap_active {
188 viewport.ensure_visible(cursor);
189 return;
190 }
191 if v.height == 0 {
192 return;
193 }
194 if cursor.row < v.top_row {
196 viewport.top_row = cursor.row;
197 viewport.top_col = 0;
198 return;
199 }
200 let height = v.height as usize;
201 let Some(mut screen) = self.cursor_screen_row_from(viewport, viewport.top_row) else {
207 let last_row = {
218 let c = self.content.lock().unwrap();
219 c.text.len_lines().saturating_sub(1)
220 };
221 viewport.top_row = cursor.row.min(last_row);
222 viewport.top_col = 0;
223 return;
224 };
225 while screen >= height {
226 let c = self.content.lock().unwrap();
227 let mut next = viewport.top_row + 1;
228 while next <= cursor.row && c.folds.iter().any(|f| f.hides(next)) {
229 next += 1;
230 }
231 if next > cursor.row {
232 drop(c);
233 viewport.top_row = cursor.row;
234 break;
235 }
236 for r in viewport.top_row..next {
240 if c.folds.iter().any(|f| f.hides(r)) {
241 continue;
242 }
243 let line = rope_line_str(&c.text, r);
244 screen -= crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len();
245 }
246 drop(c);
247 viewport.top_row = next;
248 }
249 viewport.top_col = 0;
250 }
251
252 pub fn cursor_screen_row(&self, viewport: &Viewport) -> Option<usize> {
254 if matches!(viewport.wrap, crate::Wrap::None) || viewport.text_width == 0 {
255 return None;
256 }
257 self.cursor_screen_row_from(viewport, viewport.top_row)
258 }
259
260 pub fn screen_rows_between(&self, viewport: &Viewport, start: usize, end: usize) -> usize {
262 if start > end {
263 return 0;
264 }
265 let c = self.content.lock().unwrap();
266 let n = c.text.len_lines();
267 let last = n.saturating_sub(1);
268 let end = end.min(last);
269 let v = *viewport;
270 let mut total = 0usize;
271 for r in start..=end {
272 if c.folds.iter().any(|f| f.hides(r)) {
273 continue;
274 }
275 if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
276 total += 1;
277 } else {
278 let line = rope_line_str(&c.text, r);
279 total += crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len();
280 }
281 }
282 total
283 }
284
285 pub fn max_top_for_height(&self, viewport: &Viewport, height: usize) -> usize {
288 if height == 0 {
289 return 0;
290 }
291 let c = self.content.lock().unwrap();
292 let n = c.text.len_lines();
293 let last = n.saturating_sub(1);
294 let mut total = 0usize;
295 let mut row = last;
296 loop {
297 if !c.folds.iter().any(|f| f.hides(row)) {
298 let v = *viewport;
299 total += if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
300 1
301 } else {
302 let line = rope_line_str(&c.text, row);
303 crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len()
304 };
305 }
306 if total >= height {
307 return row;
308 }
309 if row == 0 {
310 return 0;
311 }
312 row -= 1;
313 }
314 }
315
316 pub fn clamp_position(&self, pos: Position) -> Position {
318 let c = self.content.lock().unwrap();
319 let n = c.text.len_lines();
320 let last_row = n.saturating_sub(1);
321 let row = pos.row.min(last_row);
322 let line_chars = rope_line_char_count(&c.text, row);
323 let col = pos.col.min(line_chars);
324 Position::new(row, col)
325 }
326
327 pub fn replace_all(&mut self, text: &str) {
330 let new_cursor = {
331 let mut c = self.content.lock().unwrap();
332 c.text = ropey::Rope::from_str(text);
333 let n = c.text.len_lines();
334 let last_row = n.saturating_sub(1);
335 let row = self.cursor.row.min(last_row);
336 let line_chars = rope_line_char_count(&c.text, row);
337 let col = self.cursor.col.min(line_chars);
338 c.dirty_gen = c.dirty_gen.wrapping_add(1);
339 c.cached_joined = None;
340 c.cached_byte_len = None;
341 Position::new(row, col)
342 };
343 self.cursor = new_cursor;
344 }
345
346 pub(crate) fn fold_gen_bump(&mut self) {
354 let mut c = self.content.lock().unwrap();
355 c.fold_gen = c.fold_gen.wrapping_add(1);
356 }
357
358 pub(crate) fn dirty_gen_bump(&mut self) {
360 let mut c = self.content.lock().unwrap();
361 c.dirty_gen = c.dirty_gen.wrapping_add(1);
362 c.cached_joined = None;
363 c.cached_byte_len = None;
364 }
365
366 pub fn byte_len(&self) -> usize {
372 let mut c = self.content.lock().unwrap();
373 let dg = c.dirty_gen;
374 if let Some((cached_dg, len)) = c.cached_byte_len
375 && cached_dg == dg
376 {
377 return len;
378 }
379 let total = c.text.len_bytes();
380 c.cached_byte_len = Some((dg, total));
381 total
382 }
383
384 pub fn content_joined(&self) -> std::sync::Arc<String> {
393 let mut c = self.content.lock().unwrap();
394 let dg = c.dirty_gen;
395 if let Some((cached_dg, ref s)) = c.cached_joined
396 && cached_dg == dg
397 {
398 return std::sync::Arc::clone(s);
399 }
400 let joined = std::sync::Arc::new(c.text.to_string());
401 c.cached_joined = Some((dg, std::sync::Arc::clone(&joined)));
402 joined
403 }
404
405 pub fn rope(&self) -> ropey::Rope {
414 self.content.lock().unwrap().text.clone()
415 }
416
417 pub(crate) fn content_lock(&self) -> MutexGuard<'_, Buffer> {
419 self.content.lock().unwrap()
420 }
421
422 pub(crate) fn content_lock_mut(&mut self) -> MutexGuard<'_, Buffer> {
424 self.content.lock().unwrap()
425 }
426
427 fn cursor_screen_row_from(&self, viewport: &Viewport, top: usize) -> Option<usize> {
430 let cursor = self.cursor;
431 if cursor.row < top {
432 return None;
433 }
434 let c = self.content.lock().unwrap();
435 let cursor_row = cursor.row.min(c.text.len_lines().saturating_sub(1));
439 if cursor_row < top {
440 return None;
441 }
442 let v = *viewport;
443 let mut screen = 0usize;
444 for r in top..=cursor_row {
445 if c.folds.iter().any(|f| f.hides(r)) {
446 continue;
447 }
448 let line = rope_line_str(&c.text, r);
449 let segs = crate::wrap::wrap_segments(&line, v.text_width, v.wrap);
450 if r == cursor_row {
451 let seg_idx = crate::wrap::segment_for_col(&segs, cursor.col);
452 return Some(screen + seg_idx);
453 }
454 screen += segs.len();
455 }
456 None
457 }
458
459 pub fn undo_stack_is_empty(&self) -> bool {
469 self.content.lock().unwrap().undo.is_at_root()
470 }
471
472 pub fn redo_stack_is_empty(&self) -> bool {
473 !self.content.lock().unwrap().undo.has_redo()
474 }
475
476 pub fn undo_stack_len(&self) -> usize {
477 self.content.lock().unwrap().undo.depth()
478 }
479
480 pub fn push_undo_entry(&self, entry: crate::UndoEntry) {
484 self.content.lock().unwrap().undo.push(entry);
485 }
486
487 pub fn undo_step(
491 &self,
492 rope: ropey::Rope,
493 cursor: (usize, usize),
494 marks: crate::MarkSnapshot,
495 ) -> Option<crate::UndoEntry> {
496 self.content
497 .lock()
498 .unwrap()
499 .undo
500 .undo_step(rope, cursor, marks)
501 }
502
503 pub fn redo_step(
506 &self,
507 rope: ropey::Rope,
508 cursor: (usize, usize),
509 marks: crate::MarkSnapshot,
510 ) -> Option<crate::UndoEntry> {
511 self.content
512 .lock()
513 .unwrap()
514 .undo
515 .redo_step(rope, cursor, marks)
516 }
517
518 pub fn pop_committed(&self) -> bool {
522 self.content.lock().unwrap().undo.pop_committed()
523 }
524
525 pub fn seq_earlier_step(
530 &self,
531 rope: ropey::Rope,
532 cursor: (usize, usize),
533 marks: crate::MarkSnapshot,
534 ) -> Option<crate::UndoEntry> {
535 self.content
536 .lock()
537 .unwrap()
538 .undo
539 .seq_earlier_step(rope, cursor, marks)
540 }
541
542 pub fn seq_later_step(
545 &self,
546 rope: ropey::Rope,
547 cursor: (usize, usize),
548 marks: crate::MarkSnapshot,
549 ) -> Option<crate::UndoEntry> {
550 self.content
551 .lock()
552 .unwrap()
553 .undo
554 .seq_later_step(rope, cursor, marks)
555 }
556
557 pub fn seq_earlier_timestamp(&self) -> Option<std::time::SystemTime> {
559 self.content.lock().unwrap().undo.seq_earlier_timestamp()
560 }
561
562 pub fn seq_later_timestamp(&self) -> Option<std::time::SystemTime> {
564 self.content.lock().unwrap().undo.seq_later_timestamp()
565 }
566
567 pub fn undo_leaves(&self) -> Vec<(u64, usize, std::time::SystemTime, bool)> {
570 self.content.lock().unwrap().undo.leaves()
571 }
572
573 pub fn peek_undo_timestamp(&self) -> Option<std::time::SystemTime> {
574 self.content.lock().unwrap().undo.parent_timestamp()
575 }
576
577 pub fn peek_redo_timestamp(&self) -> Option<std::time::SystemTime> {
578 self.content.lock().unwrap().undo.child_timestamp()
579 }
580
581 pub fn clear_undo_redo(&self) {
582 self.content.lock().unwrap().undo.clear_all();
583 }
584
585 pub fn undo_to_serializable(&self) -> (crate::SerTree, u64) {
592 let mut c = self.content.lock().unwrap();
593 let rope = c.text.clone();
594 c.undo.sync_current(rope);
595 let seq = c.undo.current_node_seq();
596 (c.undo.to_serializable(), seq)
597 }
598
599 pub fn install_undo_tree(&self, ser: &crate::SerTree) -> bool {
605 match crate::UndoTree::from_serializable(ser) {
606 Some(tree) => {
607 self.content.lock().unwrap().undo = tree;
608 true
609 }
610 None => false,
611 }
612 }
613
614 pub fn install_recovered_undo_tree(&self, ser: &crate::SerTree, current_seq: u64) -> bool {
628 let Some(mut tree) = crate::UndoTree::from_serializable(ser) else {
629 return false;
630 };
631 if tree.current_node_seq() != current_seq {
632 return false;
633 }
634 let mut c = self.content.lock().unwrap();
635 let materialized = tree.current_content();
636 if materialized.to_string().trim_end_matches('\n')
637 != c.text.to_string().trim_end_matches('\n')
638 {
639 return false;
640 }
641 c.undo = tree;
642 true
643 }
644
645 pub fn clear_redo(&self) {
646 self.content.lock().unwrap().undo.clear_redo();
647 }
648
649 pub fn undo_group_active(&self) -> bool {
652 self.content.lock().unwrap().undo_group_active()
653 }
654
655 pub fn undo_group_arm(&self) -> bool {
657 self.content.lock().unwrap().undo_group_arm()
658 }
659
660 pub fn cap_undo(&self, cap: usize) {
661 self.content.lock().unwrap().undo.cap(cap);
662 }
663
664 pub fn content_dirty(&self) -> bool {
665 self.content.lock().unwrap().content_dirty
666 }
667
668 pub fn set_content_dirty(&self, v: bool) {
669 self.content.lock().unwrap().content_dirty = v;
670 }
671
672 pub fn mark_content_dirty(&self) {
673 let mut c = self.content.lock().unwrap();
674 c.content_dirty = true;
675 c.cached_editor_content = None;
676 }
677
678 pub fn take_dirty(&self) -> bool {
679 let mut c = self.content.lock().unwrap();
680 let v = c.content_dirty;
681 c.content_dirty = false;
682 v
683 }
684
685 pub fn cached_editor_content(&self) -> Option<std::sync::Arc<String>> {
686 self.content.lock().unwrap().cached_editor_content.clone()
687 }
688
689 pub fn set_cached_editor_content(&self, arc: std::sync::Arc<String>) {
690 self.content.lock().unwrap().cached_editor_content = Some(arc);
691 }
692
693 pub fn push_fold_op(&self, op: crate::FoldOp) {
694 self.content.lock().unwrap().pending_fold_ops.push(op);
695 }
696
697 pub fn take_fold_ops(&self) -> Vec<crate::FoldOp> {
698 std::mem::take(&mut self.content.lock().unwrap().pending_fold_ops)
699 }
700
701 pub fn extend_change_log(&self, edits: impl IntoIterator<Item = crate::EngineEdit>) {
702 self.content.lock().unwrap().change_log.extend(edits);
703 }
704
705 pub fn take_change_log(&self) -> Vec<crate::EngineEdit> {
706 std::mem::take(&mut self.content.lock().unwrap().change_log)
707 }
708
709 pub fn extend_pending_content_edits(
710 &self,
711 edits: impl IntoIterator<Item = crate::ContentEdit>,
712 ) {
713 self.content
714 .lock()
715 .unwrap()
716 .pending_content_edits
717 .extend(edits);
718 }
719
720 pub fn push_pending_content_edit(&self, edit: crate::ContentEdit) {
721 self.content
722 .lock()
723 .unwrap()
724 .pending_content_edits
725 .push(edit);
726 }
727
728 pub fn take_pending_content_edits(&self) -> Vec<crate::ContentEdit> {
729 std::mem::take(&mut self.content.lock().unwrap().pending_content_edits)
730 }
731
732 pub fn clear_pending_content_edits(&self) {
733 self.content.lock().unwrap().pending_content_edits.clear();
734 }
735
736 pub fn pending_content_reset(&self) -> bool {
737 self.content.lock().unwrap().pending_content_reset
738 }
739
740 pub fn set_pending_content_reset(&self, v: bool) {
741 self.content.lock().unwrap().pending_content_reset = v;
742 }
743
744 pub fn take_pending_content_reset(&self) -> bool {
745 let mut c = self.content.lock().unwrap();
746 let v = c.pending_content_reset;
747 c.pending_content_reset = false;
748 v
749 }
750
751 pub fn mark(&self, c: char) -> Option<(usize, usize)> {
752 self.content_lock().marks.get(&c).copied()
753 }
754 pub fn set_mark(&mut self, c: char, pos: (usize, usize)) {
755 self.content_lock_mut().marks.insert(c, pos);
756 }
757 pub fn clear_mark(&mut self, c: char) {
758 self.content_lock_mut().marks.remove(&c);
759 }
760 pub fn marks_cloned(&self) -> std::collections::BTreeMap<char, (usize, usize)> {
761 self.content_lock().marks.clone()
762 }
763 pub fn set_marks(&mut self, marks: std::collections::BTreeMap<char, (usize, usize)>) {
764 self.content_lock_mut().marks = marks;
765 }
766 pub fn rebase_marks(
770 &mut self,
771 edit_start: usize,
772 drop_end: usize,
773 shift_threshold: usize,
774 delta: isize,
775 ) {
776 let mut c = self.content_lock_mut();
777 let mut to_drop: Vec<char> = Vec::new();
778 for (ch, (row, _col)) in c.marks.iter_mut() {
779 if (edit_start..drop_end).contains(row) {
780 to_drop.push(*ch);
781 } else if *row >= shift_threshold {
782 *row = ((*row as isize) + delta).max(0) as usize;
783 }
784 }
785 for ch in to_drop {
786 c.marks.remove(&ch);
787 }
788 }
789 pub fn syntax_fold_ranges_cloned(&self) -> Vec<(usize, usize)> {
790 self.content_lock().syntax_fold_ranges.clone()
791 }
792 pub fn set_syntax_fold_ranges(&mut self, ranges: Vec<(usize, usize)>) {
793 self.content_lock_mut().syntax_fold_ranges = ranges;
794 }
795}
796
797pub fn floor_char_boundary(rope: &ropey::Rope, byte_idx: usize) -> usize {
810 let byte_idx = byte_idx.min(rope.len_bytes());
811 rope.char_to_byte(rope.byte_to_char(byte_idx))
812}
813
814pub fn rope_line_str(rope: &ropey::Rope, row: usize) -> String {
817 let mut s = rope.line(row).to_string();
818 if s.ends_with('\n') {
820 s.pop();
821 }
822 s
823}
824
825pub fn rope_line_bytes(rope: &ropey::Rope, row: usize) -> usize {
827 let slice = rope.line(row);
828 let bytes = slice.len_bytes();
829 if row + 1 < rope.len_lines() && bytes > 0 {
831 bytes - 1
832 } else {
833 bytes
834 }
835}
836
837pub fn rope_line_char_count(rope: &ropey::Rope, row: usize) -> usize {
839 let slice = rope.line(row);
840 let chars = slice.len_chars();
841 if row + 1 < rope.len_lines() && chars > 0 {
843 chars - 1
844 } else {
845 chars
846 }
847}
848
849pub fn pos_to_char_idx(rope: &ropey::Rope, row: usize, col: usize) -> usize {
854 let row = row.min(rope.len_lines().saturating_sub(1));
855 let line_start = rope.line_to_char(row);
856 let line_char_count = rope_line_char_count(rope, row);
857 line_start + col.min(line_char_count)
858}
859
860#[cfg(test)]
861mod tests {
862 use super::*;
863
864 #[test]
865 fn new_has_one_empty_row() {
866 let b = View::new();
867 assert_eq!(b.row_count(), 1);
868 assert_eq!(rope_line_str(&b.rope(), 0), "");
869 assert_eq!(b.cursor(), Position::default());
870 }
871
872 #[test]
873 fn from_str_splits_on_newline() {
874 let b = View::from_str("foo\nbar\nbaz");
875 assert_eq!(b.row_count(), 3);
876 assert_eq!(rope_line_str(&b.rope(), 0), "foo");
877 assert_eq!(rope_line_str(&b.rope(), 2), "baz");
878 }
879
880 #[test]
881 fn from_str_trailing_newline_keeps_empty_row() {
882 let b = View::from_str("foo\n");
883 assert_eq!(b.row_count(), 2);
884 assert_eq!(rope_line_str(&b.rope(), 1), "");
885 }
886
887 #[test]
888 fn from_str_empty_input_keeps_one_row() {
889 let b = View::from_str("");
890 assert_eq!(b.row_count(), 1);
891 assert_eq!(rope_line_str(&b.rope(), 0), "");
892 }
893
894 #[test]
895 fn as_string_round_trips() {
896 let b = View::from_str("a\nb\nc");
897 assert_eq!(b.as_string(), "a\nb\nc");
898 }
899
900 #[test]
901 fn dirty_gen_starts_at_zero() {
902 assert_eq!(View::new().dirty_gen(), 0);
903 }
904
905 fn single_node_tree(base: &str, seq: u64) -> crate::SerTree {
908 crate::SerTree {
909 base: base.to_string(),
910 nodes: vec![crate::SerNode {
911 parent: None,
912 children: Vec::new(),
913 last_child: None,
914 delta: None,
915 cursor: (0, 0),
916 timestamp_unix_ms: 0,
917 marks: crate::MarkSnapshot::default(),
918 seq,
919 }],
920 root: 0,
921 current: 0,
922 next_seq: seq + 1,
923 }
924 }
925
926 #[test]
930 fn install_recovered_undo_tree_guards_seq_and_content() {
931 let v = View::from_str("alpha\nhello");
933 assert!(v.install_recovered_undo_tree(&single_node_tree("alpha\nhello", 3), 3));
934
935 let v = View::from_str("alpha\nhello");
937 assert!(!v.install_recovered_undo_tree(&single_node_tree("alpha\nhello", 3), 4));
938
939 let v = View::from_str("alpha\nhello");
941 assert!(!v.install_recovered_undo_tree(&single_node_tree("something else", 3), 3));
942
943 let v = View::from_str("alpha\nhello");
945 assert!(v.install_recovered_undo_tree(&single_node_tree("alpha\nhello\n", 3), 3));
946 }
947
948 fn vp_wrap(width: u16, height: u16) -> Viewport {
949 Viewport {
950 top_row: 0,
951 top_col: 0,
952 width,
953 height,
954 wrap: crate::Wrap::Char,
955 text_width: width,
956 tab_width: 0,
957 }
958 }
959
960 #[test]
961 fn ensure_cursor_visible_wrap_scrolls_when_cursor_below_screen() {
962 let mut b = View::from_str("aaaaaaaaaa\nb\nc");
963 let mut v = vp_wrap(4, 3);
964 b.set_cursor(Position::new(2, 0));
965 b.ensure_cursor_visible(&mut v);
966 assert_eq!(v.top_row, 1);
967 }
968
969 #[test]
970 fn ensure_cursor_visible_wrap_no_scroll_when_visible() {
971 let mut b = View::from_str("aaaaaaaaaa\nb");
972 let mut v = vp_wrap(4, 4);
973 b.set_cursor(Position::new(0, 5));
974 b.ensure_cursor_visible(&mut v);
975 assert_eq!(v.top_row, 0);
976 }
977
978 #[test]
979 fn ensure_cursor_visible_wrap_snaps_top_when_cursor_above() {
980 let mut b = View::from_str("a\nb\nc\nd\ne");
981 let mut v = vp_wrap(4, 2);
982 v.top_row = 3;
983 b.set_cursor(Position::new(1, 0));
984 b.ensure_cursor_visible(&mut v);
985 assert_eq!(v.top_row, 1);
986 }
987
988 #[test]
989 fn screen_rows_between_sums_segments_under_wrap() {
990 let b = View::from_str("aaaaaaaaa\nb\n");
991 let v = vp_wrap(4, 0);
992 assert_eq!(b.screen_rows_between(&v, 0, 0), 3);
993 assert_eq!(b.screen_rows_between(&v, 0, 1), 4);
994 assert_eq!(b.screen_rows_between(&v, 0, 2), 5);
995 assert_eq!(b.screen_rows_between(&v, 1, 2), 2);
996 }
997
998 #[test]
999 fn screen_rows_between_one_per_doc_row_when_wrap_off() {
1000 let b = View::from_str("aaaaa\nb\nc");
1001 let v = Viewport::default();
1002 assert_eq!(b.screen_rows_between(&v, 0, 2), 3);
1003 }
1004
1005 #[test]
1006 fn max_top_for_height_walks_back_until_height_reached() {
1007 let b = View::from_str("a\nb\nc\nd\neeeeeeee");
1008 let v = vp_wrap(4, 0);
1009 assert_eq!(b.max_top_for_height(&v, 4), 2);
1010 assert_eq!(b.max_top_for_height(&v, 99), 0);
1011 }
1012
1013 #[test]
1014 fn cursor_screen_row_returns_none_when_wrap_off() {
1015 let b = View::from_str("a");
1016 let v = Viewport::default();
1017 assert!(b.cursor_screen_row(&v).is_none());
1018 }
1019
1020 #[test]
1021 fn cursor_screen_row_under_wrap() {
1022 let mut b = View::from_str("aaaaaaaaaa\nb");
1023 let v = vp_wrap(4, 0);
1024 b.set_cursor(Position::new(0, 5));
1025 assert_eq!(b.cursor_screen_row(&v), Some(1));
1026 b.set_cursor(Position::new(1, 0));
1027 assert_eq!(b.cursor_screen_row(&v), Some(3));
1028 }
1029
1030 #[test]
1034 fn cursor_screen_row_survives_shrink_from_other_view() {
1035 let a = View::from_str("a\nb\nc\nd\ne");
1036 let arc = a.content_arc();
1037 let mut view_a = View::new_view(Arc::clone(&arc));
1038 let mut view_b = View::new_view(Arc::clone(&arc));
1039 view_b.set_cursor(Position::new(4, 0));
1040 view_a.replace_all("a");
1042 let v = vp_wrap(4, 3);
1043 assert_eq!(view_b.cursor_screen_row(&v), Some(0));
1044 let mut v2 = vp_wrap(4, 3);
1045 view_b.ensure_cursor_visible(&mut v2); }
1047
1048 #[test]
1053 fn ensure_cursor_visible_clamps_stale_top_row_after_shrink() {
1054 let a = View::from_str("a\nb\nc\nd\ne");
1055 let arc = a.content_arc();
1056 let mut view_a = View::new_view(Arc::clone(&arc));
1057 let mut view_b = View::new_view(Arc::clone(&arc));
1058 view_b.set_cursor(Position::new(4, 0));
1059 view_a.replace_all("a");
1060 let mut v = vp_wrap(4, 3);
1061 v.top_row = 3;
1064 view_b.ensure_cursor_visible(&mut v);
1065 assert_eq!(v.top_row, 0, "top_row must clamp into the live rope");
1066 assert_eq!(v.top_col, 0);
1067 }
1068
1069 #[test]
1073 fn ensure_cursor_visible_snaps_when_cursor_row_folded() {
1074 let mut b = View::from_str("a\nb\nc\nd\ne");
1075 b.set_folds(&[crate::Fold {
1076 start_row: 2,
1077 end_row: 4,
1078 closed: true,
1079 auto_generated: false,
1080 }]);
1081 let mut v = vp_wrap(4, 2);
1082 v.top_row = 1;
1083 b.set_cursor(Position::new(3, 0));
1084 b.ensure_cursor_visible(&mut v);
1085 assert_eq!(v.top_row, 3);
1086 }
1087
1088 #[test]
1089 fn ensure_cursor_visible_falls_back_when_wrap_disabled() {
1090 let mut b = View::from_str("a\nb\nc\nd\ne");
1091 let mut v = Viewport {
1092 top_row: 0,
1093 top_col: 0,
1094 width: 4,
1095 height: 2,
1096 wrap: crate::Wrap::None,
1097 text_width: 4,
1098 tab_width: 0,
1099 };
1100 b.set_cursor(Position::new(4, 0));
1101 b.ensure_cursor_visible(&mut v);
1102 assert_eq!(v.top_row, 3);
1103 }
1104
1105 #[test]
1111 fn undo_stack_shared_across_views() {
1112 use crate::UndoEntry;
1113 use std::time::SystemTime;
1114
1115 let a = View::from_str("hello");
1116 let arc = a.content_arc();
1117 let view_a = View::new_view(Arc::clone(&arc));
1118 let view_b = View::new_view(Arc::clone(&arc));
1119
1120 assert!(view_a.undo_stack_is_empty());
1121 assert_eq!(view_a.undo_stack_len(), 0);
1122
1123 view_a.push_undo_entry(UndoEntry {
1124 rope: view_a.rope(),
1125 cursor: (0, 0),
1126 timestamp: SystemTime::UNIX_EPOCH,
1127 marks: Default::default(),
1128 });
1129
1130 assert_eq!(view_b.undo_stack_len(), 1);
1132 assert!(!view_b.undo_stack_is_empty());
1133 }
1134
1135 #[test]
1140 fn redo_stack_shared_across_views() {
1141 use crate::UndoEntry;
1142 use std::time::SystemTime;
1143
1144 let a = View::from_str("world");
1145 let arc = a.content_arc();
1146 let view_a = View::new_view(Arc::clone(&arc));
1147 let view_b = View::new_view(Arc::clone(&arc));
1148
1149 assert!(view_a.redo_stack_is_empty());
1150
1151 view_b.push_undo_entry(UndoEntry {
1155 rope: view_b.rope(),
1156 cursor: (0, 0),
1157 timestamp: SystemTime::UNIX_EPOCH,
1158 marks: Default::default(),
1159 });
1160 view_b.undo_step(view_b.rope(), (0, 2), Default::default());
1161
1162 assert!(!view_a.redo_stack_is_empty());
1164 let entry = view_a.redo_step(view_a.rope(), (0, 0), Default::default());
1165 assert!(entry.is_some());
1166 assert_eq!(entry.unwrap().cursor, (0, 2));
1167 }
1168
1169 #[test]
1174 fn clear_undo_redo_shared_across_views() {
1175 use crate::UndoEntry;
1176 use std::time::SystemTime;
1177
1178 let a = View::from_str("abc");
1179 let arc = a.content_arc();
1180 let view_a = View::new_view(Arc::clone(&arc));
1181 let view_b = View::new_view(Arc::clone(&arc));
1182
1183 for _ in 0..2 {
1185 view_a.push_undo_entry(UndoEntry {
1186 rope: view_a.rope(),
1187 cursor: (0, 0),
1188 timestamp: SystemTime::UNIX_EPOCH,
1189 marks: Default::default(),
1190 });
1191 }
1192 view_a.undo_step(view_a.rope(), (0, 1), Default::default());
1193 assert!(!view_a.undo_stack_is_empty());
1194 assert!(!view_a.redo_stack_is_empty());
1195
1196 view_b.clear_undo_redo();
1197 assert!(view_a.undo_stack_is_empty());
1198 assert!(view_a.redo_stack_is_empty());
1199 }
1200
1201 #[test]
1203 fn content_dirty_shared_across_views() {
1204 let a = View::from_str("test");
1205 let arc = a.content_arc();
1206 let view_a = View::new_view(Arc::clone(&arc));
1207 let view_b = View::new_view(Arc::clone(&arc));
1208
1209 assert!(!view_a.content_dirty());
1210
1211 view_b.mark_content_dirty();
1212 assert!(view_a.content_dirty());
1213
1214 let taken = view_a.take_dirty();
1215 assert!(taken);
1216 assert!(!view_b.content_dirty());
1217 }
1218
1219 #[test]
1221 fn pending_fold_ops_shared_across_views() {
1222 let a = View::from_str("a\nb\nc");
1223 let arc = a.content_arc();
1224 let view_a = View::new_view(Arc::clone(&arc));
1225 let view_b = View::new_view(Arc::clone(&arc));
1226
1227 view_a.push_fold_op(crate::FoldOp::Add {
1228 start_row: 0,
1229 end_row: 1,
1230 closed: true,
1231 });
1232
1233 let ops = view_b.take_fold_ops();
1234 assert_eq!(ops.len(), 1);
1235 assert!(matches!(
1236 ops[0],
1237 crate::FoldOp::Add {
1238 start_row: 0,
1239 end_row: 1,
1240 closed: true
1241 }
1242 ));
1243 }
1244
1245 #[test]
1247 fn pending_content_reset_shared_across_views() {
1248 let a = View::from_str("x");
1249 let arc = a.content_arc();
1250 let view_a = View::new_view(Arc::clone(&arc));
1251 let view_b = View::new_view(Arc::clone(&arc));
1252
1253 assert!(!view_a.pending_content_reset());
1254 view_b.set_pending_content_reset(true);
1255 assert!(view_a.pending_content_reset());
1256 let taken = view_a.take_pending_content_reset();
1257 assert!(taken);
1258 assert!(!view_b.pending_content_reset());
1259 }
1260
1261 #[test]
1266 fn buffer_views_independent_cursors() {
1267 let a = View::from_str("hello\nworld");
1268 let arc = a.content_arc();
1269 let mut view_a = View::new_view(Arc::clone(&arc));
1270 let mut view_b = View::new_view(Arc::clone(&arc));
1271
1272 view_a.set_cursor(Position::new(1, 3));
1273 assert_eq!(view_b.cursor(), Position::new(0, 0));
1275
1276 view_b.set_cursor(Position::new(0, 2));
1277 assert_eq!(view_a.cursor(), Position::new(1, 3));
1279 }
1280
1281 #[test]
1285 fn last_cursor_reflects_most_recent_move_across_views() {
1286 let a = View::from_str("aaaa\nbbbb\ncccc\ndddd");
1287 let arc = a.content_arc();
1288 let mut view_a = View::new_view(Arc::clone(&arc));
1289 let mut view_b = View::new_view(Arc::clone(&arc));
1290
1291 view_a.set_cursor(Position::new(1, 2));
1292 assert_eq!(view_a.last_cursor(), (1, 2));
1293 assert_eq!(view_b.last_cursor(), (1, 2));
1295
1296 view_b.set_cursor(Position::new(3, 1));
1298 assert_eq!(view_a.last_cursor(), (3, 1));
1299 assert_eq!(view_b.last_cursor(), (3, 1));
1300
1301 view_a.set_cursor(Position::new(99, 99));
1303 assert_eq!(view_a.last_cursor(), (3, 4));
1304 }
1305
1306 #[test]
1310 fn clamp_position_restore_semantics() {
1311 let b = View::from_str("ab\ncdef\ng");
1312 assert_eq!(b.clamp_position(Position::new(99, 99)), Position::new(2, 1));
1314 assert_eq!(b.clamp_position(Position::new(1, 99)), Position::new(1, 4));
1316 assert_eq!(b.clamp_position(Position::new(1, 2)), Position::new(1, 2));
1318 }
1319
1320 #[test]
1322 fn buffer_views_share_content() {
1323 use crate::edit::Edit;
1324
1325 let a = View::from_str("foo");
1326 let arc = a.content_arc();
1327 let mut view_a = View::new_view(Arc::clone(&arc));
1328 let view_b = View::new_view(Arc::clone(&arc));
1329
1330 view_a.apply_edit(Edit::InsertStr {
1331 at: Position::new(0, 3),
1332 text: "bar".into(),
1333 });
1334
1335 assert_eq!(rope_line_str(&view_a.rope(), 0), "foobar");
1336 assert_eq!(rope_line_str(&view_b.rope(), 0), "foobar");
1337 }
1338}
1339
1340#[cfg(test)]
1341mod marks_shared_content_tests {
1342 use super::*;
1343
1344 #[test]
1345 fn marks_shared_across_views() {
1346 let a = View::from_str("hello\nworld");
1348 let content = a.content_arc();
1349 let mut view_a = View::new_view(std::sync::Arc::clone(&content));
1350 let view_b = View::new_view(std::sync::Arc::clone(&content));
1351
1352 view_a.set_mark('x', (1, 3));
1354
1355 assert_eq!(view_b.mark('x'), Some((1, 3)));
1357 }
1358
1359 #[test]
1360 fn syntax_fold_ranges_shared_across_views() {
1361 let a = View::from_str("fn foo() {\n bar();\n}");
1362 let content = a.content_arc();
1363 let mut view_a = View::new_view(std::sync::Arc::clone(&content));
1364 let view_b = View::new_view(std::sync::Arc::clone(&content));
1365
1366 view_a.set_syntax_fold_ranges(vec![(0, 2)]);
1367
1368 assert_eq!(view_b.syntax_fold_ranges_cloned(), vec![(0, 2)]);
1369 }
1370}