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_change_start(&self) -> (usize, usize) {
520 self.content.lock().unwrap().undo.current_cursor()
521 }
522
523 pub fn redo_step(
526 &self,
527 rope: ropey::Rope,
528 cursor: (usize, usize),
529 marks: crate::MarkSnapshot,
530 ) -> Option<crate::UndoEntry> {
531 self.content
532 .lock()
533 .unwrap()
534 .undo
535 .redo_step(rope, cursor, marks)
536 }
537
538 pub fn pop_committed(&self) -> bool {
542 self.content.lock().unwrap().undo.pop_committed()
543 }
544
545 pub fn seq_earlier_step(
550 &self,
551 rope: ropey::Rope,
552 cursor: (usize, usize),
553 marks: crate::MarkSnapshot,
554 ) -> Option<crate::UndoEntry> {
555 self.content
556 .lock()
557 .unwrap()
558 .undo
559 .seq_earlier_step(rope, cursor, marks)
560 }
561
562 pub fn seq_later_step(
565 &self,
566 rope: ropey::Rope,
567 cursor: (usize, usize),
568 marks: crate::MarkSnapshot,
569 ) -> Option<crate::UndoEntry> {
570 self.content
571 .lock()
572 .unwrap()
573 .undo
574 .seq_later_step(rope, cursor, marks)
575 }
576
577 pub fn seq_earlier_timestamp(&self) -> Option<std::time::SystemTime> {
579 self.content.lock().unwrap().undo.seq_earlier_timestamp()
580 }
581
582 pub fn seq_later_timestamp(&self) -> Option<std::time::SystemTime> {
584 self.content.lock().unwrap().undo.seq_later_timestamp()
585 }
586
587 pub fn undo_leaves(&self) -> Vec<(u64, usize, std::time::SystemTime, bool)> {
590 self.content.lock().unwrap().undo.leaves()
591 }
592
593 pub fn peek_undo_timestamp(&self) -> Option<std::time::SystemTime> {
594 self.content.lock().unwrap().undo.parent_timestamp()
595 }
596
597 pub fn peek_redo_timestamp(&self) -> Option<std::time::SystemTime> {
598 self.content.lock().unwrap().undo.child_timestamp()
599 }
600
601 pub fn clear_undo_redo(&self) {
602 self.content.lock().unwrap().undo.clear_all();
603 }
604
605 pub fn undo_to_serializable(&self) -> (crate::SerTree, u64) {
612 let mut c = self.content.lock().unwrap();
613 let rope = c.text.clone();
614 c.undo.sync_current(rope);
615 let seq = c.undo.current_node_seq();
616 (c.undo.to_serializable(), seq)
617 }
618
619 pub fn install_undo_tree(&self, ser: &crate::SerTree) -> bool {
625 match crate::UndoTree::from_serializable(ser) {
626 Some(tree) => {
627 self.content.lock().unwrap().undo = tree;
628 true
629 }
630 None => false,
631 }
632 }
633
634 pub fn install_recovered_undo_tree(&self, ser: &crate::SerTree, current_seq: u64) -> bool {
648 let Some(mut tree) = crate::UndoTree::from_serializable(ser) else {
649 return false;
650 };
651 if tree.current_node_seq() != current_seq {
652 return false;
653 }
654 let mut c = self.content.lock().unwrap();
655 let materialized = tree.current_content();
656 if materialized.to_string().trim_end_matches('\n')
657 != c.text.to_string().trim_end_matches('\n')
658 {
659 return false;
660 }
661 c.undo = tree;
662 true
663 }
664
665 pub fn clear_redo(&self) {
666 self.content.lock().unwrap().undo.clear_redo();
667 }
668
669 pub fn undo_group_active(&self) -> bool {
672 self.content.lock().unwrap().undo_group_active()
673 }
674
675 pub fn undo_group_arm(&self) -> bool {
677 self.content.lock().unwrap().undo_group_arm()
678 }
679
680 pub fn cap_undo(&self, cap: usize) {
681 self.content.lock().unwrap().undo.cap(cap);
682 }
683
684 pub fn content_dirty(&self) -> bool {
685 self.content.lock().unwrap().content_dirty
686 }
687
688 pub fn set_content_dirty(&self, v: bool) {
689 self.content.lock().unwrap().content_dirty = v;
690 }
691
692 pub fn mark_content_dirty(&self) {
693 let mut c = self.content.lock().unwrap();
694 c.content_dirty = true;
695 c.cached_editor_content = None;
696 }
697
698 pub fn take_dirty(&self) -> bool {
699 let mut c = self.content.lock().unwrap();
700 let v = c.content_dirty;
701 c.content_dirty = false;
702 v
703 }
704
705 pub fn cached_editor_content(&self) -> Option<std::sync::Arc<String>> {
706 self.content.lock().unwrap().cached_editor_content.clone()
707 }
708
709 pub fn set_cached_editor_content(&self, arc: std::sync::Arc<String>) {
710 self.content.lock().unwrap().cached_editor_content = Some(arc);
711 }
712
713 pub fn push_fold_op(&self, op: crate::FoldOp) {
714 self.content.lock().unwrap().pending_fold_ops.push(op);
715 }
716
717 pub fn take_fold_ops(&self) -> Vec<crate::FoldOp> {
718 std::mem::take(&mut self.content.lock().unwrap().pending_fold_ops)
719 }
720
721 pub fn extend_change_log(&self, edits: impl IntoIterator<Item = crate::EngineEdit>) {
728 let mut content = self.content.lock().unwrap();
729 if !content.change_log_enabled {
730 return;
731 }
732 content.change_log.extend(edits);
733 }
734
735 pub fn set_change_log_enabled(&self, enabled: bool) {
738 self.content.lock().unwrap().change_log_enabled = enabled;
739 }
740
741 pub fn change_log_enabled(&self) -> bool {
744 self.content.lock().unwrap().change_log_enabled
745 }
746
747 pub fn take_change_log(&self) -> Vec<crate::EngineEdit> {
748 std::mem::take(&mut self.content.lock().unwrap().change_log)
749 }
750
751 pub fn extend_pending_content_edits(
752 &self,
753 edits: impl IntoIterator<Item = crate::ContentEdit>,
754 ) {
755 self.content
756 .lock()
757 .unwrap()
758 .pending_content_edits
759 .extend(edits);
760 }
761
762 pub fn push_pending_content_edit(&self, edit: crate::ContentEdit) {
763 self.content
764 .lock()
765 .unwrap()
766 .pending_content_edits
767 .push(edit);
768 }
769
770 pub fn take_pending_content_edits(&self) -> Vec<crate::ContentEdit> {
771 std::mem::take(&mut self.content.lock().unwrap().pending_content_edits)
772 }
773
774 pub fn clear_pending_content_edits(&self) {
775 self.content.lock().unwrap().pending_content_edits.clear();
776 }
777
778 pub fn pending_content_reset(&self) -> bool {
779 self.content.lock().unwrap().pending_content_reset
780 }
781
782 pub fn set_pending_content_reset(&self, v: bool) {
783 self.content.lock().unwrap().pending_content_reset = v;
784 }
785
786 pub fn take_pending_content_reset(&self) -> bool {
787 let mut c = self.content.lock().unwrap();
788 let v = c.pending_content_reset;
789 c.pending_content_reset = false;
790 v
791 }
792
793 pub fn mark(&self, c: char) -> Option<(usize, usize)> {
794 self.content_lock().marks.get(&c).copied()
795 }
796 pub fn set_mark(&mut self, c: char, pos: (usize, usize)) {
797 self.content_lock_mut().marks.insert(c, pos);
798 }
799 pub fn clear_mark(&mut self, c: char) {
800 self.content_lock_mut().marks.remove(&c);
801 }
802 pub fn marks_cloned(&self) -> std::collections::BTreeMap<char, (usize, usize)> {
803 self.content_lock().marks.clone()
804 }
805 pub fn set_marks(&mut self, marks: std::collections::BTreeMap<char, (usize, usize)>) {
806 self.content_lock_mut().marks = marks;
807 }
808 pub fn rebase_marks(
812 &mut self,
813 edit_start: usize,
814 drop_end: usize,
815 shift_threshold: usize,
816 delta: isize,
817 ) {
818 let mut c = self.content_lock_mut();
819 let mut to_drop: Vec<char> = Vec::new();
820 for (ch, (row, _col)) in c.marks.iter_mut() {
821 if (edit_start..drop_end).contains(row) {
822 to_drop.push(*ch);
823 } else if *row >= shift_threshold {
824 *row = ((*row as isize) + delta).max(0) as usize;
825 }
826 }
827 for ch in to_drop {
828 c.marks.remove(&ch);
829 }
830 }
831 pub fn syntax_fold_ranges_cloned(&self) -> Vec<(usize, usize)> {
832 self.content_lock().syntax_fold_ranges.clone()
833 }
834 pub fn set_syntax_fold_ranges(&mut self, ranges: Vec<(usize, usize)>) {
835 self.content_lock_mut().syntax_fold_ranges = ranges;
836 }
837}
838
839pub fn floor_char_boundary(rope: &ropey::Rope, byte_idx: usize) -> usize {
852 let byte_idx = byte_idx.min(rope.len_bytes());
853 rope.char_to_byte(rope.byte_to_char(byte_idx))
854}
855
856fn rope_line_content_end(rope: &ropey::Rope, row: usize) -> usize {
868 if row + 1 >= rope.len_lines() {
869 return rope.len_bytes();
870 }
871 let step_back = rope.line_to_byte(row + 1).saturating_sub(1);
872 floor_char_boundary(rope, step_back)
873}
874
875pub fn rope_line_str(rope: &ropey::Rope, row: usize) -> String {
878 let start = rope.line_to_byte(row);
880 rope.byte_slice(start..rope_line_content_end(rope, row))
881 .to_string()
882}
883
884pub fn rope_line_bytes(rope: &ropey::Rope, row: usize) -> usize {
890 if row >= rope.len_lines() {
891 return 0;
892 }
893 rope_line_content_end(rope, row).saturating_sub(rope.line_to_byte(row))
894}
895
896pub fn rope_line_char_count(rope: &ropey::Rope, row: usize) -> usize {
901 if row >= rope.len_lines() {
902 return 0;
903 }
904 let end = rope_line_content_end(rope, row);
905 rope.byte_to_char(end)
906 .saturating_sub(rope.byte_to_char(rope.line_to_byte(row)))
907}
908
909pub fn pos_to_char_idx(rope: &ropey::Rope, row: usize, col: usize) -> usize {
914 let row = row.min(rope.len_lines().saturating_sub(1));
915 let line_start = rope.line_to_char(row);
916 let line_char_count = rope_line_char_count(rope, row);
917 line_start + col.min(line_char_count)
918}
919
920#[cfg(test)]
921mod tests {
922 use super::*;
923
924 #[test]
925 fn new_has_one_empty_row() {
926 let b = View::new();
927 assert_eq!(b.row_count(), 1);
928 assert_eq!(rope_line_str(&b.rope(), 0), "");
929 assert_eq!(b.cursor(), Position::default());
930 }
931
932 #[test]
933 fn from_str_splits_on_newline() {
934 let b = View::from_str("foo\nbar\nbaz");
935 assert_eq!(b.row_count(), 3);
936 assert_eq!(rope_line_str(&b.rope(), 0), "foo");
937 assert_eq!(rope_line_str(&b.rope(), 2), "baz");
938 }
939
940 #[test]
941 fn from_str_trailing_newline_keeps_empty_row() {
942 let b = View::from_str("foo\n");
943 assert_eq!(b.row_count(), 2);
944 assert_eq!(rope_line_str(&b.rope(), 1), "");
945 }
946
947 #[test]
953 fn line_helpers_strip_every_line_separator() {
954 for sep in ["\r", "\u{0b}", "\u{0c}", "\u{85}", "\u{2028}", "\u{2029}"] {
957 let rope = ropey::Rope::from_str(&format!("ab{sep}cd{sep}ef"));
958 assert_eq!(rope.len_lines(), 3, "separator {sep:?} must split rows");
959 for row in 0..3 {
960 let expect = ["ab", "cd", "ef"][row];
961 assert_eq!(
962 rope_line_str(&rope, row),
963 expect,
964 "rope_line_str kept separator {sep:?} in row {row}"
965 );
966 assert_eq!(
967 rope_line_bytes(&rope, row),
968 expect.len(),
969 "rope_line_bytes counted separator {sep:?} in row {row}"
970 );
971 assert_eq!(
972 rope_line_char_count(&rope, row),
973 expect.chars().count(),
974 "rope_line_char_count counted separator {sep:?} in row {row}"
975 );
976 }
977 }
978 }
979
980 #[test]
984 fn line_helpers_agree_with_each_other() {
985 let rope = ropey::Rope::from_str("ab\r\ncd\re\u{2028}f\u{85}\u{0c}g\nh");
986 for row in 0..rope.len_lines() {
987 let s = rope_line_str(&rope, row);
988 assert_eq!(
989 rope_line_bytes(&rope, row),
990 s.len(),
991 "bytes differ on {row}"
992 );
993 assert_eq!(
994 rope_line_char_count(&rope, row),
995 s.chars().count(),
996 "char count differs on row {row}"
997 );
998 }
999 }
1000
1001 #[test]
1002 fn from_str_empty_input_keeps_one_row() {
1003 let b = View::from_str("");
1004 assert_eq!(b.row_count(), 1);
1005 assert_eq!(rope_line_str(&b.rope(), 0), "");
1006 }
1007
1008 #[test]
1009 fn as_string_round_trips() {
1010 let b = View::from_str("a\nb\nc");
1011 assert_eq!(b.as_string(), "a\nb\nc");
1012 }
1013
1014 #[test]
1015 fn dirty_gen_starts_at_zero() {
1016 assert_eq!(View::new().dirty_gen(), 0);
1017 }
1018
1019 fn single_node_tree(base: &str, seq: u64) -> crate::SerTree {
1022 crate::SerTree {
1023 base: Some(base.to_string()),
1024 nodes: vec![crate::SerNode {
1025 parent: None,
1026 children: Vec::new(),
1027 last_child: None,
1028 delta: None,
1029 cursor: (0, 0),
1030 timestamp_unix_ms: 0,
1031 marks: crate::MarkSnapshot::default(),
1032 seq,
1033 }],
1034 root: 0,
1035 current: 0,
1036 next_seq: seq + 1,
1037 }
1038 }
1039
1040 #[test]
1044 fn install_recovered_undo_tree_guards_seq_and_content() {
1045 let v = View::from_str("alpha\nhello");
1047 assert!(v.install_recovered_undo_tree(&single_node_tree("alpha\nhello", 3), 3));
1048
1049 let v = View::from_str("alpha\nhello");
1051 assert!(!v.install_recovered_undo_tree(&single_node_tree("alpha\nhello", 3), 4));
1052
1053 let v = View::from_str("alpha\nhello");
1055 assert!(!v.install_recovered_undo_tree(&single_node_tree("something else", 3), 3));
1056
1057 let v = View::from_str("alpha\nhello");
1059 assert!(v.install_recovered_undo_tree(&single_node_tree("alpha\nhello\n", 3), 3));
1060 }
1061
1062 fn vp_wrap(width: u16, height: u16) -> Viewport {
1063 Viewport {
1064 top_row: 0,
1065 top_col: 0,
1066 width,
1067 height,
1068 wrap: crate::Wrap::Char,
1069 text_width: width,
1070 tab_width: 0,
1071 }
1072 }
1073
1074 #[test]
1075 fn ensure_cursor_visible_wrap_scrolls_when_cursor_below_screen() {
1076 let mut b = View::from_str("aaaaaaaaaa\nb\nc");
1077 let mut v = vp_wrap(4, 3);
1078 b.set_cursor(Position::new(2, 0));
1079 b.ensure_cursor_visible(&mut v);
1080 assert_eq!(v.top_row, 1);
1081 }
1082
1083 #[test]
1084 fn ensure_cursor_visible_wrap_no_scroll_when_visible() {
1085 let mut b = View::from_str("aaaaaaaaaa\nb");
1086 let mut v = vp_wrap(4, 4);
1087 b.set_cursor(Position::new(0, 5));
1088 b.ensure_cursor_visible(&mut v);
1089 assert_eq!(v.top_row, 0);
1090 }
1091
1092 #[test]
1093 fn ensure_cursor_visible_wrap_snaps_top_when_cursor_above() {
1094 let mut b = View::from_str("a\nb\nc\nd\ne");
1095 let mut v = vp_wrap(4, 2);
1096 v.top_row = 3;
1097 b.set_cursor(Position::new(1, 0));
1098 b.ensure_cursor_visible(&mut v);
1099 assert_eq!(v.top_row, 1);
1100 }
1101
1102 #[test]
1103 fn screen_rows_between_sums_segments_under_wrap() {
1104 let b = View::from_str("aaaaaaaaa\nb\n");
1105 let v = vp_wrap(4, 0);
1106 assert_eq!(b.screen_rows_between(&v, 0, 0), 3);
1107 assert_eq!(b.screen_rows_between(&v, 0, 1), 4);
1108 assert_eq!(b.screen_rows_between(&v, 0, 2), 5);
1109 assert_eq!(b.screen_rows_between(&v, 1, 2), 2);
1110 }
1111
1112 #[test]
1113 fn screen_rows_between_one_per_doc_row_when_wrap_off() {
1114 let b = View::from_str("aaaaa\nb\nc");
1115 let v = Viewport::default();
1116 assert_eq!(b.screen_rows_between(&v, 0, 2), 3);
1117 }
1118
1119 #[test]
1120 fn max_top_for_height_walks_back_until_height_reached() {
1121 let b = View::from_str("a\nb\nc\nd\neeeeeeee");
1122 let v = vp_wrap(4, 0);
1123 assert_eq!(b.max_top_for_height(&v, 4), 2);
1124 assert_eq!(b.max_top_for_height(&v, 99), 0);
1125 }
1126
1127 #[test]
1128 fn cursor_screen_row_returns_none_when_wrap_off() {
1129 let b = View::from_str("a");
1130 let v = Viewport::default();
1131 assert!(b.cursor_screen_row(&v).is_none());
1132 }
1133
1134 #[test]
1135 fn cursor_screen_row_under_wrap() {
1136 let mut b = View::from_str("aaaaaaaaaa\nb");
1137 let v = vp_wrap(4, 0);
1138 b.set_cursor(Position::new(0, 5));
1139 assert_eq!(b.cursor_screen_row(&v), Some(1));
1140 b.set_cursor(Position::new(1, 0));
1141 assert_eq!(b.cursor_screen_row(&v), Some(3));
1142 }
1143
1144 #[test]
1148 fn cursor_screen_row_survives_shrink_from_other_view() {
1149 let a = View::from_str("a\nb\nc\nd\ne");
1150 let arc = a.content_arc();
1151 let mut view_a = View::new_view(Arc::clone(&arc));
1152 let mut view_b = View::new_view(Arc::clone(&arc));
1153 view_b.set_cursor(Position::new(4, 0));
1154 view_a.replace_all("a");
1156 let v = vp_wrap(4, 3);
1157 assert_eq!(view_b.cursor_screen_row(&v), Some(0));
1158 let mut v2 = vp_wrap(4, 3);
1159 view_b.ensure_cursor_visible(&mut v2); }
1161
1162 #[test]
1167 fn ensure_cursor_visible_clamps_stale_top_row_after_shrink() {
1168 let a = View::from_str("a\nb\nc\nd\ne");
1169 let arc = a.content_arc();
1170 let mut view_a = View::new_view(Arc::clone(&arc));
1171 let mut view_b = View::new_view(Arc::clone(&arc));
1172 view_b.set_cursor(Position::new(4, 0));
1173 view_a.replace_all("a");
1174 let mut v = vp_wrap(4, 3);
1175 v.top_row = 3;
1178 view_b.ensure_cursor_visible(&mut v);
1179 assert_eq!(v.top_row, 0, "top_row must clamp into the live rope");
1180 assert_eq!(v.top_col, 0);
1181 }
1182
1183 #[test]
1190 fn ensure_cursor_visible_survives_stale_cursor_row_after_shrink() {
1191 let a = View::from_str(
1193 "aa\nbb\nxxxxxxxxxxxxxxxxxxxxxxxxxxxx\ncc\ndd\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
1194 );
1195 let arc = a.content_arc();
1196 let mut view_a = View::new_view(Arc::clone(&arc));
1197 let mut view_b = View::new_view(Arc::clone(&arc));
1198 view_b.set_cursor(Position::new(5, 26));
1199 view_a.replace_all("aa\nbb\nxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
1202 let mut v = vp_wrap(4, 6);
1203 view_b.ensure_cursor_visible(&mut v); assert_eq!(
1205 v.top_row, 2,
1206 "top_row must scroll to the clamped cursor row, not past the rope"
1207 );
1208 assert_eq!(v.top_col, 0);
1209 }
1210
1211 #[test]
1215 fn ensure_cursor_visible_snaps_when_cursor_row_folded() {
1216 let mut b = View::from_str("a\nb\nc\nd\ne");
1217 b.set_folds(&[crate::Fold {
1218 start_row: 2,
1219 end_row: 4,
1220 closed: true,
1221 auto_generated: false,
1222 }]);
1223 let mut v = vp_wrap(4, 2);
1224 v.top_row = 1;
1225 b.set_cursor(Position::new(3, 0));
1226 b.ensure_cursor_visible(&mut v);
1227 assert_eq!(v.top_row, 3);
1228 }
1229
1230 #[test]
1231 fn ensure_cursor_visible_falls_back_when_wrap_disabled() {
1232 let mut b = View::from_str("a\nb\nc\nd\ne");
1233 let mut v = Viewport {
1234 top_row: 0,
1235 top_col: 0,
1236 width: 4,
1237 height: 2,
1238 wrap: crate::Wrap::None,
1239 text_width: 4,
1240 tab_width: 0,
1241 };
1242 b.set_cursor(Position::new(4, 0));
1243 b.ensure_cursor_visible(&mut v);
1244 assert_eq!(v.top_row, 3);
1245 }
1246
1247 #[test]
1253 fn undo_stack_shared_across_views() {
1254 use crate::UndoEntry;
1255 use std::time::SystemTime;
1256
1257 let a = View::from_str("hello");
1258 let arc = a.content_arc();
1259 let view_a = View::new_view(Arc::clone(&arc));
1260 let view_b = View::new_view(Arc::clone(&arc));
1261
1262 assert!(view_a.undo_stack_is_empty());
1263 assert_eq!(view_a.undo_stack_len(), 0);
1264
1265 view_a.push_undo_entry(UndoEntry {
1266 rope: view_a.rope(),
1267 cursor: (0, 0),
1268 timestamp: SystemTime::UNIX_EPOCH,
1269 marks: Default::default(),
1270 });
1271
1272 assert_eq!(view_b.undo_stack_len(), 1);
1274 assert!(!view_b.undo_stack_is_empty());
1275 }
1276
1277 #[test]
1282 fn redo_stack_shared_across_views() {
1283 use crate::UndoEntry;
1284 use std::time::SystemTime;
1285
1286 let a = View::from_str("world");
1287 let arc = a.content_arc();
1288 let view_a = View::new_view(Arc::clone(&arc));
1289 let view_b = View::new_view(Arc::clone(&arc));
1290
1291 assert!(view_a.redo_stack_is_empty());
1292
1293 view_b.push_undo_entry(UndoEntry {
1297 rope: view_b.rope(),
1298 cursor: (0, 0),
1299 timestamp: SystemTime::UNIX_EPOCH,
1300 marks: Default::default(),
1301 });
1302 view_b.undo_step(view_b.rope(), (0, 2), Default::default());
1303
1304 assert!(!view_a.redo_stack_is_empty());
1306 let entry = view_a.redo_step(view_a.rope(), (0, 0), Default::default());
1307 assert!(entry.is_some());
1308 assert_eq!(entry.unwrap().cursor, (0, 2));
1309 }
1310
1311 #[test]
1316 fn clear_undo_redo_shared_across_views() {
1317 use crate::UndoEntry;
1318 use std::time::SystemTime;
1319
1320 let a = View::from_str("abc");
1321 let arc = a.content_arc();
1322 let view_a = View::new_view(Arc::clone(&arc));
1323 let view_b = View::new_view(Arc::clone(&arc));
1324
1325 for _ in 0..2 {
1327 view_a.push_undo_entry(UndoEntry {
1328 rope: view_a.rope(),
1329 cursor: (0, 0),
1330 timestamp: SystemTime::UNIX_EPOCH,
1331 marks: Default::default(),
1332 });
1333 }
1334 view_a.undo_step(view_a.rope(), (0, 1), Default::default());
1335 assert!(!view_a.undo_stack_is_empty());
1336 assert!(!view_a.redo_stack_is_empty());
1337
1338 view_b.clear_undo_redo();
1339 assert!(view_a.undo_stack_is_empty());
1340 assert!(view_a.redo_stack_is_empty());
1341 }
1342
1343 #[test]
1345 fn content_dirty_shared_across_views() {
1346 let a = View::from_str("test");
1347 let arc = a.content_arc();
1348 let view_a = View::new_view(Arc::clone(&arc));
1349 let view_b = View::new_view(Arc::clone(&arc));
1350
1351 assert!(!view_a.content_dirty());
1352
1353 view_b.mark_content_dirty();
1354 assert!(view_a.content_dirty());
1355
1356 let taken = view_a.take_dirty();
1357 assert!(taken);
1358 assert!(!view_b.content_dirty());
1359 }
1360
1361 #[test]
1363 fn pending_fold_ops_shared_across_views() {
1364 let a = View::from_str("a\nb\nc");
1365 let arc = a.content_arc();
1366 let view_a = View::new_view(Arc::clone(&arc));
1367 let view_b = View::new_view(Arc::clone(&arc));
1368
1369 view_a.push_fold_op(crate::FoldOp::Add {
1370 start_row: 0,
1371 end_row: 1,
1372 closed: true,
1373 });
1374
1375 let ops = view_b.take_fold_ops();
1376 assert_eq!(ops.len(), 1);
1377 assert!(matches!(
1378 ops[0],
1379 crate::FoldOp::Add {
1380 start_row: 0,
1381 end_row: 1,
1382 closed: true
1383 }
1384 ));
1385 }
1386
1387 #[test]
1389 fn pending_content_reset_shared_across_views() {
1390 let a = View::from_str("x");
1391 let arc = a.content_arc();
1392 let view_a = View::new_view(Arc::clone(&arc));
1393 let view_b = View::new_view(Arc::clone(&arc));
1394
1395 assert!(!view_a.pending_content_reset());
1396 view_b.set_pending_content_reset(true);
1397 assert!(view_a.pending_content_reset());
1398 let taken = view_a.take_pending_content_reset();
1399 assert!(taken);
1400 assert!(!view_b.pending_content_reset());
1401 }
1402
1403 #[test]
1408 fn buffer_views_independent_cursors() {
1409 let a = View::from_str("hello\nworld");
1410 let arc = a.content_arc();
1411 let mut view_a = View::new_view(Arc::clone(&arc));
1412 let mut view_b = View::new_view(Arc::clone(&arc));
1413
1414 view_a.set_cursor(Position::new(1, 3));
1415 assert_eq!(view_b.cursor(), Position::new(0, 0));
1417
1418 view_b.set_cursor(Position::new(0, 2));
1419 assert_eq!(view_a.cursor(), Position::new(1, 3));
1421 }
1422
1423 #[test]
1427 fn last_cursor_reflects_most_recent_move_across_views() {
1428 let a = View::from_str("aaaa\nbbbb\ncccc\ndddd");
1429 let arc = a.content_arc();
1430 let mut view_a = View::new_view(Arc::clone(&arc));
1431 let mut view_b = View::new_view(Arc::clone(&arc));
1432
1433 view_a.set_cursor(Position::new(1, 2));
1434 assert_eq!(view_a.last_cursor(), (1, 2));
1435 assert_eq!(view_b.last_cursor(), (1, 2));
1437
1438 view_b.set_cursor(Position::new(3, 1));
1440 assert_eq!(view_a.last_cursor(), (3, 1));
1441 assert_eq!(view_b.last_cursor(), (3, 1));
1442
1443 view_a.set_cursor(Position::new(99, 99));
1445 assert_eq!(view_a.last_cursor(), (3, 4));
1446 }
1447
1448 #[test]
1452 fn clamp_position_restore_semantics() {
1453 let b = View::from_str("ab\ncdef\ng");
1454 assert_eq!(b.clamp_position(Position::new(99, 99)), Position::new(2, 1));
1456 assert_eq!(b.clamp_position(Position::new(1, 99)), Position::new(1, 4));
1458 assert_eq!(b.clamp_position(Position::new(1, 2)), Position::new(1, 2));
1460 }
1461
1462 #[test]
1464 fn buffer_views_share_content() {
1465 use crate::edit::Edit;
1466
1467 let a = View::from_str("foo");
1468 let arc = a.content_arc();
1469 let mut view_a = View::new_view(Arc::clone(&arc));
1470 let view_b = View::new_view(Arc::clone(&arc));
1471
1472 view_a.apply_edit(Edit::InsertStr {
1473 at: Position::new(0, 3),
1474 text: "bar".into(),
1475 });
1476
1477 assert_eq!(rope_line_str(&view_a.rope(), 0), "foobar");
1478 assert_eq!(rope_line_str(&view_b.rope(), 0), "foobar");
1479 }
1480
1481 #[test]
1486 fn line_measures_answer_zero_past_the_last_row() {
1487 let rope = ropey::Rope::from_str("café\nx");
1488 assert_eq!(rope.len_lines(), 2);
1489
1490 assert_eq!(rope_line_bytes(&rope, 0), 5);
1492 assert_eq!(rope_line_char_count(&rope, 0), 4);
1493
1494 assert_eq!(rope_line_bytes(&rope, 1), 1);
1496 assert_eq!(rope_line_char_count(&rope, 1), 1);
1497
1498 assert_eq!(rope_line_bytes(&rope, 2), 0);
1500 assert_eq!(rope_line_char_count(&rope, 2), 0);
1501 assert_eq!(rope_line_bytes(&rope, usize::MAX), 0);
1502 assert_eq!(rope_line_char_count(&rope, usize::MAX), 0);
1503 }
1504}
1505
1506#[cfg(test)]
1507mod marks_shared_content_tests {
1508 use super::*;
1509
1510 #[test]
1511 fn marks_shared_across_views() {
1512 let a = View::from_str("hello\nworld");
1514 let content = a.content_arc();
1515 let mut view_a = View::new_view(std::sync::Arc::clone(&content));
1516 let view_b = View::new_view(std::sync::Arc::clone(&content));
1517
1518 view_a.set_mark('x', (1, 3));
1520
1521 assert_eq!(view_b.mark('x'), Some((1, 3)));
1523 }
1524
1525 #[test]
1526 fn syntax_fold_ranges_shared_across_views() {
1527 let a = View::from_str("fn foo() {\n bar();\n}");
1528 let content = a.content_arc();
1529 let mut view_a = View::new_view(std::sync::Arc::clone(&content));
1530 let view_b = View::new_view(std::sync::Arc::clone(&content));
1531
1532 view_a.set_syntax_fold_ranges(vec![(0, 2)]);
1533
1534 assert_eq!(view_b.syntax_fold_ranges_cloned(), vec![(0, 2)]);
1535 }
1536}