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 viewport.top_col = 0;
191 return;
192 };
193 while screen >= height {
194 let c = self.content.lock().unwrap();
195 let mut next = viewport.top_row + 1;
196 while next <= cursor.row && c.folds.iter().any(|f| f.hides(next)) {
197 next += 1;
198 }
199 if next > cursor.row {
200 drop(c);
201 viewport.top_row = cursor.row;
202 break;
203 }
204 for r in viewport.top_row..next {
208 if c.folds.iter().any(|f| f.hides(r)) {
209 continue;
210 }
211 let line = rope_line_str(&c.text, r);
212 screen -= crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len();
213 }
214 drop(c);
215 viewport.top_row = next;
216 }
217 viewport.top_col = 0;
218 }
219
220 pub fn cursor_screen_row(&self, viewport: &Viewport) -> Option<usize> {
222 if matches!(viewport.wrap, crate::Wrap::None) || viewport.text_width == 0 {
223 return None;
224 }
225 self.cursor_screen_row_from(viewport, viewport.top_row)
226 }
227
228 pub fn screen_rows_between(&self, viewport: &Viewport, start: usize, end: usize) -> usize {
230 if start > end {
231 return 0;
232 }
233 let c = self.content.lock().unwrap();
234 let n = c.text.len_lines();
235 let last = n.saturating_sub(1);
236 let end = end.min(last);
237 let v = *viewport;
238 let mut total = 0usize;
239 for r in start..=end {
240 if c.folds.iter().any(|f| f.hides(r)) {
241 continue;
242 }
243 if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
244 total += 1;
245 } else {
246 let line = rope_line_str(&c.text, r);
247 total += crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len();
248 }
249 }
250 total
251 }
252
253 pub fn max_top_for_height(&self, viewport: &Viewport, height: usize) -> usize {
256 if height == 0 {
257 return 0;
258 }
259 let c = self.content.lock().unwrap();
260 let n = c.text.len_lines();
261 let last = n.saturating_sub(1);
262 let mut total = 0usize;
263 let mut row = last;
264 loop {
265 if !c.folds.iter().any(|f| f.hides(row)) {
266 let v = *viewport;
267 total += if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
268 1
269 } else {
270 let line = rope_line_str(&c.text, row);
271 crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len()
272 };
273 }
274 if total >= height {
275 return row;
276 }
277 if row == 0 {
278 return 0;
279 }
280 row -= 1;
281 }
282 }
283
284 pub fn clamp_position(&self, pos: Position) -> Position {
286 let c = self.content.lock().unwrap();
287 let n = c.text.len_lines();
288 let last_row = n.saturating_sub(1);
289 let row = pos.row.min(last_row);
290 let line_chars = rope_line_char_count(&c.text, row);
291 let col = pos.col.min(line_chars);
292 Position::new(row, col)
293 }
294
295 pub fn replace_all(&mut self, text: &str) {
298 let new_cursor = {
299 let mut c = self.content.lock().unwrap();
300 c.text = ropey::Rope::from_str(text);
301 let n = c.text.len_lines();
302 let last_row = n.saturating_sub(1);
303 let row = self.cursor.row.min(last_row);
304 let line_chars = rope_line_char_count(&c.text, row);
305 let col = self.cursor.col.min(line_chars);
306 c.dirty_gen = c.dirty_gen.wrapping_add(1);
307 c.cached_joined = None;
308 c.cached_byte_len = None;
309 Position::new(row, col)
310 };
311 self.cursor = new_cursor;
312 }
313
314 pub(crate) fn fold_gen_bump(&mut self) {
322 let mut c = self.content.lock().unwrap();
323 c.fold_gen = c.fold_gen.wrapping_add(1);
324 }
325
326 pub(crate) fn dirty_gen_bump(&mut self) {
328 let mut c = self.content.lock().unwrap();
329 c.dirty_gen = c.dirty_gen.wrapping_add(1);
330 c.cached_joined = None;
331 c.cached_byte_len = None;
332 }
333
334 pub fn byte_len(&self) -> usize {
340 let mut c = self.content.lock().unwrap();
341 let dg = c.dirty_gen;
342 if let Some((cached_dg, len)) = c.cached_byte_len
343 && cached_dg == dg
344 {
345 return len;
346 }
347 let total = c.text.len_bytes();
348 c.cached_byte_len = Some((dg, total));
349 total
350 }
351
352 pub fn content_joined(&self) -> std::sync::Arc<String> {
361 let mut c = self.content.lock().unwrap();
362 let dg = c.dirty_gen;
363 if let Some((cached_dg, ref s)) = c.cached_joined
364 && cached_dg == dg
365 {
366 return std::sync::Arc::clone(s);
367 }
368 let joined = std::sync::Arc::new(c.text.to_string());
369 c.cached_joined = Some((dg, std::sync::Arc::clone(&joined)));
370 joined
371 }
372
373 pub fn rope(&self) -> ropey::Rope {
382 self.content.lock().unwrap().text.clone()
383 }
384
385 pub(crate) fn content_lock(&self) -> MutexGuard<'_, Buffer> {
387 self.content.lock().unwrap()
388 }
389
390 pub(crate) fn content_lock_mut(&mut self) -> MutexGuard<'_, Buffer> {
392 self.content.lock().unwrap()
393 }
394
395 fn cursor_screen_row_from(&self, viewport: &Viewport, top: usize) -> Option<usize> {
398 let cursor = self.cursor;
399 if cursor.row < top {
400 return None;
401 }
402 let c = self.content.lock().unwrap();
403 let cursor_row = cursor.row.min(c.text.len_lines().saturating_sub(1));
407 if cursor_row < top {
408 return None;
409 }
410 let v = *viewport;
411 let mut screen = 0usize;
412 for r in top..=cursor_row {
413 if c.folds.iter().any(|f| f.hides(r)) {
414 continue;
415 }
416 let line = rope_line_str(&c.text, r);
417 let segs = crate::wrap::wrap_segments(&line, v.text_width, v.wrap);
418 if r == cursor_row {
419 let seg_idx = crate::wrap::segment_for_col(&segs, cursor.col);
420 return Some(screen + seg_idx);
421 }
422 screen += segs.len();
423 }
424 None
425 }
426
427 pub fn undo_stack_is_empty(&self) -> bool {
437 self.content.lock().unwrap().undo.is_at_root()
438 }
439
440 pub fn redo_stack_is_empty(&self) -> bool {
441 !self.content.lock().unwrap().undo.has_redo()
442 }
443
444 pub fn undo_stack_len(&self) -> usize {
445 self.content.lock().unwrap().undo.depth()
446 }
447
448 pub fn push_undo_entry(&self, entry: crate::UndoEntry) {
452 self.content.lock().unwrap().undo.push(entry);
453 }
454
455 pub fn undo_step(
459 &self,
460 rope: ropey::Rope,
461 cursor: (usize, usize),
462 marks: crate::MarkSnapshot,
463 ) -> Option<crate::UndoEntry> {
464 self.content
465 .lock()
466 .unwrap()
467 .undo
468 .undo_step(rope, cursor, marks)
469 }
470
471 pub fn redo_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 .redo_step(rope, cursor, marks)
484 }
485
486 pub fn pop_committed(&self) -> bool {
490 self.content.lock().unwrap().undo.pop_committed()
491 }
492
493 pub fn seq_earlier_step(
498 &self,
499 rope: ropey::Rope,
500 cursor: (usize, usize),
501 marks: crate::MarkSnapshot,
502 ) -> Option<crate::UndoEntry> {
503 self.content
504 .lock()
505 .unwrap()
506 .undo
507 .seq_earlier_step(rope, cursor, marks)
508 }
509
510 pub fn seq_later_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_later_step(rope, cursor, marks)
523 }
524
525 pub fn seq_earlier_timestamp(&self) -> Option<std::time::SystemTime> {
527 self.content.lock().unwrap().undo.seq_earlier_timestamp()
528 }
529
530 pub fn seq_later_timestamp(&self) -> Option<std::time::SystemTime> {
532 self.content.lock().unwrap().undo.seq_later_timestamp()
533 }
534
535 pub fn undo_leaves(&self) -> Vec<(u64, usize, std::time::SystemTime, bool)> {
538 self.content.lock().unwrap().undo.leaves()
539 }
540
541 pub fn peek_undo_timestamp(&self) -> Option<std::time::SystemTime> {
542 self.content.lock().unwrap().undo.parent_timestamp()
543 }
544
545 pub fn peek_redo_timestamp(&self) -> Option<std::time::SystemTime> {
546 self.content.lock().unwrap().undo.child_timestamp()
547 }
548
549 pub fn clear_undo_redo(&self) {
550 self.content.lock().unwrap().undo.clear_all();
551 }
552
553 pub fn undo_to_serializable(&self) -> (crate::SerTree, u64) {
560 let mut c = self.content.lock().unwrap();
561 let rope = c.text.clone();
562 c.undo.sync_current(rope);
563 let seq = c.undo.current_node_seq();
564 (c.undo.to_serializable(), seq)
565 }
566
567 pub fn install_undo_tree(&self, ser: &crate::SerTree) -> bool {
573 match crate::UndoTree::from_serializable(ser) {
574 Some(tree) => {
575 self.content.lock().unwrap().undo = tree;
576 true
577 }
578 None => false,
579 }
580 }
581
582 pub fn install_recovered_undo_tree(&self, ser: &crate::SerTree, current_seq: u64) -> bool {
596 let Some(mut tree) = crate::UndoTree::from_serializable(ser) else {
597 return false;
598 };
599 if tree.current_node_seq() != current_seq {
600 return false;
601 }
602 let mut c = self.content.lock().unwrap();
603 let materialized = tree.current_content();
604 if materialized.to_string().trim_end_matches('\n')
605 != c.text.to_string().trim_end_matches('\n')
606 {
607 return false;
608 }
609 c.undo = tree;
610 true
611 }
612
613 pub fn clear_redo(&self) {
614 self.content.lock().unwrap().undo.clear_redo();
615 }
616
617 pub fn undo_group_active(&self) -> bool {
620 self.content.lock().unwrap().undo_group_active()
621 }
622
623 pub fn undo_group_arm(&self) -> bool {
625 self.content.lock().unwrap().undo_group_arm()
626 }
627
628 pub fn cap_undo(&self, cap: usize) {
629 self.content.lock().unwrap().undo.cap(cap);
630 }
631
632 pub fn content_dirty(&self) -> bool {
633 self.content.lock().unwrap().content_dirty
634 }
635
636 pub fn set_content_dirty(&self, v: bool) {
637 self.content.lock().unwrap().content_dirty = v;
638 }
639
640 pub fn mark_content_dirty(&self) {
641 let mut c = self.content.lock().unwrap();
642 c.content_dirty = true;
643 c.cached_editor_content = None;
644 }
645
646 pub fn take_dirty(&self) -> bool {
647 let mut c = self.content.lock().unwrap();
648 let v = c.content_dirty;
649 c.content_dirty = false;
650 v
651 }
652
653 pub fn cached_editor_content(&self) -> Option<std::sync::Arc<String>> {
654 self.content.lock().unwrap().cached_editor_content.clone()
655 }
656
657 pub fn set_cached_editor_content(&self, arc: std::sync::Arc<String>) {
658 self.content.lock().unwrap().cached_editor_content = Some(arc);
659 }
660
661 pub fn push_fold_op(&self, op: crate::FoldOp) {
662 self.content.lock().unwrap().pending_fold_ops.push(op);
663 }
664
665 pub fn take_fold_ops(&self) -> Vec<crate::FoldOp> {
666 std::mem::take(&mut self.content.lock().unwrap().pending_fold_ops)
667 }
668
669 pub fn extend_change_log(&self, edits: impl IntoIterator<Item = crate::EngineEdit>) {
670 self.content.lock().unwrap().change_log.extend(edits);
671 }
672
673 pub fn take_change_log(&self) -> Vec<crate::EngineEdit> {
674 std::mem::take(&mut self.content.lock().unwrap().change_log)
675 }
676
677 pub fn extend_pending_content_edits(
678 &self,
679 edits: impl IntoIterator<Item = crate::ContentEdit>,
680 ) {
681 self.content
682 .lock()
683 .unwrap()
684 .pending_content_edits
685 .extend(edits);
686 }
687
688 pub fn push_pending_content_edit(&self, edit: crate::ContentEdit) {
689 self.content
690 .lock()
691 .unwrap()
692 .pending_content_edits
693 .push(edit);
694 }
695
696 pub fn take_pending_content_edits(&self) -> Vec<crate::ContentEdit> {
697 std::mem::take(&mut self.content.lock().unwrap().pending_content_edits)
698 }
699
700 pub fn clear_pending_content_edits(&self) {
701 self.content.lock().unwrap().pending_content_edits.clear();
702 }
703
704 pub fn pending_content_reset(&self) -> bool {
705 self.content.lock().unwrap().pending_content_reset
706 }
707
708 pub fn set_pending_content_reset(&self, v: bool) {
709 self.content.lock().unwrap().pending_content_reset = v;
710 }
711
712 pub fn take_pending_content_reset(&self) -> bool {
713 let mut c = self.content.lock().unwrap();
714 let v = c.pending_content_reset;
715 c.pending_content_reset = false;
716 v
717 }
718
719 pub fn mark(&self, c: char) -> Option<(usize, usize)> {
720 self.content_lock().marks.get(&c).copied()
721 }
722 pub fn set_mark(&mut self, c: char, pos: (usize, usize)) {
723 self.content_lock_mut().marks.insert(c, pos);
724 }
725 pub fn clear_mark(&mut self, c: char) {
726 self.content_lock_mut().marks.remove(&c);
727 }
728 pub fn marks_cloned(&self) -> std::collections::BTreeMap<char, (usize, usize)> {
729 self.content_lock().marks.clone()
730 }
731 pub fn set_marks(&mut self, marks: std::collections::BTreeMap<char, (usize, usize)>) {
732 self.content_lock_mut().marks = marks;
733 }
734 pub fn rebase_marks(
738 &mut self,
739 edit_start: usize,
740 drop_end: usize,
741 shift_threshold: usize,
742 delta: isize,
743 ) {
744 let mut c = self.content_lock_mut();
745 let mut to_drop: Vec<char> = Vec::new();
746 for (ch, (row, _col)) in c.marks.iter_mut() {
747 if (edit_start..drop_end).contains(row) {
748 to_drop.push(*ch);
749 } else if *row >= shift_threshold {
750 *row = ((*row as isize) + delta).max(0) as usize;
751 }
752 }
753 for ch in to_drop {
754 c.marks.remove(&ch);
755 }
756 }
757 pub fn syntax_fold_ranges_cloned(&self) -> Vec<(usize, usize)> {
758 self.content_lock().syntax_fold_ranges.clone()
759 }
760 pub fn set_syntax_fold_ranges(&mut self, ranges: Vec<(usize, usize)>) {
761 self.content_lock_mut().syntax_fold_ranges = ranges;
762 }
763}
764
765pub fn floor_char_boundary(rope: &ropey::Rope, byte_idx: usize) -> usize {
778 let byte_idx = byte_idx.min(rope.len_bytes());
779 rope.char_to_byte(rope.byte_to_char(byte_idx))
780}
781
782pub fn rope_line_str(rope: &ropey::Rope, row: usize) -> String {
785 let mut s = rope.line(row).to_string();
786 if s.ends_with('\n') {
788 s.pop();
789 }
790 s
791}
792
793pub fn rope_line_bytes(rope: &ropey::Rope, row: usize) -> usize {
795 let slice = rope.line(row);
796 let bytes = slice.len_bytes();
797 if row + 1 < rope.len_lines() && bytes > 0 {
799 bytes - 1
800 } else {
801 bytes
802 }
803}
804
805pub fn rope_line_char_count(rope: &ropey::Rope, row: usize) -> usize {
807 let slice = rope.line(row);
808 let chars = slice.len_chars();
809 if row + 1 < rope.len_lines() && chars > 0 {
811 chars - 1
812 } else {
813 chars
814 }
815}
816
817pub fn pos_to_char_idx(rope: &ropey::Rope, row: usize, col: usize) -> usize {
822 let row = row.min(rope.len_lines().saturating_sub(1));
823 let line_start = rope.line_to_char(row);
824 let line_char_count = rope_line_char_count(rope, row);
825 line_start + col.min(line_char_count)
826}
827
828#[cfg(test)]
829mod tests {
830 use super::*;
831
832 #[test]
833 fn new_has_one_empty_row() {
834 let b = View::new();
835 assert_eq!(b.row_count(), 1);
836 assert_eq!(rope_line_str(&b.rope(), 0), "");
837 assert_eq!(b.cursor(), Position::default());
838 }
839
840 #[test]
841 fn from_str_splits_on_newline() {
842 let b = View::from_str("foo\nbar\nbaz");
843 assert_eq!(b.row_count(), 3);
844 assert_eq!(rope_line_str(&b.rope(), 0), "foo");
845 assert_eq!(rope_line_str(&b.rope(), 2), "baz");
846 }
847
848 #[test]
849 fn from_str_trailing_newline_keeps_empty_row() {
850 let b = View::from_str("foo\n");
851 assert_eq!(b.row_count(), 2);
852 assert_eq!(rope_line_str(&b.rope(), 1), "");
853 }
854
855 #[test]
856 fn from_str_empty_input_keeps_one_row() {
857 let b = View::from_str("");
858 assert_eq!(b.row_count(), 1);
859 assert_eq!(rope_line_str(&b.rope(), 0), "");
860 }
861
862 #[test]
863 fn as_string_round_trips() {
864 let b = View::from_str("a\nb\nc");
865 assert_eq!(b.as_string(), "a\nb\nc");
866 }
867
868 #[test]
869 fn dirty_gen_starts_at_zero() {
870 assert_eq!(View::new().dirty_gen(), 0);
871 }
872
873 fn single_node_tree(base: &str, seq: u64) -> crate::SerTree {
876 crate::SerTree {
877 base: base.to_string(),
878 nodes: vec![crate::SerNode {
879 parent: None,
880 children: Vec::new(),
881 last_child: None,
882 delta: None,
883 cursor: (0, 0),
884 timestamp_unix_ms: 0,
885 marks: crate::MarkSnapshot::default(),
886 seq,
887 }],
888 root: 0,
889 current: 0,
890 next_seq: seq + 1,
891 }
892 }
893
894 #[test]
898 fn install_recovered_undo_tree_guards_seq_and_content() {
899 let v = View::from_str("alpha\nhello");
901 assert!(v.install_recovered_undo_tree(&single_node_tree("alpha\nhello", 3), 3));
902
903 let v = View::from_str("alpha\nhello");
905 assert!(!v.install_recovered_undo_tree(&single_node_tree("alpha\nhello", 3), 4));
906
907 let v = View::from_str("alpha\nhello");
909 assert!(!v.install_recovered_undo_tree(&single_node_tree("something else", 3), 3));
910
911 let v = View::from_str("alpha\nhello");
913 assert!(v.install_recovered_undo_tree(&single_node_tree("alpha\nhello\n", 3), 3));
914 }
915
916 fn vp_wrap(width: u16, height: u16) -> Viewport {
917 Viewport {
918 top_row: 0,
919 top_col: 0,
920 width,
921 height,
922 wrap: crate::Wrap::Char,
923 text_width: width,
924 tab_width: 0,
925 }
926 }
927
928 #[test]
929 fn ensure_cursor_visible_wrap_scrolls_when_cursor_below_screen() {
930 let mut b = View::from_str("aaaaaaaaaa\nb\nc");
931 let mut v = vp_wrap(4, 3);
932 b.set_cursor(Position::new(2, 0));
933 b.ensure_cursor_visible(&mut v);
934 assert_eq!(v.top_row, 1);
935 }
936
937 #[test]
938 fn ensure_cursor_visible_wrap_no_scroll_when_visible() {
939 let mut b = View::from_str("aaaaaaaaaa\nb");
940 let mut v = vp_wrap(4, 4);
941 b.set_cursor(Position::new(0, 5));
942 b.ensure_cursor_visible(&mut v);
943 assert_eq!(v.top_row, 0);
944 }
945
946 #[test]
947 fn ensure_cursor_visible_wrap_snaps_top_when_cursor_above() {
948 let mut b = View::from_str("a\nb\nc\nd\ne");
949 let mut v = vp_wrap(4, 2);
950 v.top_row = 3;
951 b.set_cursor(Position::new(1, 0));
952 b.ensure_cursor_visible(&mut v);
953 assert_eq!(v.top_row, 1);
954 }
955
956 #[test]
957 fn screen_rows_between_sums_segments_under_wrap() {
958 let b = View::from_str("aaaaaaaaa\nb\n");
959 let v = vp_wrap(4, 0);
960 assert_eq!(b.screen_rows_between(&v, 0, 0), 3);
961 assert_eq!(b.screen_rows_between(&v, 0, 1), 4);
962 assert_eq!(b.screen_rows_between(&v, 0, 2), 5);
963 assert_eq!(b.screen_rows_between(&v, 1, 2), 2);
964 }
965
966 #[test]
967 fn screen_rows_between_one_per_doc_row_when_wrap_off() {
968 let b = View::from_str("aaaaa\nb\nc");
969 let v = Viewport::default();
970 assert_eq!(b.screen_rows_between(&v, 0, 2), 3);
971 }
972
973 #[test]
974 fn max_top_for_height_walks_back_until_height_reached() {
975 let b = View::from_str("a\nb\nc\nd\neeeeeeee");
976 let v = vp_wrap(4, 0);
977 assert_eq!(b.max_top_for_height(&v, 4), 2);
978 assert_eq!(b.max_top_for_height(&v, 99), 0);
979 }
980
981 #[test]
982 fn cursor_screen_row_returns_none_when_wrap_off() {
983 let b = View::from_str("a");
984 let v = Viewport::default();
985 assert!(b.cursor_screen_row(&v).is_none());
986 }
987
988 #[test]
989 fn cursor_screen_row_under_wrap() {
990 let mut b = View::from_str("aaaaaaaaaa\nb");
991 let v = vp_wrap(4, 0);
992 b.set_cursor(Position::new(0, 5));
993 assert_eq!(b.cursor_screen_row(&v), Some(1));
994 b.set_cursor(Position::new(1, 0));
995 assert_eq!(b.cursor_screen_row(&v), Some(3));
996 }
997
998 #[test]
1002 fn cursor_screen_row_survives_shrink_from_other_view() {
1003 let a = View::from_str("a\nb\nc\nd\ne");
1004 let arc = a.content_arc();
1005 let mut view_a = View::new_view(Arc::clone(&arc));
1006 let mut view_b = View::new_view(Arc::clone(&arc));
1007 view_b.set_cursor(Position::new(4, 0));
1008 view_a.replace_all("a");
1010 let v = vp_wrap(4, 3);
1011 assert_eq!(view_b.cursor_screen_row(&v), Some(0));
1012 let mut v2 = vp_wrap(4, 3);
1013 view_b.ensure_cursor_visible(&mut v2); }
1015
1016 #[test]
1017 fn ensure_cursor_visible_falls_back_when_wrap_disabled() {
1018 let mut b = View::from_str("a\nb\nc\nd\ne");
1019 let mut v = Viewport {
1020 top_row: 0,
1021 top_col: 0,
1022 width: 4,
1023 height: 2,
1024 wrap: crate::Wrap::None,
1025 text_width: 4,
1026 tab_width: 0,
1027 };
1028 b.set_cursor(Position::new(4, 0));
1029 b.ensure_cursor_visible(&mut v);
1030 assert_eq!(v.top_row, 3);
1031 }
1032
1033 #[test]
1039 fn undo_stack_shared_across_views() {
1040 use crate::UndoEntry;
1041 use std::time::SystemTime;
1042
1043 let a = View::from_str("hello");
1044 let arc = a.content_arc();
1045 let view_a = View::new_view(Arc::clone(&arc));
1046 let view_b = View::new_view(Arc::clone(&arc));
1047
1048 assert!(view_a.undo_stack_is_empty());
1049 assert_eq!(view_a.undo_stack_len(), 0);
1050
1051 view_a.push_undo_entry(UndoEntry {
1052 rope: view_a.rope(),
1053 cursor: (0, 0),
1054 timestamp: SystemTime::UNIX_EPOCH,
1055 marks: Default::default(),
1056 });
1057
1058 assert_eq!(view_b.undo_stack_len(), 1);
1060 assert!(!view_b.undo_stack_is_empty());
1061 }
1062
1063 #[test]
1068 fn redo_stack_shared_across_views() {
1069 use crate::UndoEntry;
1070 use std::time::SystemTime;
1071
1072 let a = View::from_str("world");
1073 let arc = a.content_arc();
1074 let view_a = View::new_view(Arc::clone(&arc));
1075 let view_b = View::new_view(Arc::clone(&arc));
1076
1077 assert!(view_a.redo_stack_is_empty());
1078
1079 view_b.push_undo_entry(UndoEntry {
1083 rope: view_b.rope(),
1084 cursor: (0, 0),
1085 timestamp: SystemTime::UNIX_EPOCH,
1086 marks: Default::default(),
1087 });
1088 view_b.undo_step(view_b.rope(), (0, 2), Default::default());
1089
1090 assert!(!view_a.redo_stack_is_empty());
1092 let entry = view_a.redo_step(view_a.rope(), (0, 0), Default::default());
1093 assert!(entry.is_some());
1094 assert_eq!(entry.unwrap().cursor, (0, 2));
1095 }
1096
1097 #[test]
1102 fn clear_undo_redo_shared_across_views() {
1103 use crate::UndoEntry;
1104 use std::time::SystemTime;
1105
1106 let a = View::from_str("abc");
1107 let arc = a.content_arc();
1108 let view_a = View::new_view(Arc::clone(&arc));
1109 let view_b = View::new_view(Arc::clone(&arc));
1110
1111 for _ in 0..2 {
1113 view_a.push_undo_entry(UndoEntry {
1114 rope: view_a.rope(),
1115 cursor: (0, 0),
1116 timestamp: SystemTime::UNIX_EPOCH,
1117 marks: Default::default(),
1118 });
1119 }
1120 view_a.undo_step(view_a.rope(), (0, 1), Default::default());
1121 assert!(!view_a.undo_stack_is_empty());
1122 assert!(!view_a.redo_stack_is_empty());
1123
1124 view_b.clear_undo_redo();
1125 assert!(view_a.undo_stack_is_empty());
1126 assert!(view_a.redo_stack_is_empty());
1127 }
1128
1129 #[test]
1131 fn content_dirty_shared_across_views() {
1132 let a = View::from_str("test");
1133 let arc = a.content_arc();
1134 let view_a = View::new_view(Arc::clone(&arc));
1135 let view_b = View::new_view(Arc::clone(&arc));
1136
1137 assert!(!view_a.content_dirty());
1138
1139 view_b.mark_content_dirty();
1140 assert!(view_a.content_dirty());
1141
1142 let taken = view_a.take_dirty();
1143 assert!(taken);
1144 assert!(!view_b.content_dirty());
1145 }
1146
1147 #[test]
1149 fn pending_fold_ops_shared_across_views() {
1150 let a = View::from_str("a\nb\nc");
1151 let arc = a.content_arc();
1152 let view_a = View::new_view(Arc::clone(&arc));
1153 let view_b = View::new_view(Arc::clone(&arc));
1154
1155 view_a.push_fold_op(crate::FoldOp::Add {
1156 start_row: 0,
1157 end_row: 1,
1158 closed: true,
1159 });
1160
1161 let ops = view_b.take_fold_ops();
1162 assert_eq!(ops.len(), 1);
1163 assert!(matches!(
1164 ops[0],
1165 crate::FoldOp::Add {
1166 start_row: 0,
1167 end_row: 1,
1168 closed: true
1169 }
1170 ));
1171 }
1172
1173 #[test]
1175 fn pending_content_reset_shared_across_views() {
1176 let a = View::from_str("x");
1177 let arc = a.content_arc();
1178 let view_a = View::new_view(Arc::clone(&arc));
1179 let view_b = View::new_view(Arc::clone(&arc));
1180
1181 assert!(!view_a.pending_content_reset());
1182 view_b.set_pending_content_reset(true);
1183 assert!(view_a.pending_content_reset());
1184 let taken = view_a.take_pending_content_reset();
1185 assert!(taken);
1186 assert!(!view_b.pending_content_reset());
1187 }
1188
1189 #[test]
1194 fn buffer_views_independent_cursors() {
1195 let a = View::from_str("hello\nworld");
1196 let arc = a.content_arc();
1197 let mut view_a = View::new_view(Arc::clone(&arc));
1198 let mut view_b = View::new_view(Arc::clone(&arc));
1199
1200 view_a.set_cursor(Position::new(1, 3));
1201 assert_eq!(view_b.cursor(), Position::new(0, 0));
1203
1204 view_b.set_cursor(Position::new(0, 2));
1205 assert_eq!(view_a.cursor(), Position::new(1, 3));
1207 }
1208
1209 #[test]
1213 fn last_cursor_reflects_most_recent_move_across_views() {
1214 let a = View::from_str("aaaa\nbbbb\ncccc\ndddd");
1215 let arc = a.content_arc();
1216 let mut view_a = View::new_view(Arc::clone(&arc));
1217 let mut view_b = View::new_view(Arc::clone(&arc));
1218
1219 view_a.set_cursor(Position::new(1, 2));
1220 assert_eq!(view_a.last_cursor(), (1, 2));
1221 assert_eq!(view_b.last_cursor(), (1, 2));
1223
1224 view_b.set_cursor(Position::new(3, 1));
1226 assert_eq!(view_a.last_cursor(), (3, 1));
1227 assert_eq!(view_b.last_cursor(), (3, 1));
1228
1229 view_a.set_cursor(Position::new(99, 99));
1231 assert_eq!(view_a.last_cursor(), (3, 4));
1232 }
1233
1234 #[test]
1238 fn clamp_position_restore_semantics() {
1239 let b = View::from_str("ab\ncdef\ng");
1240 assert_eq!(b.clamp_position(Position::new(99, 99)), Position::new(2, 1));
1242 assert_eq!(b.clamp_position(Position::new(1, 99)), Position::new(1, 4));
1244 assert_eq!(b.clamp_position(Position::new(1, 2)), Position::new(1, 2));
1246 }
1247
1248 #[test]
1250 fn buffer_views_share_content() {
1251 use crate::edit::Edit;
1252
1253 let a = View::from_str("foo");
1254 let arc = a.content_arc();
1255 let mut view_a = View::new_view(Arc::clone(&arc));
1256 let view_b = View::new_view(Arc::clone(&arc));
1257
1258 view_a.apply_edit(Edit::InsertStr {
1259 at: Position::new(0, 3),
1260 text: "bar".into(),
1261 });
1262
1263 assert_eq!(rope_line_str(&view_a.rope(), 0), "foobar");
1264 assert_eq!(rope_line_str(&view_b.rope(), 0), "foobar");
1265 }
1266}
1267
1268#[cfg(test)]
1269mod marks_shared_content_tests {
1270 use super::*;
1271
1272 #[test]
1273 fn marks_shared_across_views() {
1274 let a = View::from_str("hello\nworld");
1276 let content = a.content_arc();
1277 let mut view_a = View::new_view(std::sync::Arc::clone(&content));
1278 let view_b = View::new_view(std::sync::Arc::clone(&content));
1279
1280 view_a.set_mark('x', (1, 3));
1282
1283 assert_eq!(view_b.mark('x'), Some((1, 3)));
1285 }
1286
1287 #[test]
1288 fn syntax_fold_ranges_shared_across_views() {
1289 let a = View::from_str("fn foo() {\n bar();\n}");
1290 let content = a.content_arc();
1291 let mut view_a = View::new_view(std::sync::Arc::clone(&content));
1292 let view_b = View::new_view(std::sync::Arc::clone(&content));
1293
1294 view_a.set_syntax_fold_ranges(vec![(0, 2)]);
1295
1296 assert_eq!(view_b.syntax_fold_ranges_cloned(), vec![(0, 2)]);
1297 }
1298}