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 mut screen = match self.cursor_screen_row_from(viewport, viewport.top_row) {
190 Some(s) => s,
191 None => {
192 viewport.top_col = 0;
193 return;
194 }
195 };
196 while screen >= height {
197 let c = self.content.lock().unwrap();
198 let mut next = viewport.top_row + 1;
199 while next <= cursor.row && c.folds.iter().any(|f| f.hides(next)) {
200 next += 1;
201 }
202 if next > cursor.row {
203 drop(c);
204 viewport.top_row = cursor.row;
205 break;
206 }
207 for r in viewport.top_row..next {
211 if c.folds.iter().any(|f| f.hides(r)) {
212 continue;
213 }
214 let line = rope_line_str(&c.text, r);
215 screen -= crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len();
216 }
217 drop(c);
218 viewport.top_row = next;
219 }
220 viewport.top_col = 0;
221 }
222
223 pub fn cursor_screen_row(&self, viewport: &Viewport) -> Option<usize> {
225 if matches!(viewport.wrap, crate::Wrap::None) || viewport.text_width == 0 {
226 return None;
227 }
228 self.cursor_screen_row_from(viewport, viewport.top_row)
229 }
230
231 pub fn screen_rows_between(&self, viewport: &Viewport, start: usize, end: usize) -> usize {
233 if start > end {
234 return 0;
235 }
236 let c = self.content.lock().unwrap();
237 let n = c.text.len_lines();
238 let last = n.saturating_sub(1);
239 let end = end.min(last);
240 let v = *viewport;
241 let mut total = 0usize;
242 for r in start..=end {
243 if c.folds.iter().any(|f| f.hides(r)) {
244 continue;
245 }
246 if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
247 total += 1;
248 } else {
249 let line = rope_line_str(&c.text, r);
250 total += crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len();
251 }
252 }
253 total
254 }
255
256 pub fn max_top_for_height(&self, viewport: &Viewport, height: usize) -> usize {
259 if height == 0 {
260 return 0;
261 }
262 let c = self.content.lock().unwrap();
263 let n = c.text.len_lines();
264 let last = n.saturating_sub(1);
265 let mut total = 0usize;
266 let mut row = last;
267 loop {
268 if !c.folds.iter().any(|f| f.hides(row)) {
269 let v = *viewport;
270 total += if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
271 1
272 } else {
273 let line = rope_line_str(&c.text, row);
274 crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len()
275 };
276 }
277 if total >= height {
278 return row;
279 }
280 if row == 0 {
281 return 0;
282 }
283 row -= 1;
284 }
285 }
286
287 pub fn clamp_position(&self, pos: Position) -> Position {
289 let c = self.content.lock().unwrap();
290 let n = c.text.len_lines();
291 let last_row = n.saturating_sub(1);
292 let row = pos.row.min(last_row);
293 let line_chars = rope_line_char_count(&c.text, row);
294 let col = pos.col.min(line_chars);
295 Position::new(row, col)
296 }
297
298 pub fn replace_all(&mut self, text: &str) {
301 let new_cursor = {
302 let mut c = self.content.lock().unwrap();
303 c.text = ropey::Rope::from_str(text);
304 let n = c.text.len_lines();
305 let last_row = n.saturating_sub(1);
306 let row = self.cursor.row.min(last_row);
307 let line_chars = rope_line_char_count(&c.text, row);
308 let col = self.cursor.col.min(line_chars);
309 c.dirty_gen = c.dirty_gen.wrapping_add(1);
310 c.cached_joined = None;
311 c.cached_byte_len = None;
312 Position::new(row, col)
313 };
314 self.cursor = new_cursor;
315 }
316
317 pub(crate) fn dirty_gen_bump(&mut self) {
321 let mut c = self.content.lock().unwrap();
322 c.dirty_gen = c.dirty_gen.wrapping_add(1);
323 c.cached_joined = None;
324 c.cached_byte_len = None;
325 }
326
327 pub fn byte_len(&self) -> usize {
333 let mut c = self.content.lock().unwrap();
334 let dg = c.dirty_gen;
335 if let Some((cached_dg, len)) = c.cached_byte_len
336 && cached_dg == dg
337 {
338 return len;
339 }
340 let total = c.text.len_bytes();
341 c.cached_byte_len = Some((dg, total));
342 total
343 }
344
345 pub fn content_joined(&self) -> std::sync::Arc<String> {
354 let mut c = self.content.lock().unwrap();
355 let dg = c.dirty_gen;
356 if let Some((cached_dg, ref s)) = c.cached_joined
357 && cached_dg == dg
358 {
359 return std::sync::Arc::clone(s);
360 }
361 let joined = std::sync::Arc::new(c.text.to_string());
362 c.cached_joined = Some((dg, std::sync::Arc::clone(&joined)));
363 joined
364 }
365
366 pub fn rope(&self) -> ropey::Rope {
375 self.content.lock().unwrap().text.clone()
376 }
377
378 pub(crate) fn content_lock(&self) -> MutexGuard<'_, Buffer> {
380 self.content.lock().unwrap()
381 }
382
383 pub(crate) fn content_lock_mut(&mut self) -> MutexGuard<'_, Buffer> {
385 self.content.lock().unwrap()
386 }
387
388 fn cursor_screen_row_from(&self, viewport: &Viewport, top: usize) -> Option<usize> {
391 let cursor = self.cursor;
392 if cursor.row < top {
393 return None;
394 }
395 let c = self.content.lock().unwrap();
396 let cursor_row = cursor.row.min(c.text.len_lines().saturating_sub(1));
400 if cursor_row < top {
401 return None;
402 }
403 let v = *viewport;
404 let mut screen = 0usize;
405 for r in top..=cursor_row {
406 if c.folds.iter().any(|f| f.hides(r)) {
407 continue;
408 }
409 let line = rope_line_str(&c.text, r);
410 let segs = crate::wrap::wrap_segments(&line, v.text_width, v.wrap);
411 if r == cursor_row {
412 let seg_idx = crate::wrap::segment_for_col(&segs, cursor.col);
413 return Some(screen + seg_idx);
414 }
415 screen += segs.len();
416 }
417 None
418 }
419
420 pub fn undo_stack_is_empty(&self) -> bool {
430 self.content.lock().unwrap().undo.is_at_root()
431 }
432
433 pub fn redo_stack_is_empty(&self) -> bool {
434 !self.content.lock().unwrap().undo.has_redo()
435 }
436
437 pub fn undo_stack_len(&self) -> usize {
438 self.content.lock().unwrap().undo.depth()
439 }
440
441 pub fn push_undo_entry(&self, entry: crate::UndoEntry) {
445 self.content.lock().unwrap().undo.push(entry);
446 }
447
448 pub fn undo_step(
452 &self,
453 rope: ropey::Rope,
454 cursor: (usize, usize),
455 marks: crate::MarkSnapshot,
456 ) -> Option<crate::UndoEntry> {
457 self.content
458 .lock()
459 .unwrap()
460 .undo
461 .undo_step(rope, cursor, marks)
462 }
463
464 pub fn redo_step(
467 &self,
468 rope: ropey::Rope,
469 cursor: (usize, usize),
470 marks: crate::MarkSnapshot,
471 ) -> Option<crate::UndoEntry> {
472 self.content
473 .lock()
474 .unwrap()
475 .undo
476 .redo_step(rope, cursor, marks)
477 }
478
479 pub fn pop_committed(&self) -> bool {
483 self.content.lock().unwrap().undo.pop_committed()
484 }
485
486 pub fn seq_earlier_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 .seq_earlier_step(rope, cursor, marks)
501 }
502
503 pub fn seq_later_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 .seq_later_step(rope, cursor, marks)
516 }
517
518 pub fn seq_earlier_timestamp(&self) -> Option<std::time::SystemTime> {
520 self.content.lock().unwrap().undo.seq_earlier_timestamp()
521 }
522
523 pub fn seq_later_timestamp(&self) -> Option<std::time::SystemTime> {
525 self.content.lock().unwrap().undo.seq_later_timestamp()
526 }
527
528 pub fn undo_leaves(&self) -> Vec<(u64, usize, std::time::SystemTime, bool)> {
531 self.content.lock().unwrap().undo.leaves()
532 }
533
534 pub fn peek_undo_timestamp(&self) -> Option<std::time::SystemTime> {
535 self.content.lock().unwrap().undo.parent_timestamp()
536 }
537
538 pub fn peek_redo_timestamp(&self) -> Option<std::time::SystemTime> {
539 self.content.lock().unwrap().undo.child_timestamp()
540 }
541
542 pub fn clear_undo_redo(&self) {
543 self.content.lock().unwrap().undo.clear_all();
544 }
545
546 pub fn undo_to_serializable(&self) -> (crate::SerTree, u64) {
553 let mut c = self.content.lock().unwrap();
554 let rope = c.text.clone();
555 c.undo.sync_current(rope);
556 let seq = c.undo.current_node_seq();
557 (c.undo.to_serializable(), seq)
558 }
559
560 pub fn install_undo_tree(&self, ser: &crate::SerTree) -> bool {
566 match crate::UndoTree::from_serializable(ser) {
567 Some(tree) => {
568 self.content.lock().unwrap().undo = tree;
569 true
570 }
571 None => false,
572 }
573 }
574
575 pub fn install_recovered_undo_tree(&self, ser: &crate::SerTree, current_seq: u64) -> bool {
589 let Some(mut tree) = crate::UndoTree::from_serializable(ser) else {
590 return false;
591 };
592 if tree.current_node_seq() != current_seq {
593 return false;
594 }
595 let mut c = self.content.lock().unwrap();
596 let materialized = tree.current_content();
597 if materialized.to_string().trim_end_matches('\n')
598 != c.text.to_string().trim_end_matches('\n')
599 {
600 return false;
601 }
602 c.undo = tree;
603 true
604 }
605
606 pub fn clear_redo(&self) {
607 self.content.lock().unwrap().undo.clear_redo();
608 }
609
610 pub fn undo_group_active(&self) -> bool {
613 self.content.lock().unwrap().undo_group_active()
614 }
615
616 pub fn undo_group_arm(&self) -> bool {
618 self.content.lock().unwrap().undo_group_arm()
619 }
620
621 pub fn cap_undo(&self, cap: usize) {
622 self.content.lock().unwrap().undo.cap(cap);
623 }
624
625 pub fn content_dirty(&self) -> bool {
626 self.content.lock().unwrap().content_dirty
627 }
628
629 pub fn set_content_dirty(&self, v: bool) {
630 self.content.lock().unwrap().content_dirty = v;
631 }
632
633 pub fn mark_content_dirty(&self) {
634 let mut c = self.content.lock().unwrap();
635 c.content_dirty = true;
636 c.cached_editor_content = None;
637 }
638
639 pub fn take_dirty(&self) -> bool {
640 let mut c = self.content.lock().unwrap();
641 let v = c.content_dirty;
642 c.content_dirty = false;
643 v
644 }
645
646 pub fn cached_editor_content(&self) -> Option<std::sync::Arc<String>> {
647 self.content.lock().unwrap().cached_editor_content.clone()
648 }
649
650 pub fn set_cached_editor_content(&self, arc: std::sync::Arc<String>) {
651 self.content.lock().unwrap().cached_editor_content = Some(arc);
652 }
653
654 pub fn push_fold_op(&self, op: crate::FoldOp) {
655 self.content.lock().unwrap().pending_fold_ops.push(op);
656 }
657
658 pub fn take_fold_ops(&self) -> Vec<crate::FoldOp> {
659 std::mem::take(&mut self.content.lock().unwrap().pending_fold_ops)
660 }
661
662 pub fn extend_change_log(&self, edits: impl IntoIterator<Item = crate::EngineEdit>) {
663 self.content.lock().unwrap().change_log.extend(edits);
664 }
665
666 pub fn take_change_log(&self) -> Vec<crate::EngineEdit> {
667 std::mem::take(&mut self.content.lock().unwrap().change_log)
668 }
669
670 pub fn extend_pending_content_edits(
671 &self,
672 edits: impl IntoIterator<Item = crate::ContentEdit>,
673 ) {
674 self.content
675 .lock()
676 .unwrap()
677 .pending_content_edits
678 .extend(edits);
679 }
680
681 pub fn push_pending_content_edit(&self, edit: crate::ContentEdit) {
682 self.content
683 .lock()
684 .unwrap()
685 .pending_content_edits
686 .push(edit);
687 }
688
689 pub fn take_pending_content_edits(&self) -> Vec<crate::ContentEdit> {
690 std::mem::take(&mut self.content.lock().unwrap().pending_content_edits)
691 }
692
693 pub fn clear_pending_content_edits(&self) {
694 self.content.lock().unwrap().pending_content_edits.clear();
695 }
696
697 pub fn pending_content_reset(&self) -> bool {
698 self.content.lock().unwrap().pending_content_reset
699 }
700
701 pub fn set_pending_content_reset(&self, v: bool) {
702 self.content.lock().unwrap().pending_content_reset = v;
703 }
704
705 pub fn take_pending_content_reset(&self) -> bool {
706 let mut c = self.content.lock().unwrap();
707 let v = c.pending_content_reset;
708 c.pending_content_reset = false;
709 v
710 }
711
712 pub fn mark(&self, c: char) -> Option<(usize, usize)> {
713 self.content_lock().marks.get(&c).copied()
714 }
715 pub fn set_mark(&mut self, c: char, pos: (usize, usize)) {
716 self.content_lock_mut().marks.insert(c, pos);
717 }
718 pub fn clear_mark(&mut self, c: char) {
719 self.content_lock_mut().marks.remove(&c);
720 }
721 pub fn marks_cloned(&self) -> std::collections::BTreeMap<char, (usize, usize)> {
722 self.content_lock().marks.clone()
723 }
724 pub fn set_marks(&mut self, marks: std::collections::BTreeMap<char, (usize, usize)>) {
725 self.content_lock_mut().marks = marks;
726 }
727 pub fn rebase_marks(
731 &mut self,
732 edit_start: usize,
733 drop_end: usize,
734 shift_threshold: usize,
735 delta: isize,
736 ) {
737 let mut c = self.content_lock_mut();
738 let mut to_drop: Vec<char> = Vec::new();
739 for (ch, (row, _col)) in c.marks.iter_mut() {
740 if (edit_start..drop_end).contains(row) {
741 to_drop.push(*ch);
742 } else if *row >= shift_threshold {
743 *row = ((*row as isize) + delta).max(0) as usize;
744 }
745 }
746 for ch in to_drop {
747 c.marks.remove(&ch);
748 }
749 }
750 pub fn syntax_fold_ranges_cloned(&self) -> Vec<(usize, usize)> {
751 self.content_lock().syntax_fold_ranges.clone()
752 }
753 pub fn set_syntax_fold_ranges(&mut self, ranges: Vec<(usize, usize)>) {
754 self.content_lock_mut().syntax_fold_ranges = ranges;
755 }
756}
757
758pub fn rope_line_str(rope: &ropey::Rope, row: usize) -> String {
763 let mut s = rope.line(row).to_string();
764 if s.ends_with('\n') {
766 s.pop();
767 }
768 s
769}
770
771pub fn rope_line_bytes(rope: &ropey::Rope, row: usize) -> usize {
773 let slice = rope.line(row);
774 let bytes = slice.len_bytes();
775 if row + 1 < rope.len_lines() && bytes > 0 {
777 bytes - 1
778 } else {
779 bytes
780 }
781}
782
783pub(crate) fn rope_line_char_count(rope: &ropey::Rope, row: usize) -> usize {
785 let slice = rope.line(row);
786 let chars = slice.len_chars();
787 if row + 1 < rope.len_lines() && chars > 0 {
789 chars - 1
790 } else {
791 chars
792 }
793}
794
795pub(crate) fn pos_to_char_idx(rope: &ropey::Rope, row: usize, col: usize) -> usize {
800 let row = row.min(rope.len_lines().saturating_sub(1));
801 let line_start = rope.line_to_char(row);
802 let line_char_count = rope_line_char_count(rope, row);
803 line_start + col.min(line_char_count)
804}
805
806#[cfg(test)]
807mod tests {
808 use super::*;
809
810 #[test]
811 fn new_has_one_empty_row() {
812 let b = View::new();
813 assert_eq!(b.row_count(), 1);
814 assert_eq!(rope_line_str(&b.rope(), 0), "");
815 assert_eq!(b.cursor(), Position::default());
816 }
817
818 #[test]
819 fn from_str_splits_on_newline() {
820 let b = View::from_str("foo\nbar\nbaz");
821 assert_eq!(b.row_count(), 3);
822 assert_eq!(rope_line_str(&b.rope(), 0), "foo");
823 assert_eq!(rope_line_str(&b.rope(), 2), "baz");
824 }
825
826 #[test]
827 fn from_str_trailing_newline_keeps_empty_row() {
828 let b = View::from_str("foo\n");
829 assert_eq!(b.row_count(), 2);
830 assert_eq!(rope_line_str(&b.rope(), 1), "");
831 }
832
833 #[test]
834 fn from_str_empty_input_keeps_one_row() {
835 let b = View::from_str("");
836 assert_eq!(b.row_count(), 1);
837 assert_eq!(rope_line_str(&b.rope(), 0), "");
838 }
839
840 #[test]
841 fn as_string_round_trips() {
842 let b = View::from_str("a\nb\nc");
843 assert_eq!(b.as_string(), "a\nb\nc");
844 }
845
846 #[test]
847 fn dirty_gen_starts_at_zero() {
848 assert_eq!(View::new().dirty_gen(), 0);
849 }
850
851 fn single_node_tree(base: &str, seq: u64) -> crate::SerTree {
854 crate::SerTree {
855 base: base.to_string(),
856 nodes: vec![crate::SerNode {
857 parent: None,
858 children: Vec::new(),
859 last_child: None,
860 delta: None,
861 cursor: (0, 0),
862 timestamp_unix_ms: 0,
863 marks: crate::MarkSnapshot::default(),
864 seq,
865 }],
866 root: 0,
867 current: 0,
868 next_seq: seq + 1,
869 }
870 }
871
872 #[test]
876 fn install_recovered_undo_tree_guards_seq_and_content() {
877 let v = View::from_str("alpha\nhello");
879 assert!(v.install_recovered_undo_tree(&single_node_tree("alpha\nhello", 3), 3));
880
881 let v = View::from_str("alpha\nhello");
883 assert!(!v.install_recovered_undo_tree(&single_node_tree("alpha\nhello", 3), 4));
884
885 let v = View::from_str("alpha\nhello");
887 assert!(!v.install_recovered_undo_tree(&single_node_tree("something else", 3), 3));
888
889 let v = View::from_str("alpha\nhello");
891 assert!(v.install_recovered_undo_tree(&single_node_tree("alpha\nhello\n", 3), 3));
892 }
893
894 fn vp_wrap(width: u16, height: u16) -> Viewport {
895 Viewport {
896 top_row: 0,
897 top_col: 0,
898 width,
899 height,
900 wrap: crate::Wrap::Char,
901 text_width: width,
902 tab_width: 0,
903 }
904 }
905
906 #[test]
907 fn ensure_cursor_visible_wrap_scrolls_when_cursor_below_screen() {
908 let mut b = View::from_str("aaaaaaaaaa\nb\nc");
909 let mut v = vp_wrap(4, 3);
910 b.set_cursor(Position::new(2, 0));
911 b.ensure_cursor_visible(&mut v);
912 assert_eq!(v.top_row, 1);
913 }
914
915 #[test]
916 fn ensure_cursor_visible_wrap_no_scroll_when_visible() {
917 let mut b = View::from_str("aaaaaaaaaa\nb");
918 let mut v = vp_wrap(4, 4);
919 b.set_cursor(Position::new(0, 5));
920 b.ensure_cursor_visible(&mut v);
921 assert_eq!(v.top_row, 0);
922 }
923
924 #[test]
925 fn ensure_cursor_visible_wrap_snaps_top_when_cursor_above() {
926 let mut b = View::from_str("a\nb\nc\nd\ne");
927 let mut v = vp_wrap(4, 2);
928 v.top_row = 3;
929 b.set_cursor(Position::new(1, 0));
930 b.ensure_cursor_visible(&mut v);
931 assert_eq!(v.top_row, 1);
932 }
933
934 #[test]
935 fn screen_rows_between_sums_segments_under_wrap() {
936 let b = View::from_str("aaaaaaaaa\nb\n");
937 let v = vp_wrap(4, 0);
938 assert_eq!(b.screen_rows_between(&v, 0, 0), 3);
939 assert_eq!(b.screen_rows_between(&v, 0, 1), 4);
940 assert_eq!(b.screen_rows_between(&v, 0, 2), 5);
941 assert_eq!(b.screen_rows_between(&v, 1, 2), 2);
942 }
943
944 #[test]
945 fn screen_rows_between_one_per_doc_row_when_wrap_off() {
946 let b = View::from_str("aaaaa\nb\nc");
947 let v = Viewport::default();
948 assert_eq!(b.screen_rows_between(&v, 0, 2), 3);
949 }
950
951 #[test]
952 fn max_top_for_height_walks_back_until_height_reached() {
953 let b = View::from_str("a\nb\nc\nd\neeeeeeee");
954 let v = vp_wrap(4, 0);
955 assert_eq!(b.max_top_for_height(&v, 4), 2);
956 assert_eq!(b.max_top_for_height(&v, 99), 0);
957 }
958
959 #[test]
960 fn cursor_screen_row_returns_none_when_wrap_off() {
961 let b = View::from_str("a");
962 let v = Viewport::default();
963 assert!(b.cursor_screen_row(&v).is_none());
964 }
965
966 #[test]
967 fn cursor_screen_row_under_wrap() {
968 let mut b = View::from_str("aaaaaaaaaa\nb");
969 let v = vp_wrap(4, 0);
970 b.set_cursor(Position::new(0, 5));
971 assert_eq!(b.cursor_screen_row(&v), Some(1));
972 b.set_cursor(Position::new(1, 0));
973 assert_eq!(b.cursor_screen_row(&v), Some(3));
974 }
975
976 #[test]
980 fn cursor_screen_row_survives_shrink_from_other_view() {
981 let a = View::from_str("a\nb\nc\nd\ne");
982 let arc = a.content_arc();
983 let mut view_a = View::new_view(Arc::clone(&arc));
984 let mut view_b = View::new_view(Arc::clone(&arc));
985 view_b.set_cursor(Position::new(4, 0));
986 view_a.replace_all("a");
988 let v = vp_wrap(4, 3);
989 assert_eq!(view_b.cursor_screen_row(&v), Some(0));
990 let mut v2 = vp_wrap(4, 3);
991 view_b.ensure_cursor_visible(&mut v2); }
993
994 #[test]
995 fn ensure_cursor_visible_falls_back_when_wrap_disabled() {
996 let mut b = View::from_str("a\nb\nc\nd\ne");
997 let mut v = Viewport {
998 top_row: 0,
999 top_col: 0,
1000 width: 4,
1001 height: 2,
1002 wrap: crate::Wrap::None,
1003 text_width: 4,
1004 tab_width: 0,
1005 };
1006 b.set_cursor(Position::new(4, 0));
1007 b.ensure_cursor_visible(&mut v);
1008 assert_eq!(v.top_row, 3);
1009 }
1010
1011 #[test]
1017 fn undo_stack_shared_across_views() {
1018 use crate::UndoEntry;
1019 use std::time::SystemTime;
1020
1021 let a = View::from_str("hello");
1022 let arc = a.content_arc();
1023 let view_a = View::new_view(Arc::clone(&arc));
1024 let view_b = View::new_view(Arc::clone(&arc));
1025
1026 assert!(view_a.undo_stack_is_empty());
1027 assert_eq!(view_a.undo_stack_len(), 0);
1028
1029 view_a.push_undo_entry(UndoEntry {
1030 rope: view_a.rope(),
1031 cursor: (0, 0),
1032 timestamp: SystemTime::UNIX_EPOCH,
1033 marks: Default::default(),
1034 });
1035
1036 assert_eq!(view_b.undo_stack_len(), 1);
1038 assert!(!view_b.undo_stack_is_empty());
1039 }
1040
1041 #[test]
1046 fn redo_stack_shared_across_views() {
1047 use crate::UndoEntry;
1048 use std::time::SystemTime;
1049
1050 let a = View::from_str("world");
1051 let arc = a.content_arc();
1052 let view_a = View::new_view(Arc::clone(&arc));
1053 let view_b = View::new_view(Arc::clone(&arc));
1054
1055 assert!(view_a.redo_stack_is_empty());
1056
1057 view_b.push_undo_entry(UndoEntry {
1061 rope: view_b.rope(),
1062 cursor: (0, 0),
1063 timestamp: SystemTime::UNIX_EPOCH,
1064 marks: Default::default(),
1065 });
1066 view_b.undo_step(view_b.rope(), (0, 2), Default::default());
1067
1068 assert!(!view_a.redo_stack_is_empty());
1070 let entry = view_a.redo_step(view_a.rope(), (0, 0), Default::default());
1071 assert!(entry.is_some());
1072 assert_eq!(entry.unwrap().cursor, (0, 2));
1073 }
1074
1075 #[test]
1080 fn clear_undo_redo_shared_across_views() {
1081 use crate::UndoEntry;
1082 use std::time::SystemTime;
1083
1084 let a = View::from_str("abc");
1085 let arc = a.content_arc();
1086 let view_a = View::new_view(Arc::clone(&arc));
1087 let view_b = View::new_view(Arc::clone(&arc));
1088
1089 for _ in 0..2 {
1091 view_a.push_undo_entry(UndoEntry {
1092 rope: view_a.rope(),
1093 cursor: (0, 0),
1094 timestamp: SystemTime::UNIX_EPOCH,
1095 marks: Default::default(),
1096 });
1097 }
1098 view_a.undo_step(view_a.rope(), (0, 1), Default::default());
1099 assert!(!view_a.undo_stack_is_empty());
1100 assert!(!view_a.redo_stack_is_empty());
1101
1102 view_b.clear_undo_redo();
1103 assert!(view_a.undo_stack_is_empty());
1104 assert!(view_a.redo_stack_is_empty());
1105 }
1106
1107 #[test]
1109 fn content_dirty_shared_across_views() {
1110 let a = View::from_str("test");
1111 let arc = a.content_arc();
1112 let view_a = View::new_view(Arc::clone(&arc));
1113 let view_b = View::new_view(Arc::clone(&arc));
1114
1115 assert!(!view_a.content_dirty());
1116
1117 view_b.mark_content_dirty();
1118 assert!(view_a.content_dirty());
1119
1120 let taken = view_a.take_dirty();
1121 assert!(taken);
1122 assert!(!view_b.content_dirty());
1123 }
1124
1125 #[test]
1127 fn pending_fold_ops_shared_across_views() {
1128 let a = View::from_str("a\nb\nc");
1129 let arc = a.content_arc();
1130 let view_a = View::new_view(Arc::clone(&arc));
1131 let view_b = View::new_view(Arc::clone(&arc));
1132
1133 view_a.push_fold_op(crate::FoldOp::Add {
1134 start_row: 0,
1135 end_row: 1,
1136 closed: true,
1137 });
1138
1139 let ops = view_b.take_fold_ops();
1140 assert_eq!(ops.len(), 1);
1141 assert!(matches!(
1142 ops[0],
1143 crate::FoldOp::Add {
1144 start_row: 0,
1145 end_row: 1,
1146 closed: true
1147 }
1148 ));
1149 }
1150
1151 #[test]
1153 fn pending_content_reset_shared_across_views() {
1154 let a = View::from_str("x");
1155 let arc = a.content_arc();
1156 let view_a = View::new_view(Arc::clone(&arc));
1157 let view_b = View::new_view(Arc::clone(&arc));
1158
1159 assert!(!view_a.pending_content_reset());
1160 view_b.set_pending_content_reset(true);
1161 assert!(view_a.pending_content_reset());
1162 let taken = view_a.take_pending_content_reset();
1163 assert!(taken);
1164 assert!(!view_b.pending_content_reset());
1165 }
1166
1167 #[test]
1172 fn buffer_views_independent_cursors() {
1173 let a = View::from_str("hello\nworld");
1174 let arc = a.content_arc();
1175 let mut view_a = View::new_view(Arc::clone(&arc));
1176 let mut view_b = View::new_view(Arc::clone(&arc));
1177
1178 view_a.set_cursor(Position::new(1, 3));
1179 assert_eq!(view_b.cursor(), Position::new(0, 0));
1181
1182 view_b.set_cursor(Position::new(0, 2));
1183 assert_eq!(view_a.cursor(), Position::new(1, 3));
1185 }
1186
1187 #[test]
1191 fn last_cursor_reflects_most_recent_move_across_views() {
1192 let a = View::from_str("aaaa\nbbbb\ncccc\ndddd");
1193 let arc = a.content_arc();
1194 let mut view_a = View::new_view(Arc::clone(&arc));
1195 let mut view_b = View::new_view(Arc::clone(&arc));
1196
1197 view_a.set_cursor(Position::new(1, 2));
1198 assert_eq!(view_a.last_cursor(), (1, 2));
1199 assert_eq!(view_b.last_cursor(), (1, 2));
1201
1202 view_b.set_cursor(Position::new(3, 1));
1204 assert_eq!(view_a.last_cursor(), (3, 1));
1205 assert_eq!(view_b.last_cursor(), (3, 1));
1206
1207 view_a.set_cursor(Position::new(99, 99));
1209 assert_eq!(view_a.last_cursor(), (3, 4));
1210 }
1211
1212 #[test]
1216 fn clamp_position_restore_semantics() {
1217 let b = View::from_str("ab\ncdef\ng");
1218 assert_eq!(b.clamp_position(Position::new(99, 99)), Position::new(2, 1));
1220 assert_eq!(b.clamp_position(Position::new(1, 99)), Position::new(1, 4));
1222 assert_eq!(b.clamp_position(Position::new(1, 2)), Position::new(1, 2));
1224 }
1225
1226 #[test]
1228 fn buffer_views_share_content() {
1229 use crate::edit::Edit;
1230
1231 let a = View::from_str("foo");
1232 let arc = a.content_arc();
1233 let mut view_a = View::new_view(Arc::clone(&arc));
1234 let view_b = View::new_view(Arc::clone(&arc));
1235
1236 view_a.apply_edit(Edit::InsertStr {
1237 at: Position::new(0, 3),
1238 text: "bar".into(),
1239 });
1240
1241 assert_eq!(rope_line_str(&view_a.rope(), 0), "foobar");
1242 assert_eq!(rope_line_str(&view_b.rope(), 0), "foobar");
1243 }
1244}
1245
1246#[cfg(test)]
1247mod marks_shared_content_tests {
1248 use super::*;
1249
1250 #[test]
1251 fn marks_shared_across_views() {
1252 let a = View::from_str("hello\nworld");
1254 let content = a.content_arc();
1255 let mut view_a = View::new_view(std::sync::Arc::clone(&content));
1256 let view_b = View::new_view(std::sync::Arc::clone(&content));
1257
1258 view_a.set_mark('x', (1, 3));
1260
1261 assert_eq!(view_b.mark('x'), Some((1, 3)));
1263 }
1264
1265 #[test]
1266 fn syntax_fold_ranges_shared_across_views() {
1267 let a = View::from_str("fn foo() {\n bar();\n}");
1268 let content = a.content_arc();
1269 let mut view_a = View::new_view(std::sync::Arc::clone(&content));
1270 let view_b = View::new_view(std::sync::Arc::clone(&content));
1271
1272 view_a.set_syntax_fold_ranges(vec![(0, 2)]);
1273
1274 assert_eq!(view_b.syntax_fold_ranges_cloned(), vec![(0, 2)]);
1275 }
1276}