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