1use std::sync::{Arc, Mutex, MutexGuard};
2
3use crate::content::Buffer;
4use crate::{Position, Viewport};
5
6pub struct View {
44 pub(crate) content: Arc<Mutex<Buffer>>,
46 cursor: Position,
49}
50
51impl Default for View {
52 fn default() -> Self {
53 Self::new()
54 }
55}
56
57impl View {
58 pub fn new() -> Self {
62 Self {
63 content: Arc::new(Mutex::new(Buffer::new())),
64 cursor: Position::default(),
65 }
66 }
67
68 #[allow(clippy::should_implement_trait)]
73 pub fn from_str(text: &str) -> Self {
74 Self {
75 content: Arc::new(Mutex::new(Buffer::from_str(text))),
76 cursor: Position::default(),
77 }
78 }
79
80 pub fn new_view(content: Arc<Mutex<Buffer>>) -> Self {
100 Self {
101 content,
102 cursor: Position::default(),
103 }
104 }
105
106 pub fn content_arc(&self) -> Arc<Mutex<Buffer>> {
109 Arc::clone(&self.content)
110 }
111
112 pub fn cursor(&self) -> Position {
115 self.cursor
116 }
117
118 pub fn last_cursor(&self) -> (usize, usize) {
123 self.content.lock().unwrap().last_cursor
124 }
125
126 pub fn dirty_gen(&self) -> u64 {
127 self.content.lock().unwrap().dirty_gen
128 }
129
130 pub fn row_count(&self) -> usize {
132 self.content.lock().unwrap().text.len_lines()
133 }
134
135 pub fn as_string(&self) -> String {
140 self.content.lock().unwrap().text.to_string()
141 }
142
143 pub fn set_cursor(&mut self, pos: Position) {
150 let mut c = self.content.lock().unwrap();
151 let n = c.text.len_lines();
152 let last_row = n.saturating_sub(1);
153 let row = pos.row.min(last_row);
154 let line_chars = rope_line_char_count(&c.text, row);
155 let col = pos.col.min(line_chars);
156 c.last_cursor = (row, col);
160 drop(c);
161 self.cursor = Position::new(row, col);
162 }
163
164 pub fn ensure_cursor_visible(&mut self, viewport: &mut Viewport) {
167 let cursor = self.cursor;
168 let v = *viewport;
169 let wrap_active = !matches!(v.wrap, crate::Wrap::None) && v.text_width > 0;
170 if !wrap_active {
171 viewport.ensure_visible(cursor);
172 return;
173 }
174 if v.height == 0 {
175 return;
176 }
177 if cursor.row < v.top_row {
179 viewport.top_row = cursor.row;
180 viewport.top_col = 0;
181 return;
182 }
183 let height = v.height as usize;
184 let Some(mut screen) = self.cursor_screen_row_from(viewport, viewport.top_row) else {
190 let last_row = {
201 let c = self.content.lock().unwrap();
202 c.text.len_lines().saturating_sub(1)
203 };
204 viewport.top_row = cursor.row.min(last_row);
205 viewport.top_col = 0;
206 return;
207 };
208 while screen >= height {
209 let c = self.content.lock().unwrap();
210 let mut next = viewport.top_row + 1;
211 while next <= cursor.row && c.folds.iter().any(|f| f.hides(next)) {
212 next += 1;
213 }
214 if next > cursor.row {
215 drop(c);
216 viewport.top_row = cursor.row;
217 break;
218 }
219 for r in viewport.top_row..next {
223 if c.folds.iter().any(|f| f.hides(r)) {
224 continue;
225 }
226 let line = rope_line_str(&c.text, r);
227 screen -= crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len();
228 }
229 drop(c);
230 viewport.top_row = next;
231 }
232 viewport.top_col = 0;
233 }
234
235 pub fn cursor_screen_row(&self, viewport: &Viewport) -> Option<usize> {
237 if matches!(viewport.wrap, crate::Wrap::None) || viewport.text_width == 0 {
238 return None;
239 }
240 self.cursor_screen_row_from(viewport, viewport.top_row)
241 }
242
243 pub fn screen_rows_between(&self, viewport: &Viewport, start: usize, end: usize) -> usize {
245 if start > end {
246 return 0;
247 }
248 let c = self.content.lock().unwrap();
249 let n = c.text.len_lines();
250 let last = n.saturating_sub(1);
251 let end = end.min(last);
252 let v = *viewport;
253 let mut total = 0usize;
254 for r in start..=end {
255 if c.folds.iter().any(|f| f.hides(r)) {
256 continue;
257 }
258 if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
259 total += 1;
260 } else {
261 let line = rope_line_str(&c.text, r);
262 total += crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len();
263 }
264 }
265 total
266 }
267
268 pub fn max_top_for_height(&self, viewport: &Viewport, height: usize) -> usize {
271 if height == 0 {
272 return 0;
273 }
274 let c = self.content.lock().unwrap();
275 let n = c.text.len_lines();
276 let last = n.saturating_sub(1);
277 let mut total = 0usize;
278 let mut row = last;
279 loop {
280 if !c.folds.iter().any(|f| f.hides(row)) {
281 let v = *viewport;
282 total += if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
283 1
284 } else {
285 let line = rope_line_str(&c.text, row);
286 crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len()
287 };
288 }
289 if total >= height {
290 return row;
291 }
292 if row == 0 {
293 return 0;
294 }
295 row -= 1;
296 }
297 }
298
299 pub fn clamp_position(&self, pos: Position) -> Position {
301 let c = self.content.lock().unwrap();
302 let n = c.text.len_lines();
303 let last_row = n.saturating_sub(1);
304 let row = pos.row.min(last_row);
305 let line_chars = rope_line_char_count(&c.text, row);
306 let col = pos.col.min(line_chars);
307 Position::new(row, col)
308 }
309
310 pub fn replace_all(&mut self, text: &str) {
313 let new_cursor = {
314 let mut c = self.content.lock().unwrap();
315 c.text = ropey::Rope::from_str(text);
316 let n = c.text.len_lines();
317 let last_row = n.saturating_sub(1);
318 let row = self.cursor.row.min(last_row);
319 let line_chars = rope_line_char_count(&c.text, row);
320 let col = self.cursor.col.min(line_chars);
321 c.dirty_gen = c.dirty_gen.wrapping_add(1);
322 c.cached_joined = None;
323 c.cached_byte_len = None;
324 Position::new(row, col)
325 };
326 self.cursor = new_cursor;
327 }
328
329 pub(crate) fn fold_gen_bump(&mut self) {
337 let mut c = self.content.lock().unwrap();
338 c.fold_gen = c.fold_gen.wrapping_add(1);
339 }
340
341 pub(crate) fn dirty_gen_bump(&mut self) {
343 let mut c = self.content.lock().unwrap();
344 c.dirty_gen = c.dirty_gen.wrapping_add(1);
345 c.cached_joined = None;
346 c.cached_byte_len = None;
347 }
348
349 pub fn byte_len(&self) -> usize {
355 let mut c = self.content.lock().unwrap();
356 let dg = c.dirty_gen;
357 if let Some((cached_dg, len)) = c.cached_byte_len
358 && cached_dg == dg
359 {
360 return len;
361 }
362 let total = c.text.len_bytes();
363 c.cached_byte_len = Some((dg, total));
364 total
365 }
366
367 pub fn content_joined(&self) -> std::sync::Arc<String> {
376 let mut c = self.content.lock().unwrap();
377 let dg = c.dirty_gen;
378 if let Some((cached_dg, ref s)) = c.cached_joined
379 && cached_dg == dg
380 {
381 return std::sync::Arc::clone(s);
382 }
383 let joined = std::sync::Arc::new(c.text.to_string());
384 c.cached_joined = Some((dg, std::sync::Arc::clone(&joined)));
385 joined
386 }
387
388 pub fn rope(&self) -> ropey::Rope {
397 self.content.lock().unwrap().text.clone()
398 }
399
400 pub(crate) fn content_lock(&self) -> MutexGuard<'_, Buffer> {
402 self.content.lock().unwrap()
403 }
404
405 pub(crate) fn content_lock_mut(&mut self) -> MutexGuard<'_, Buffer> {
407 self.content.lock().unwrap()
408 }
409
410 fn cursor_screen_row_from(&self, viewport: &Viewport, top: usize) -> Option<usize> {
413 let cursor = self.cursor;
414 if cursor.row < top {
415 return None;
416 }
417 let c = self.content.lock().unwrap();
418 let cursor_row = cursor.row.min(c.text.len_lines().saturating_sub(1));
422 if cursor_row < top {
423 return None;
424 }
425 let v = *viewport;
426 let mut screen = 0usize;
427 for r in top..=cursor_row {
428 if c.folds.iter().any(|f| f.hides(r)) {
429 continue;
430 }
431 let line = rope_line_str(&c.text, r);
432 let segs = crate::wrap::wrap_segments(&line, v.text_width, v.wrap);
433 if r == cursor_row {
434 let seg_idx = crate::wrap::segment_for_col(&segs, cursor.col);
435 return Some(screen + seg_idx);
436 }
437 screen += segs.len();
438 }
439 None
440 }
441
442 pub fn undo_stack_is_empty(&self) -> bool {
452 self.content.lock().unwrap().undo.is_at_root()
453 }
454
455 pub fn redo_stack_is_empty(&self) -> bool {
456 !self.content.lock().unwrap().undo.has_redo()
457 }
458
459 pub fn undo_stack_len(&self) -> usize {
460 self.content.lock().unwrap().undo.depth()
461 }
462
463 pub fn push_undo_entry(&self, entry: crate::UndoEntry) {
467 self.content.lock().unwrap().undo.push(entry);
468 }
469
470 pub fn undo_step(
474 &self,
475 rope: ropey::Rope,
476 cursor: (usize, usize),
477 marks: crate::MarkSnapshot,
478 ) -> Option<crate::UndoEntry> {
479 self.content
480 .lock()
481 .unwrap()
482 .undo
483 .undo_step(rope, cursor, marks)
484 }
485
486 pub fn redo_step(
489 &self,
490 rope: ropey::Rope,
491 cursor: (usize, usize),
492 marks: crate::MarkSnapshot,
493 ) -> Option<crate::UndoEntry> {
494 self.content
495 .lock()
496 .unwrap()
497 .undo
498 .redo_step(rope, cursor, marks)
499 }
500
501 pub fn pop_committed(&self) -> bool {
505 self.content.lock().unwrap().undo.pop_committed()
506 }
507
508 pub fn seq_earlier_step(
513 &self,
514 rope: ropey::Rope,
515 cursor: (usize, usize),
516 marks: crate::MarkSnapshot,
517 ) -> Option<crate::UndoEntry> {
518 self.content
519 .lock()
520 .unwrap()
521 .undo
522 .seq_earlier_step(rope, cursor, marks)
523 }
524
525 pub fn seq_later_step(
528 &self,
529 rope: ropey::Rope,
530 cursor: (usize, usize),
531 marks: crate::MarkSnapshot,
532 ) -> Option<crate::UndoEntry> {
533 self.content
534 .lock()
535 .unwrap()
536 .undo
537 .seq_later_step(rope, cursor, marks)
538 }
539
540 pub fn seq_earlier_timestamp(&self) -> Option<std::time::SystemTime> {
542 self.content.lock().unwrap().undo.seq_earlier_timestamp()
543 }
544
545 pub fn seq_later_timestamp(&self) -> Option<std::time::SystemTime> {
547 self.content.lock().unwrap().undo.seq_later_timestamp()
548 }
549
550 pub fn undo_leaves(&self) -> Vec<(u64, usize, std::time::SystemTime, bool)> {
553 self.content.lock().unwrap().undo.leaves()
554 }
555
556 pub fn peek_undo_timestamp(&self) -> Option<std::time::SystemTime> {
557 self.content.lock().unwrap().undo.parent_timestamp()
558 }
559
560 pub fn peek_redo_timestamp(&self) -> Option<std::time::SystemTime> {
561 self.content.lock().unwrap().undo.child_timestamp()
562 }
563
564 pub fn clear_undo_redo(&self) {
565 self.content.lock().unwrap().undo.clear_all();
566 }
567
568 pub fn undo_to_serializable(&self) -> (crate::SerTree, u64) {
575 let mut c = self.content.lock().unwrap();
576 let rope = c.text.clone();
577 c.undo.sync_current(rope);
578 let seq = c.undo.current_node_seq();
579 (c.undo.to_serializable(), seq)
580 }
581
582 pub fn install_undo_tree(&self, ser: &crate::SerTree) -> bool {
588 match crate::UndoTree::from_serializable(ser) {
589 Some(tree) => {
590 self.content.lock().unwrap().undo = tree;
591 true
592 }
593 None => false,
594 }
595 }
596
597 pub fn install_recovered_undo_tree(&self, ser: &crate::SerTree, current_seq: u64) -> bool {
611 let Some(mut tree) = crate::UndoTree::from_serializable(ser) else {
612 return false;
613 };
614 if tree.current_node_seq() != current_seq {
615 return false;
616 }
617 let mut c = self.content.lock().unwrap();
618 let materialized = tree.current_content();
619 if materialized.to_string().trim_end_matches('\n')
620 != c.text.to_string().trim_end_matches('\n')
621 {
622 return false;
623 }
624 c.undo = tree;
625 true
626 }
627
628 pub fn clear_redo(&self) {
629 self.content.lock().unwrap().undo.clear_redo();
630 }
631
632 pub fn undo_group_active(&self) -> bool {
635 self.content.lock().unwrap().undo_group_active()
636 }
637
638 pub fn undo_group_arm(&self) -> bool {
640 self.content.lock().unwrap().undo_group_arm()
641 }
642
643 pub fn cap_undo(&self, cap: usize) {
644 self.content.lock().unwrap().undo.cap(cap);
645 }
646
647 pub fn content_dirty(&self) -> bool {
648 self.content.lock().unwrap().content_dirty
649 }
650
651 pub fn set_content_dirty(&self, v: bool) {
652 self.content.lock().unwrap().content_dirty = v;
653 }
654
655 pub fn mark_content_dirty(&self) {
656 let mut c = self.content.lock().unwrap();
657 c.content_dirty = true;
658 c.cached_editor_content = None;
659 }
660
661 pub fn take_dirty(&self) -> bool {
662 let mut c = self.content.lock().unwrap();
663 let v = c.content_dirty;
664 c.content_dirty = false;
665 v
666 }
667
668 pub fn cached_editor_content(&self) -> Option<std::sync::Arc<String>> {
669 self.content.lock().unwrap().cached_editor_content.clone()
670 }
671
672 pub fn set_cached_editor_content(&self, arc: std::sync::Arc<String>) {
673 self.content.lock().unwrap().cached_editor_content = Some(arc);
674 }
675
676 pub fn push_fold_op(&self, op: crate::FoldOp) {
677 self.content.lock().unwrap().pending_fold_ops.push(op);
678 }
679
680 pub fn take_fold_ops(&self) -> Vec<crate::FoldOp> {
681 std::mem::take(&mut self.content.lock().unwrap().pending_fold_ops)
682 }
683
684 pub fn extend_change_log(&self, edits: impl IntoIterator<Item = crate::EngineEdit>) {
685 self.content.lock().unwrap().change_log.extend(edits);
686 }
687
688 pub fn take_change_log(&self) -> Vec<crate::EngineEdit> {
689 std::mem::take(&mut self.content.lock().unwrap().change_log)
690 }
691
692 pub fn extend_pending_content_edits(
693 &self,
694 edits: impl IntoIterator<Item = crate::ContentEdit>,
695 ) {
696 self.content
697 .lock()
698 .unwrap()
699 .pending_content_edits
700 .extend(edits);
701 }
702
703 pub fn push_pending_content_edit(&self, edit: crate::ContentEdit) {
704 self.content
705 .lock()
706 .unwrap()
707 .pending_content_edits
708 .push(edit);
709 }
710
711 pub fn take_pending_content_edits(&self) -> Vec<crate::ContentEdit> {
712 std::mem::take(&mut self.content.lock().unwrap().pending_content_edits)
713 }
714
715 pub fn clear_pending_content_edits(&self) {
716 self.content.lock().unwrap().pending_content_edits.clear();
717 }
718
719 pub fn pending_content_reset(&self) -> bool {
720 self.content.lock().unwrap().pending_content_reset
721 }
722
723 pub fn set_pending_content_reset(&self, v: bool) {
724 self.content.lock().unwrap().pending_content_reset = v;
725 }
726
727 pub fn take_pending_content_reset(&self) -> bool {
728 let mut c = self.content.lock().unwrap();
729 let v = c.pending_content_reset;
730 c.pending_content_reset = false;
731 v
732 }
733
734 pub fn mark(&self, c: char) -> Option<(usize, usize)> {
735 self.content_lock().marks.get(&c).copied()
736 }
737 pub fn set_mark(&mut self, c: char, pos: (usize, usize)) {
738 self.content_lock_mut().marks.insert(c, pos);
739 }
740 pub fn clear_mark(&mut self, c: char) {
741 self.content_lock_mut().marks.remove(&c);
742 }
743 pub fn marks_cloned(&self) -> std::collections::BTreeMap<char, (usize, usize)> {
744 self.content_lock().marks.clone()
745 }
746 pub fn set_marks(&mut self, marks: std::collections::BTreeMap<char, (usize, usize)>) {
747 self.content_lock_mut().marks = marks;
748 }
749 pub fn rebase_marks(
753 &mut self,
754 edit_start: usize,
755 drop_end: usize,
756 shift_threshold: usize,
757 delta: isize,
758 ) {
759 let mut c = self.content_lock_mut();
760 let mut to_drop: Vec<char> = Vec::new();
761 for (ch, (row, _col)) in c.marks.iter_mut() {
762 if (edit_start..drop_end).contains(row) {
763 to_drop.push(*ch);
764 } else if *row >= shift_threshold {
765 *row = ((*row as isize) + delta).max(0) as usize;
766 }
767 }
768 for ch in to_drop {
769 c.marks.remove(&ch);
770 }
771 }
772 pub fn syntax_fold_ranges_cloned(&self) -> Vec<(usize, usize)> {
773 self.content_lock().syntax_fold_ranges.clone()
774 }
775 pub fn set_syntax_fold_ranges(&mut self, ranges: Vec<(usize, usize)>) {
776 self.content_lock_mut().syntax_fold_ranges = ranges;
777 }
778}
779
780pub fn floor_char_boundary(rope: &ropey::Rope, byte_idx: usize) -> usize {
793 let byte_idx = byte_idx.min(rope.len_bytes());
794 rope.char_to_byte(rope.byte_to_char(byte_idx))
795}
796
797pub fn rope_line_str(rope: &ropey::Rope, row: usize) -> String {
800 let mut s = rope.line(row).to_string();
801 if s.ends_with('\n') {
803 s.pop();
804 }
805 s
806}
807
808pub fn rope_line_bytes(rope: &ropey::Rope, row: usize) -> usize {
810 let slice = rope.line(row);
811 let bytes = slice.len_bytes();
812 if row + 1 < rope.len_lines() && bytes > 0 {
814 bytes - 1
815 } else {
816 bytes
817 }
818}
819
820pub fn rope_line_char_count(rope: &ropey::Rope, row: usize) -> usize {
822 let slice = rope.line(row);
823 let chars = slice.len_chars();
824 if row + 1 < rope.len_lines() && chars > 0 {
826 chars - 1
827 } else {
828 chars
829 }
830}
831
832pub fn pos_to_char_idx(rope: &ropey::Rope, row: usize, col: usize) -> usize {
837 let row = row.min(rope.len_lines().saturating_sub(1));
838 let line_start = rope.line_to_char(row);
839 let line_char_count = rope_line_char_count(rope, row);
840 line_start + col.min(line_char_count)
841}
842
843#[cfg(test)]
844mod tests {
845 use super::*;
846
847 #[test]
848 fn new_has_one_empty_row() {
849 let b = View::new();
850 assert_eq!(b.row_count(), 1);
851 assert_eq!(rope_line_str(&b.rope(), 0), "");
852 assert_eq!(b.cursor(), Position::default());
853 }
854
855 #[test]
856 fn from_str_splits_on_newline() {
857 let b = View::from_str("foo\nbar\nbaz");
858 assert_eq!(b.row_count(), 3);
859 assert_eq!(rope_line_str(&b.rope(), 0), "foo");
860 assert_eq!(rope_line_str(&b.rope(), 2), "baz");
861 }
862
863 #[test]
864 fn from_str_trailing_newline_keeps_empty_row() {
865 let b = View::from_str("foo\n");
866 assert_eq!(b.row_count(), 2);
867 assert_eq!(rope_line_str(&b.rope(), 1), "");
868 }
869
870 #[test]
871 fn from_str_empty_input_keeps_one_row() {
872 let b = View::from_str("");
873 assert_eq!(b.row_count(), 1);
874 assert_eq!(rope_line_str(&b.rope(), 0), "");
875 }
876
877 #[test]
878 fn as_string_round_trips() {
879 let b = View::from_str("a\nb\nc");
880 assert_eq!(b.as_string(), "a\nb\nc");
881 }
882
883 #[test]
884 fn dirty_gen_starts_at_zero() {
885 assert_eq!(View::new().dirty_gen(), 0);
886 }
887
888 fn single_node_tree(base: &str, seq: u64) -> crate::SerTree {
891 crate::SerTree {
892 base: base.to_string(),
893 nodes: vec![crate::SerNode {
894 parent: None,
895 children: Vec::new(),
896 last_child: None,
897 delta: None,
898 cursor: (0, 0),
899 timestamp_unix_ms: 0,
900 marks: crate::MarkSnapshot::default(),
901 seq,
902 }],
903 root: 0,
904 current: 0,
905 next_seq: seq + 1,
906 }
907 }
908
909 #[test]
913 fn install_recovered_undo_tree_guards_seq_and_content() {
914 let v = View::from_str("alpha\nhello");
916 assert!(v.install_recovered_undo_tree(&single_node_tree("alpha\nhello", 3), 3));
917
918 let v = View::from_str("alpha\nhello");
920 assert!(!v.install_recovered_undo_tree(&single_node_tree("alpha\nhello", 3), 4));
921
922 let v = View::from_str("alpha\nhello");
924 assert!(!v.install_recovered_undo_tree(&single_node_tree("something else", 3), 3));
925
926 let v = View::from_str("alpha\nhello");
928 assert!(v.install_recovered_undo_tree(&single_node_tree("alpha\nhello\n", 3), 3));
929 }
930
931 fn vp_wrap(width: u16, height: u16) -> Viewport {
932 Viewport {
933 top_row: 0,
934 top_col: 0,
935 width,
936 height,
937 wrap: crate::Wrap::Char,
938 text_width: width,
939 tab_width: 0,
940 }
941 }
942
943 #[test]
944 fn ensure_cursor_visible_wrap_scrolls_when_cursor_below_screen() {
945 let mut b = View::from_str("aaaaaaaaaa\nb\nc");
946 let mut v = vp_wrap(4, 3);
947 b.set_cursor(Position::new(2, 0));
948 b.ensure_cursor_visible(&mut v);
949 assert_eq!(v.top_row, 1);
950 }
951
952 #[test]
953 fn ensure_cursor_visible_wrap_no_scroll_when_visible() {
954 let mut b = View::from_str("aaaaaaaaaa\nb");
955 let mut v = vp_wrap(4, 4);
956 b.set_cursor(Position::new(0, 5));
957 b.ensure_cursor_visible(&mut v);
958 assert_eq!(v.top_row, 0);
959 }
960
961 #[test]
962 fn ensure_cursor_visible_wrap_snaps_top_when_cursor_above() {
963 let mut b = View::from_str("a\nb\nc\nd\ne");
964 let mut v = vp_wrap(4, 2);
965 v.top_row = 3;
966 b.set_cursor(Position::new(1, 0));
967 b.ensure_cursor_visible(&mut v);
968 assert_eq!(v.top_row, 1);
969 }
970
971 #[test]
972 fn screen_rows_between_sums_segments_under_wrap() {
973 let b = View::from_str("aaaaaaaaa\nb\n");
974 let v = vp_wrap(4, 0);
975 assert_eq!(b.screen_rows_between(&v, 0, 0), 3);
976 assert_eq!(b.screen_rows_between(&v, 0, 1), 4);
977 assert_eq!(b.screen_rows_between(&v, 0, 2), 5);
978 assert_eq!(b.screen_rows_between(&v, 1, 2), 2);
979 }
980
981 #[test]
982 fn screen_rows_between_one_per_doc_row_when_wrap_off() {
983 let b = View::from_str("aaaaa\nb\nc");
984 let v = Viewport::default();
985 assert_eq!(b.screen_rows_between(&v, 0, 2), 3);
986 }
987
988 #[test]
989 fn max_top_for_height_walks_back_until_height_reached() {
990 let b = View::from_str("a\nb\nc\nd\neeeeeeee");
991 let v = vp_wrap(4, 0);
992 assert_eq!(b.max_top_for_height(&v, 4), 2);
993 assert_eq!(b.max_top_for_height(&v, 99), 0);
994 }
995
996 #[test]
997 fn cursor_screen_row_returns_none_when_wrap_off() {
998 let b = View::from_str("a");
999 let v = Viewport::default();
1000 assert!(b.cursor_screen_row(&v).is_none());
1001 }
1002
1003 #[test]
1004 fn cursor_screen_row_under_wrap() {
1005 let mut b = View::from_str("aaaaaaaaaa\nb");
1006 let v = vp_wrap(4, 0);
1007 b.set_cursor(Position::new(0, 5));
1008 assert_eq!(b.cursor_screen_row(&v), Some(1));
1009 b.set_cursor(Position::new(1, 0));
1010 assert_eq!(b.cursor_screen_row(&v), Some(3));
1011 }
1012
1013 #[test]
1017 fn cursor_screen_row_survives_shrink_from_other_view() {
1018 let a = View::from_str("a\nb\nc\nd\ne");
1019 let arc = a.content_arc();
1020 let mut view_a = View::new_view(Arc::clone(&arc));
1021 let mut view_b = View::new_view(Arc::clone(&arc));
1022 view_b.set_cursor(Position::new(4, 0));
1023 view_a.replace_all("a");
1025 let v = vp_wrap(4, 3);
1026 assert_eq!(view_b.cursor_screen_row(&v), Some(0));
1027 let mut v2 = vp_wrap(4, 3);
1028 view_b.ensure_cursor_visible(&mut v2); }
1030
1031 #[test]
1036 fn ensure_cursor_visible_clamps_stale_top_row_after_shrink() {
1037 let a = View::from_str("a\nb\nc\nd\ne");
1038 let arc = a.content_arc();
1039 let mut view_a = View::new_view(Arc::clone(&arc));
1040 let mut view_b = View::new_view(Arc::clone(&arc));
1041 view_b.set_cursor(Position::new(4, 0));
1042 view_a.replace_all("a");
1043 let mut v = vp_wrap(4, 3);
1044 v.top_row = 3;
1047 view_b.ensure_cursor_visible(&mut v);
1048 assert_eq!(v.top_row, 0, "top_row must clamp into the live rope");
1049 assert_eq!(v.top_col, 0);
1050 }
1051
1052 #[test]
1056 fn ensure_cursor_visible_snaps_when_cursor_row_folded() {
1057 let mut b = View::from_str("a\nb\nc\nd\ne");
1058 b.set_folds(&[crate::Fold {
1059 start_row: 2,
1060 end_row: 4,
1061 closed: true,
1062 auto_generated: false,
1063 }]);
1064 let mut v = vp_wrap(4, 2);
1065 v.top_row = 1;
1066 b.set_cursor(Position::new(3, 0));
1067 b.ensure_cursor_visible(&mut v);
1068 assert_eq!(v.top_row, 3);
1069 }
1070
1071 #[test]
1072 fn ensure_cursor_visible_falls_back_when_wrap_disabled() {
1073 let mut b = View::from_str("a\nb\nc\nd\ne");
1074 let mut v = Viewport {
1075 top_row: 0,
1076 top_col: 0,
1077 width: 4,
1078 height: 2,
1079 wrap: crate::Wrap::None,
1080 text_width: 4,
1081 tab_width: 0,
1082 };
1083 b.set_cursor(Position::new(4, 0));
1084 b.ensure_cursor_visible(&mut v);
1085 assert_eq!(v.top_row, 3);
1086 }
1087
1088 #[test]
1094 fn undo_stack_shared_across_views() {
1095 use crate::UndoEntry;
1096 use std::time::SystemTime;
1097
1098 let a = View::from_str("hello");
1099 let arc = a.content_arc();
1100 let view_a = View::new_view(Arc::clone(&arc));
1101 let view_b = View::new_view(Arc::clone(&arc));
1102
1103 assert!(view_a.undo_stack_is_empty());
1104 assert_eq!(view_a.undo_stack_len(), 0);
1105
1106 view_a.push_undo_entry(UndoEntry {
1107 rope: view_a.rope(),
1108 cursor: (0, 0),
1109 timestamp: SystemTime::UNIX_EPOCH,
1110 marks: Default::default(),
1111 });
1112
1113 assert_eq!(view_b.undo_stack_len(), 1);
1115 assert!(!view_b.undo_stack_is_empty());
1116 }
1117
1118 #[test]
1123 fn redo_stack_shared_across_views() {
1124 use crate::UndoEntry;
1125 use std::time::SystemTime;
1126
1127 let a = View::from_str("world");
1128 let arc = a.content_arc();
1129 let view_a = View::new_view(Arc::clone(&arc));
1130 let view_b = View::new_view(Arc::clone(&arc));
1131
1132 assert!(view_a.redo_stack_is_empty());
1133
1134 view_b.push_undo_entry(UndoEntry {
1138 rope: view_b.rope(),
1139 cursor: (0, 0),
1140 timestamp: SystemTime::UNIX_EPOCH,
1141 marks: Default::default(),
1142 });
1143 view_b.undo_step(view_b.rope(), (0, 2), Default::default());
1144
1145 assert!(!view_a.redo_stack_is_empty());
1147 let entry = view_a.redo_step(view_a.rope(), (0, 0), Default::default());
1148 assert!(entry.is_some());
1149 assert_eq!(entry.unwrap().cursor, (0, 2));
1150 }
1151
1152 #[test]
1157 fn clear_undo_redo_shared_across_views() {
1158 use crate::UndoEntry;
1159 use std::time::SystemTime;
1160
1161 let a = View::from_str("abc");
1162 let arc = a.content_arc();
1163 let view_a = View::new_view(Arc::clone(&arc));
1164 let view_b = View::new_view(Arc::clone(&arc));
1165
1166 for _ in 0..2 {
1168 view_a.push_undo_entry(UndoEntry {
1169 rope: view_a.rope(),
1170 cursor: (0, 0),
1171 timestamp: SystemTime::UNIX_EPOCH,
1172 marks: Default::default(),
1173 });
1174 }
1175 view_a.undo_step(view_a.rope(), (0, 1), Default::default());
1176 assert!(!view_a.undo_stack_is_empty());
1177 assert!(!view_a.redo_stack_is_empty());
1178
1179 view_b.clear_undo_redo();
1180 assert!(view_a.undo_stack_is_empty());
1181 assert!(view_a.redo_stack_is_empty());
1182 }
1183
1184 #[test]
1186 fn content_dirty_shared_across_views() {
1187 let a = View::from_str("test");
1188 let arc = a.content_arc();
1189 let view_a = View::new_view(Arc::clone(&arc));
1190 let view_b = View::new_view(Arc::clone(&arc));
1191
1192 assert!(!view_a.content_dirty());
1193
1194 view_b.mark_content_dirty();
1195 assert!(view_a.content_dirty());
1196
1197 let taken = view_a.take_dirty();
1198 assert!(taken);
1199 assert!(!view_b.content_dirty());
1200 }
1201
1202 #[test]
1204 fn pending_fold_ops_shared_across_views() {
1205 let a = View::from_str("a\nb\nc");
1206 let arc = a.content_arc();
1207 let view_a = View::new_view(Arc::clone(&arc));
1208 let view_b = View::new_view(Arc::clone(&arc));
1209
1210 view_a.push_fold_op(crate::FoldOp::Add {
1211 start_row: 0,
1212 end_row: 1,
1213 closed: true,
1214 });
1215
1216 let ops = view_b.take_fold_ops();
1217 assert_eq!(ops.len(), 1);
1218 assert!(matches!(
1219 ops[0],
1220 crate::FoldOp::Add {
1221 start_row: 0,
1222 end_row: 1,
1223 closed: true
1224 }
1225 ));
1226 }
1227
1228 #[test]
1230 fn pending_content_reset_shared_across_views() {
1231 let a = View::from_str("x");
1232 let arc = a.content_arc();
1233 let view_a = View::new_view(Arc::clone(&arc));
1234 let view_b = View::new_view(Arc::clone(&arc));
1235
1236 assert!(!view_a.pending_content_reset());
1237 view_b.set_pending_content_reset(true);
1238 assert!(view_a.pending_content_reset());
1239 let taken = view_a.take_pending_content_reset();
1240 assert!(taken);
1241 assert!(!view_b.pending_content_reset());
1242 }
1243
1244 #[test]
1249 fn buffer_views_independent_cursors() {
1250 let a = View::from_str("hello\nworld");
1251 let arc = a.content_arc();
1252 let mut view_a = View::new_view(Arc::clone(&arc));
1253 let mut view_b = View::new_view(Arc::clone(&arc));
1254
1255 view_a.set_cursor(Position::new(1, 3));
1256 assert_eq!(view_b.cursor(), Position::new(0, 0));
1258
1259 view_b.set_cursor(Position::new(0, 2));
1260 assert_eq!(view_a.cursor(), Position::new(1, 3));
1262 }
1263
1264 #[test]
1268 fn last_cursor_reflects_most_recent_move_across_views() {
1269 let a = View::from_str("aaaa\nbbbb\ncccc\ndddd");
1270 let arc = a.content_arc();
1271 let mut view_a = View::new_view(Arc::clone(&arc));
1272 let mut view_b = View::new_view(Arc::clone(&arc));
1273
1274 view_a.set_cursor(Position::new(1, 2));
1275 assert_eq!(view_a.last_cursor(), (1, 2));
1276 assert_eq!(view_b.last_cursor(), (1, 2));
1278
1279 view_b.set_cursor(Position::new(3, 1));
1281 assert_eq!(view_a.last_cursor(), (3, 1));
1282 assert_eq!(view_b.last_cursor(), (3, 1));
1283
1284 view_a.set_cursor(Position::new(99, 99));
1286 assert_eq!(view_a.last_cursor(), (3, 4));
1287 }
1288
1289 #[test]
1293 fn clamp_position_restore_semantics() {
1294 let b = View::from_str("ab\ncdef\ng");
1295 assert_eq!(b.clamp_position(Position::new(99, 99)), Position::new(2, 1));
1297 assert_eq!(b.clamp_position(Position::new(1, 99)), Position::new(1, 4));
1299 assert_eq!(b.clamp_position(Position::new(1, 2)), Position::new(1, 2));
1301 }
1302
1303 #[test]
1305 fn buffer_views_share_content() {
1306 use crate::edit::Edit;
1307
1308 let a = View::from_str("foo");
1309 let arc = a.content_arc();
1310 let mut view_a = View::new_view(Arc::clone(&arc));
1311 let view_b = View::new_view(Arc::clone(&arc));
1312
1313 view_a.apply_edit(Edit::InsertStr {
1314 at: Position::new(0, 3),
1315 text: "bar".into(),
1316 });
1317
1318 assert_eq!(rope_line_str(&view_a.rope(), 0), "foobar");
1319 assert_eq!(rope_line_str(&view_b.rope(), 0), "foobar");
1320 }
1321}
1322
1323#[cfg(test)]
1324mod marks_shared_content_tests {
1325 use super::*;
1326
1327 #[test]
1328 fn marks_shared_across_views() {
1329 let a = View::from_str("hello\nworld");
1331 let content = a.content_arc();
1332 let mut view_a = View::new_view(std::sync::Arc::clone(&content));
1333 let view_b = View::new_view(std::sync::Arc::clone(&content));
1334
1335 view_a.set_mark('x', (1, 3));
1337
1338 assert_eq!(view_b.mark('x'), Some((1, 3)));
1340 }
1341
1342 #[test]
1343 fn syntax_fold_ranges_shared_across_views() {
1344 let a = View::from_str("fn foo() {\n bar();\n}");
1345 let content = a.content_arc();
1346 let mut view_a = View::new_view(std::sync::Arc::clone(&content));
1347 let view_b = View::new_view(std::sync::Arc::clone(&content));
1348
1349 view_a.set_syntax_fold_ranges(vec![(0, 2)]);
1350
1351 assert_eq!(view_b.syntax_fold_ranges_cloned(), vec![(0, 2)]);
1352 }
1353}