1use std::sync::{Arc, Mutex, MutexGuard};
2
3use crate::content::Buffer;
4use crate::{Position, Viewport};
5
6pub struct View {
61 pub(crate) content: Arc<Mutex<Buffer>>,
63 cursor: Position,
66}
67
68impl Default for View {
69 fn default() -> Self {
70 Self::new()
71 }
72}
73
74impl View {
75 pub fn new() -> Self {
79 Self {
80 content: Arc::new(Mutex::new(Buffer::new())),
81 cursor: Position::default(),
82 }
83 }
84
85 #[allow(clippy::should_implement_trait)]
90 pub fn from_str(text: &str) -> Self {
91 Self {
92 content: Arc::new(Mutex::new(Buffer::from_str(text))),
93 cursor: Position::default(),
94 }
95 }
96
97 pub fn new_view(content: Arc<Mutex<Buffer>>) -> Self {
117 Self {
118 content,
119 cursor: Position::default(),
120 }
121 }
122
123 pub fn content_arc(&self) -> Arc<Mutex<Buffer>> {
126 Arc::clone(&self.content)
127 }
128
129 pub fn cursor(&self) -> Position {
132 self.cursor
133 }
134
135 pub fn last_cursor(&self) -> (usize, usize) {
140 self.content.lock().unwrap().last_cursor
141 }
142
143 pub fn dirty_gen(&self) -> u64 {
144 self.content.lock().unwrap().dirty_gen
145 }
146
147 pub fn row_count(&self) -> usize {
149 self.content.lock().unwrap().text.len_lines()
150 }
151
152 pub fn as_string(&self) -> String {
157 self.content.lock().unwrap().text.to_string()
158 }
159
160 pub fn set_cursor(&mut self, pos: Position) {
167 let mut c = self.content.lock().unwrap();
168 let n = c.text.len_lines();
169 let last_row = n.saturating_sub(1);
170 let row = pos.row.min(last_row);
171 let line_chars = rope_line_char_count(&c.text, row);
172 let col = pos.col.min(line_chars);
173 c.last_cursor = (row, col);
177 drop(c);
178 self.cursor = Position::new(row, col);
179 }
180
181 pub fn ensure_cursor_visible(&mut self, viewport: &mut Viewport) {
184 let cursor = self.cursor;
185 let v = *viewport;
186 let wrap_active = !matches!(v.wrap, crate::Wrap::None) && v.text_width > 0;
187 if !wrap_active {
188 viewport.ensure_visible(cursor);
189 return;
190 }
191 if v.height == 0 {
192 return;
193 }
194 if cursor.row < v.top_row {
196 viewport.top_row = cursor.row;
197 viewport.top_col = 0;
198 return;
199 }
200 let height = v.height as usize;
201 let cursor_row = {
212 let c = self.content.lock().unwrap();
213 cursor.row.min(c.text.len_lines().saturating_sub(1))
214 };
215 let Some(mut screen) = self.cursor_screen_row_from(viewport, viewport.top_row) else {
216 let last_row = {
227 let c = self.content.lock().unwrap();
228 c.text.len_lines().saturating_sub(1)
229 };
230 viewport.top_row = cursor.row.min(last_row);
231 viewport.top_col = 0;
232 return;
233 };
234 while screen >= height {
235 let c = self.content.lock().unwrap();
236 let mut next = viewport.top_row + 1;
237 while next <= cursor_row && c.folds.iter().any(|f| f.hides(next)) {
238 next += 1;
239 }
240 if next > cursor_row {
241 drop(c);
242 viewport.top_row = cursor_row;
243 break;
244 }
245 for r in viewport.top_row..next {
249 if c.folds.iter().any(|f| f.hides(r)) {
250 continue;
251 }
252 let line = rope_line_str(&c.text, r);
253 screen -= crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len();
254 }
255 drop(c);
256 viewport.top_row = next;
257 }
258 viewport.top_col = 0;
259 }
260
261 pub fn cursor_screen_row(&self, viewport: &Viewport) -> Option<usize> {
263 if matches!(viewport.wrap, crate::Wrap::None) || viewport.text_width == 0 {
264 return None;
265 }
266 self.cursor_screen_row_from(viewport, viewport.top_row)
267 }
268
269 pub fn screen_rows_between(&self, viewport: &Viewport, start: usize, end: usize) -> usize {
271 if start > end {
272 return 0;
273 }
274 let c = self.content.lock().unwrap();
275 let n = c.text.len_lines();
276 let last = n.saturating_sub(1);
277 let end = end.min(last);
278 let v = *viewport;
279 let mut total = 0usize;
280 for r in start..=end {
281 if c.folds.iter().any(|f| f.hides(r)) {
282 continue;
283 }
284 if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
285 total += 1;
286 } else {
287 let line = rope_line_str(&c.text, r);
288 total += crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len();
289 }
290 }
291 total
292 }
293
294 pub fn max_top_for_height(&self, viewport: &Viewport, height: usize) -> usize {
297 if height == 0 {
298 return 0;
299 }
300 let c = self.content.lock().unwrap();
301 let n = c.text.len_lines();
302 let last = n.saturating_sub(1);
303 let mut total = 0usize;
304 let mut row = last;
305 loop {
306 if !c.folds.iter().any(|f| f.hides(row)) {
307 let v = *viewport;
308 total += if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
309 1
310 } else {
311 let line = rope_line_str(&c.text, row);
312 crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len()
313 };
314 }
315 if total >= height {
316 return row;
317 }
318 if row == 0 {
319 return 0;
320 }
321 row -= 1;
322 }
323 }
324
325 pub fn clamp_position(&self, pos: Position) -> Position {
327 let c = self.content.lock().unwrap();
328 let n = c.text.len_lines();
329 let last_row = n.saturating_sub(1);
330 let row = pos.row.min(last_row);
331 let line_chars = rope_line_char_count(&c.text, row);
332 let col = pos.col.min(line_chars);
333 Position::new(row, col)
334 }
335
336 pub fn replace_all(&mut self, text: &str) {
339 let new_cursor = {
340 let mut c = self.content.lock().unwrap();
341 c.text = ropey::Rope::from_str(text);
342 let n = c.text.len_lines();
343 let last_row = n.saturating_sub(1);
344 let row = self.cursor.row.min(last_row);
345 let line_chars = rope_line_char_count(&c.text, row);
346 let col = self.cursor.col.min(line_chars);
347 c.dirty_gen = c.dirty_gen.wrapping_add(1);
348 c.cached_joined = None;
349 c.cached_byte_len = None;
350 Position::new(row, col)
351 };
352 self.cursor = new_cursor;
353 }
354
355 pub(crate) fn fold_gen_bump(&mut self) {
363 let mut c = self.content.lock().unwrap();
364 c.fold_gen = c.fold_gen.wrapping_add(1);
365 }
366
367 pub(crate) fn dirty_gen_bump(&mut self) {
369 let mut c = self.content.lock().unwrap();
370 c.dirty_gen = c.dirty_gen.wrapping_add(1);
371 c.cached_joined = None;
372 c.cached_byte_len = None;
373 }
374
375 pub fn byte_len(&self) -> usize {
381 let mut c = self.content.lock().unwrap();
382 let dg = c.dirty_gen;
383 if let Some((cached_dg, len)) = c.cached_byte_len
384 && cached_dg == dg
385 {
386 return len;
387 }
388 let total = c.text.len_bytes();
389 c.cached_byte_len = Some((dg, total));
390 total
391 }
392
393 pub fn content_joined(&self) -> std::sync::Arc<String> {
402 let mut c = self.content.lock().unwrap();
403 let dg = c.dirty_gen;
404 if let Some((cached_dg, ref s)) = c.cached_joined
405 && cached_dg == dg
406 {
407 return std::sync::Arc::clone(s);
408 }
409 let joined = std::sync::Arc::new(c.text.to_string());
410 c.cached_joined = Some((dg, std::sync::Arc::clone(&joined)));
411 joined
412 }
413
414 pub fn rope(&self) -> ropey::Rope {
423 self.content.lock().unwrap().text.clone()
424 }
425
426 pub(crate) fn content_lock(&self) -> MutexGuard<'_, Buffer> {
428 self.content.lock().unwrap()
429 }
430
431 pub(crate) fn content_lock_mut(&mut self) -> MutexGuard<'_, Buffer> {
433 self.content.lock().unwrap()
434 }
435
436 fn cursor_screen_row_from(&self, viewport: &Viewport, top: usize) -> Option<usize> {
439 let cursor = self.cursor;
440 if cursor.row < top {
441 return None;
442 }
443 let c = self.content.lock().unwrap();
444 let cursor_row = cursor.row.min(c.text.len_lines().saturating_sub(1));
448 if cursor_row < top {
449 return None;
450 }
451 let v = *viewport;
452 let mut screen = 0usize;
453 for r in top..=cursor_row {
454 if c.folds.iter().any(|f| f.hides(r)) {
455 continue;
456 }
457 let line = rope_line_str(&c.text, r);
458 let segs = crate::wrap::wrap_segments(&line, v.text_width, v.wrap);
459 if r == cursor_row {
460 let seg_idx = crate::wrap::segment_for_col(&segs, cursor.col);
461 return Some(screen + seg_idx);
462 }
463 screen += segs.len();
464 }
465 None
466 }
467
468 pub fn undo_stack_is_empty(&self) -> bool {
478 self.content.lock().unwrap().undo.is_at_root()
479 }
480
481 pub fn redo_stack_is_empty(&self) -> bool {
482 !self.content.lock().unwrap().undo.has_redo()
483 }
484
485 pub fn undo_stack_len(&self) -> usize {
486 self.content.lock().unwrap().undo.depth_from_root()
487 }
488
489 pub fn push_undo_entry(&self, entry: crate::UndoEntry) {
493 self.content.lock().unwrap().undo.push(entry);
494 }
495
496 pub fn undo_step(
500 &self,
501 rope: ropey::Rope,
502 cursor: (usize, usize),
503 marks: crate::MarkSnapshot,
504 ) -> Option<crate::UndoEntry> {
505 self.content
506 .lock()
507 .unwrap()
508 .undo
509 .undo_step(rope, cursor, marks)
510 }
511
512 pub fn redo_step(
515 &self,
516 rope: ropey::Rope,
517 cursor: (usize, usize),
518 marks: crate::MarkSnapshot,
519 ) -> Option<crate::UndoEntry> {
520 self.content
521 .lock()
522 .unwrap()
523 .undo
524 .redo_step(rope, cursor, marks)
525 }
526
527 pub fn pop_committed(&self) -> bool {
531 self.content.lock().unwrap().undo.pop_committed()
532 }
533
534 pub fn seq_earlier_step(
539 &self,
540 rope: ropey::Rope,
541 cursor: (usize, usize),
542 marks: crate::MarkSnapshot,
543 ) -> Option<crate::UndoEntry> {
544 self.content
545 .lock()
546 .unwrap()
547 .undo
548 .seq_earlier_step(rope, cursor, marks)
549 }
550
551 pub fn seq_later_step(
554 &self,
555 rope: ropey::Rope,
556 cursor: (usize, usize),
557 marks: crate::MarkSnapshot,
558 ) -> Option<crate::UndoEntry> {
559 self.content
560 .lock()
561 .unwrap()
562 .undo
563 .seq_later_step(rope, cursor, marks)
564 }
565
566 pub fn seq_earlier_timestamp(&self) -> Option<std::time::SystemTime> {
568 self.content.lock().unwrap().undo.seq_earlier_timestamp()
569 }
570
571 pub fn seq_later_timestamp(&self) -> Option<std::time::SystemTime> {
573 self.content.lock().unwrap().undo.seq_later_timestamp()
574 }
575
576 pub fn undo_leaves(&self) -> Vec<(u64, usize, std::time::SystemTime, bool)> {
579 self.content.lock().unwrap().undo.leaves()
580 }
581
582 pub fn peek_undo_timestamp(&self) -> Option<std::time::SystemTime> {
583 self.content.lock().unwrap().undo.parent_timestamp()
584 }
585
586 pub fn peek_redo_timestamp(&self) -> Option<std::time::SystemTime> {
587 self.content.lock().unwrap().undo.child_timestamp()
588 }
589
590 pub fn clear_undo_redo(&self) {
591 self.content.lock().unwrap().undo.clear_all();
592 }
593
594 pub fn undo_to_serializable(&self) -> (crate::SerTree, u64) {
601 let mut c = self.content.lock().unwrap();
602 let rope = c.text.clone();
603 c.undo.sync_current(rope);
604 let seq = c.undo.current_node_seq();
605 (c.undo.to_serializable(), seq)
606 }
607
608 pub fn install_undo_tree(&self, ser: &crate::SerTree) -> bool {
614 match crate::UndoTree::from_serializable(ser) {
615 Some(tree) => {
616 self.content.lock().unwrap().undo = tree;
617 true
618 }
619 None => false,
620 }
621 }
622
623 pub fn install_recovered_undo_tree(&self, ser: &crate::SerTree, current_seq: u64) -> bool {
637 let Some(mut tree) = crate::UndoTree::from_serializable(ser) else {
638 return false;
639 };
640 if tree.current_node_seq() != current_seq {
641 return false;
642 }
643 let mut c = self.content.lock().unwrap();
644 let materialized = tree.current_content();
645 if materialized.to_string().trim_end_matches('\n')
646 != c.text.to_string().trim_end_matches('\n')
647 {
648 return false;
649 }
650 c.undo = tree;
651 true
652 }
653
654 pub fn clear_redo(&self) {
655 self.content.lock().unwrap().undo.clear_redo();
656 }
657
658 pub fn undo_group_active(&self) -> bool {
661 self.content.lock().unwrap().undo_group_active()
662 }
663
664 pub fn undo_group_arm(&self) -> bool {
666 self.content.lock().unwrap().undo_group_arm()
667 }
668
669 pub fn cap_undo(&self, cap: usize) {
670 self.content.lock().unwrap().undo.cap(cap);
671 }
672
673 pub fn content_dirty(&self) -> bool {
674 self.content.lock().unwrap().content_dirty
675 }
676
677 pub fn set_content_dirty(&self, v: bool) {
678 self.content.lock().unwrap().content_dirty = v;
679 }
680
681 pub fn mark_content_dirty(&self) {
682 let mut c = self.content.lock().unwrap();
683 c.content_dirty = true;
684 c.cached_editor_content = None;
685 }
686
687 pub fn take_dirty(&self) -> bool {
688 let mut c = self.content.lock().unwrap();
689 let v = c.content_dirty;
690 c.content_dirty = false;
691 v
692 }
693
694 pub fn cached_editor_content(&self) -> Option<std::sync::Arc<String>> {
695 self.content.lock().unwrap().cached_editor_content.clone()
696 }
697
698 pub fn set_cached_editor_content(&self, arc: std::sync::Arc<String>) {
699 self.content.lock().unwrap().cached_editor_content = Some(arc);
700 }
701
702 pub fn push_fold_op(&self, op: crate::FoldOp) {
703 self.content.lock().unwrap().pending_fold_ops.push(op);
704 }
705
706 pub fn take_fold_ops(&self) -> Vec<crate::FoldOp> {
707 std::mem::take(&mut self.content.lock().unwrap().pending_fold_ops)
708 }
709
710 pub fn extend_change_log(&self, edits: impl IntoIterator<Item = crate::EngineEdit>) {
711 self.content.lock().unwrap().change_log.extend(edits);
712 }
713
714 pub fn take_change_log(&self) -> Vec<crate::EngineEdit> {
715 std::mem::take(&mut self.content.lock().unwrap().change_log)
716 }
717
718 pub fn extend_pending_content_edits(
719 &self,
720 edits: impl IntoIterator<Item = crate::ContentEdit>,
721 ) {
722 self.content
723 .lock()
724 .unwrap()
725 .pending_content_edits
726 .extend(edits);
727 }
728
729 pub fn push_pending_content_edit(&self, edit: crate::ContentEdit) {
730 self.content
731 .lock()
732 .unwrap()
733 .pending_content_edits
734 .push(edit);
735 }
736
737 pub fn take_pending_content_edits(&self) -> Vec<crate::ContentEdit> {
738 std::mem::take(&mut self.content.lock().unwrap().pending_content_edits)
739 }
740
741 pub fn clear_pending_content_edits(&self) {
742 self.content.lock().unwrap().pending_content_edits.clear();
743 }
744
745 pub fn pending_content_reset(&self) -> bool {
746 self.content.lock().unwrap().pending_content_reset
747 }
748
749 pub fn set_pending_content_reset(&self, v: bool) {
750 self.content.lock().unwrap().pending_content_reset = v;
751 }
752
753 pub fn take_pending_content_reset(&self) -> bool {
754 let mut c = self.content.lock().unwrap();
755 let v = c.pending_content_reset;
756 c.pending_content_reset = false;
757 v
758 }
759
760 pub fn mark(&self, c: char) -> Option<(usize, usize)> {
761 self.content_lock().marks.get(&c).copied()
762 }
763 pub fn set_mark(&mut self, c: char, pos: (usize, usize)) {
764 self.content_lock_mut().marks.insert(c, pos);
765 }
766 pub fn clear_mark(&mut self, c: char) {
767 self.content_lock_mut().marks.remove(&c);
768 }
769 pub fn marks_cloned(&self) -> std::collections::BTreeMap<char, (usize, usize)> {
770 self.content_lock().marks.clone()
771 }
772 pub fn set_marks(&mut self, marks: std::collections::BTreeMap<char, (usize, usize)>) {
773 self.content_lock_mut().marks = marks;
774 }
775 pub fn rebase_marks(
779 &mut self,
780 edit_start: usize,
781 drop_end: usize,
782 shift_threshold: usize,
783 delta: isize,
784 ) {
785 let mut c = self.content_lock_mut();
786 let mut to_drop: Vec<char> = Vec::new();
787 for (ch, (row, _col)) in c.marks.iter_mut() {
788 if (edit_start..drop_end).contains(row) {
789 to_drop.push(*ch);
790 } else if *row >= shift_threshold {
791 *row = ((*row as isize) + delta).max(0) as usize;
792 }
793 }
794 for ch in to_drop {
795 c.marks.remove(&ch);
796 }
797 }
798 pub fn syntax_fold_ranges_cloned(&self) -> Vec<(usize, usize)> {
799 self.content_lock().syntax_fold_ranges.clone()
800 }
801 pub fn set_syntax_fold_ranges(&mut self, ranges: Vec<(usize, usize)>) {
802 self.content_lock_mut().syntax_fold_ranges = ranges;
803 }
804}
805
806pub fn floor_char_boundary(rope: &ropey::Rope, byte_idx: usize) -> usize {
819 let byte_idx = byte_idx.min(rope.len_bytes());
820 rope.char_to_byte(rope.byte_to_char(byte_idx))
821}
822
823fn rope_line_content_end(rope: &ropey::Rope, row: usize) -> usize {
835 if row + 1 >= rope.len_lines() {
836 return rope.len_bytes();
837 }
838 let step_back = rope.line_to_byte(row + 1).saturating_sub(1);
839 floor_char_boundary(rope, step_back)
840}
841
842pub fn rope_line_str(rope: &ropey::Rope, row: usize) -> String {
845 let start = rope.line_to_byte(row);
847 rope.byte_slice(start..rope_line_content_end(rope, row))
848 .to_string()
849}
850
851pub fn rope_line_bytes(rope: &ropey::Rope, row: usize) -> usize {
857 if row >= rope.len_lines() {
858 return 0;
859 }
860 rope_line_content_end(rope, row).saturating_sub(rope.line_to_byte(row))
861}
862
863pub fn rope_line_char_count(rope: &ropey::Rope, row: usize) -> usize {
868 if row >= rope.len_lines() {
869 return 0;
870 }
871 let end = rope_line_content_end(rope, row);
872 rope.byte_to_char(end)
873 .saturating_sub(rope.byte_to_char(rope.line_to_byte(row)))
874}
875
876pub fn pos_to_char_idx(rope: &ropey::Rope, row: usize, col: usize) -> usize {
881 let row = row.min(rope.len_lines().saturating_sub(1));
882 let line_start = rope.line_to_char(row);
883 let line_char_count = rope_line_char_count(rope, row);
884 line_start + col.min(line_char_count)
885}
886
887#[cfg(test)]
888mod tests {
889 use super::*;
890
891 #[test]
892 fn new_has_one_empty_row() {
893 let b = View::new();
894 assert_eq!(b.row_count(), 1);
895 assert_eq!(rope_line_str(&b.rope(), 0), "");
896 assert_eq!(b.cursor(), Position::default());
897 }
898
899 #[test]
900 fn from_str_splits_on_newline() {
901 let b = View::from_str("foo\nbar\nbaz");
902 assert_eq!(b.row_count(), 3);
903 assert_eq!(rope_line_str(&b.rope(), 0), "foo");
904 assert_eq!(rope_line_str(&b.rope(), 2), "baz");
905 }
906
907 #[test]
908 fn from_str_trailing_newline_keeps_empty_row() {
909 let b = View::from_str("foo\n");
910 assert_eq!(b.row_count(), 2);
911 assert_eq!(rope_line_str(&b.rope(), 1), "");
912 }
913
914 #[test]
920 fn line_helpers_strip_every_line_separator() {
921 for sep in ["\r", "\u{0b}", "\u{0c}", "\u{85}", "\u{2028}", "\u{2029}"] {
924 let rope = ropey::Rope::from_str(&format!("ab{sep}cd{sep}ef"));
925 assert_eq!(rope.len_lines(), 3, "separator {sep:?} must split rows");
926 for row in 0..3 {
927 let expect = ["ab", "cd", "ef"][row];
928 assert_eq!(
929 rope_line_str(&rope, row),
930 expect,
931 "rope_line_str kept separator {sep:?} in row {row}"
932 );
933 assert_eq!(
934 rope_line_bytes(&rope, row),
935 expect.len(),
936 "rope_line_bytes counted separator {sep:?} in row {row}"
937 );
938 assert_eq!(
939 rope_line_char_count(&rope, row),
940 expect.chars().count(),
941 "rope_line_char_count counted separator {sep:?} in row {row}"
942 );
943 }
944 }
945 }
946
947 #[test]
951 fn line_helpers_agree_with_each_other() {
952 let rope = ropey::Rope::from_str("ab\r\ncd\re\u{2028}f\u{85}\u{0c}g\nh");
953 for row in 0..rope.len_lines() {
954 let s = rope_line_str(&rope, row);
955 assert_eq!(
956 rope_line_bytes(&rope, row),
957 s.len(),
958 "bytes differ on {row}"
959 );
960 assert_eq!(
961 rope_line_char_count(&rope, row),
962 s.chars().count(),
963 "char count differs on row {row}"
964 );
965 }
966 }
967
968 #[test]
969 fn from_str_empty_input_keeps_one_row() {
970 let b = View::from_str("");
971 assert_eq!(b.row_count(), 1);
972 assert_eq!(rope_line_str(&b.rope(), 0), "");
973 }
974
975 #[test]
976 fn as_string_round_trips() {
977 let b = View::from_str("a\nb\nc");
978 assert_eq!(b.as_string(), "a\nb\nc");
979 }
980
981 #[test]
982 fn dirty_gen_starts_at_zero() {
983 assert_eq!(View::new().dirty_gen(), 0);
984 }
985
986 fn single_node_tree(base: &str, seq: u64) -> crate::SerTree {
989 crate::SerTree {
990 base: Some(base.to_string()),
991 nodes: vec![crate::SerNode {
992 parent: None,
993 children: Vec::new(),
994 last_child: None,
995 delta: None,
996 cursor: (0, 0),
997 timestamp_unix_ms: 0,
998 marks: crate::MarkSnapshot::default(),
999 seq,
1000 }],
1001 root: 0,
1002 current: 0,
1003 next_seq: seq + 1,
1004 }
1005 }
1006
1007 #[test]
1011 fn install_recovered_undo_tree_guards_seq_and_content() {
1012 let v = View::from_str("alpha\nhello");
1014 assert!(v.install_recovered_undo_tree(&single_node_tree("alpha\nhello", 3), 3));
1015
1016 let v = View::from_str("alpha\nhello");
1018 assert!(!v.install_recovered_undo_tree(&single_node_tree("alpha\nhello", 3), 4));
1019
1020 let v = View::from_str("alpha\nhello");
1022 assert!(!v.install_recovered_undo_tree(&single_node_tree("something else", 3), 3));
1023
1024 let v = View::from_str("alpha\nhello");
1026 assert!(v.install_recovered_undo_tree(&single_node_tree("alpha\nhello\n", 3), 3));
1027 }
1028
1029 fn vp_wrap(width: u16, height: u16) -> Viewport {
1030 Viewport {
1031 top_row: 0,
1032 top_col: 0,
1033 width,
1034 height,
1035 wrap: crate::Wrap::Char,
1036 text_width: width,
1037 tab_width: 0,
1038 }
1039 }
1040
1041 #[test]
1042 fn ensure_cursor_visible_wrap_scrolls_when_cursor_below_screen() {
1043 let mut b = View::from_str("aaaaaaaaaa\nb\nc");
1044 let mut v = vp_wrap(4, 3);
1045 b.set_cursor(Position::new(2, 0));
1046 b.ensure_cursor_visible(&mut v);
1047 assert_eq!(v.top_row, 1);
1048 }
1049
1050 #[test]
1051 fn ensure_cursor_visible_wrap_no_scroll_when_visible() {
1052 let mut b = View::from_str("aaaaaaaaaa\nb");
1053 let mut v = vp_wrap(4, 4);
1054 b.set_cursor(Position::new(0, 5));
1055 b.ensure_cursor_visible(&mut v);
1056 assert_eq!(v.top_row, 0);
1057 }
1058
1059 #[test]
1060 fn ensure_cursor_visible_wrap_snaps_top_when_cursor_above() {
1061 let mut b = View::from_str("a\nb\nc\nd\ne");
1062 let mut v = vp_wrap(4, 2);
1063 v.top_row = 3;
1064 b.set_cursor(Position::new(1, 0));
1065 b.ensure_cursor_visible(&mut v);
1066 assert_eq!(v.top_row, 1);
1067 }
1068
1069 #[test]
1070 fn screen_rows_between_sums_segments_under_wrap() {
1071 let b = View::from_str("aaaaaaaaa\nb\n");
1072 let v = vp_wrap(4, 0);
1073 assert_eq!(b.screen_rows_between(&v, 0, 0), 3);
1074 assert_eq!(b.screen_rows_between(&v, 0, 1), 4);
1075 assert_eq!(b.screen_rows_between(&v, 0, 2), 5);
1076 assert_eq!(b.screen_rows_between(&v, 1, 2), 2);
1077 }
1078
1079 #[test]
1080 fn screen_rows_between_one_per_doc_row_when_wrap_off() {
1081 let b = View::from_str("aaaaa\nb\nc");
1082 let v = Viewport::default();
1083 assert_eq!(b.screen_rows_between(&v, 0, 2), 3);
1084 }
1085
1086 #[test]
1087 fn max_top_for_height_walks_back_until_height_reached() {
1088 let b = View::from_str("a\nb\nc\nd\neeeeeeee");
1089 let v = vp_wrap(4, 0);
1090 assert_eq!(b.max_top_for_height(&v, 4), 2);
1091 assert_eq!(b.max_top_for_height(&v, 99), 0);
1092 }
1093
1094 #[test]
1095 fn cursor_screen_row_returns_none_when_wrap_off() {
1096 let b = View::from_str("a");
1097 let v = Viewport::default();
1098 assert!(b.cursor_screen_row(&v).is_none());
1099 }
1100
1101 #[test]
1102 fn cursor_screen_row_under_wrap() {
1103 let mut b = View::from_str("aaaaaaaaaa\nb");
1104 let v = vp_wrap(4, 0);
1105 b.set_cursor(Position::new(0, 5));
1106 assert_eq!(b.cursor_screen_row(&v), Some(1));
1107 b.set_cursor(Position::new(1, 0));
1108 assert_eq!(b.cursor_screen_row(&v), Some(3));
1109 }
1110
1111 #[test]
1115 fn cursor_screen_row_survives_shrink_from_other_view() {
1116 let a = View::from_str("a\nb\nc\nd\ne");
1117 let arc = a.content_arc();
1118 let mut view_a = View::new_view(Arc::clone(&arc));
1119 let mut view_b = View::new_view(Arc::clone(&arc));
1120 view_b.set_cursor(Position::new(4, 0));
1121 view_a.replace_all("a");
1123 let v = vp_wrap(4, 3);
1124 assert_eq!(view_b.cursor_screen_row(&v), Some(0));
1125 let mut v2 = vp_wrap(4, 3);
1126 view_b.ensure_cursor_visible(&mut v2); }
1128
1129 #[test]
1134 fn ensure_cursor_visible_clamps_stale_top_row_after_shrink() {
1135 let a = View::from_str("a\nb\nc\nd\ne");
1136 let arc = a.content_arc();
1137 let mut view_a = View::new_view(Arc::clone(&arc));
1138 let mut view_b = View::new_view(Arc::clone(&arc));
1139 view_b.set_cursor(Position::new(4, 0));
1140 view_a.replace_all("a");
1141 let mut v = vp_wrap(4, 3);
1142 v.top_row = 3;
1145 view_b.ensure_cursor_visible(&mut v);
1146 assert_eq!(v.top_row, 0, "top_row must clamp into the live rope");
1147 assert_eq!(v.top_col, 0);
1148 }
1149
1150 #[test]
1157 fn ensure_cursor_visible_survives_stale_cursor_row_after_shrink() {
1158 let a = View::from_str(
1160 "aa\nbb\nxxxxxxxxxxxxxxxxxxxxxxxxxxxx\ncc\ndd\nyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
1161 );
1162 let arc = a.content_arc();
1163 let mut view_a = View::new_view(Arc::clone(&arc));
1164 let mut view_b = View::new_view(Arc::clone(&arc));
1165 view_b.set_cursor(Position::new(5, 26));
1166 view_a.replace_all("aa\nbb\nxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
1169 let mut v = vp_wrap(4, 6);
1170 view_b.ensure_cursor_visible(&mut v); assert_eq!(
1172 v.top_row, 2,
1173 "top_row must scroll to the clamped cursor row, not past the rope"
1174 );
1175 assert_eq!(v.top_col, 0);
1176 }
1177
1178 #[test]
1182 fn ensure_cursor_visible_snaps_when_cursor_row_folded() {
1183 let mut b = View::from_str("a\nb\nc\nd\ne");
1184 b.set_folds(&[crate::Fold {
1185 start_row: 2,
1186 end_row: 4,
1187 closed: true,
1188 auto_generated: false,
1189 }]);
1190 let mut v = vp_wrap(4, 2);
1191 v.top_row = 1;
1192 b.set_cursor(Position::new(3, 0));
1193 b.ensure_cursor_visible(&mut v);
1194 assert_eq!(v.top_row, 3);
1195 }
1196
1197 #[test]
1198 fn ensure_cursor_visible_falls_back_when_wrap_disabled() {
1199 let mut b = View::from_str("a\nb\nc\nd\ne");
1200 let mut v = Viewport {
1201 top_row: 0,
1202 top_col: 0,
1203 width: 4,
1204 height: 2,
1205 wrap: crate::Wrap::None,
1206 text_width: 4,
1207 tab_width: 0,
1208 };
1209 b.set_cursor(Position::new(4, 0));
1210 b.ensure_cursor_visible(&mut v);
1211 assert_eq!(v.top_row, 3);
1212 }
1213
1214 #[test]
1220 fn undo_stack_shared_across_views() {
1221 use crate::UndoEntry;
1222 use std::time::SystemTime;
1223
1224 let a = View::from_str("hello");
1225 let arc = a.content_arc();
1226 let view_a = View::new_view(Arc::clone(&arc));
1227 let view_b = View::new_view(Arc::clone(&arc));
1228
1229 assert!(view_a.undo_stack_is_empty());
1230 assert_eq!(view_a.undo_stack_len(), 0);
1231
1232 view_a.push_undo_entry(UndoEntry {
1233 rope: view_a.rope(),
1234 cursor: (0, 0),
1235 timestamp: SystemTime::UNIX_EPOCH,
1236 marks: Default::default(),
1237 });
1238
1239 assert_eq!(view_b.undo_stack_len(), 1);
1241 assert!(!view_b.undo_stack_is_empty());
1242 }
1243
1244 #[test]
1249 fn redo_stack_shared_across_views() {
1250 use crate::UndoEntry;
1251 use std::time::SystemTime;
1252
1253 let a = View::from_str("world");
1254 let arc = a.content_arc();
1255 let view_a = View::new_view(Arc::clone(&arc));
1256 let view_b = View::new_view(Arc::clone(&arc));
1257
1258 assert!(view_a.redo_stack_is_empty());
1259
1260 view_b.push_undo_entry(UndoEntry {
1264 rope: view_b.rope(),
1265 cursor: (0, 0),
1266 timestamp: SystemTime::UNIX_EPOCH,
1267 marks: Default::default(),
1268 });
1269 view_b.undo_step(view_b.rope(), (0, 2), Default::default());
1270
1271 assert!(!view_a.redo_stack_is_empty());
1273 let entry = view_a.redo_step(view_a.rope(), (0, 0), Default::default());
1274 assert!(entry.is_some());
1275 assert_eq!(entry.unwrap().cursor, (0, 2));
1276 }
1277
1278 #[test]
1283 fn clear_undo_redo_shared_across_views() {
1284 use crate::UndoEntry;
1285 use std::time::SystemTime;
1286
1287 let a = View::from_str("abc");
1288 let arc = a.content_arc();
1289 let view_a = View::new_view(Arc::clone(&arc));
1290 let view_b = View::new_view(Arc::clone(&arc));
1291
1292 for _ in 0..2 {
1294 view_a.push_undo_entry(UndoEntry {
1295 rope: view_a.rope(),
1296 cursor: (0, 0),
1297 timestamp: SystemTime::UNIX_EPOCH,
1298 marks: Default::default(),
1299 });
1300 }
1301 view_a.undo_step(view_a.rope(), (0, 1), Default::default());
1302 assert!(!view_a.undo_stack_is_empty());
1303 assert!(!view_a.redo_stack_is_empty());
1304
1305 view_b.clear_undo_redo();
1306 assert!(view_a.undo_stack_is_empty());
1307 assert!(view_a.redo_stack_is_empty());
1308 }
1309
1310 #[test]
1312 fn content_dirty_shared_across_views() {
1313 let a = View::from_str("test");
1314 let arc = a.content_arc();
1315 let view_a = View::new_view(Arc::clone(&arc));
1316 let view_b = View::new_view(Arc::clone(&arc));
1317
1318 assert!(!view_a.content_dirty());
1319
1320 view_b.mark_content_dirty();
1321 assert!(view_a.content_dirty());
1322
1323 let taken = view_a.take_dirty();
1324 assert!(taken);
1325 assert!(!view_b.content_dirty());
1326 }
1327
1328 #[test]
1330 fn pending_fold_ops_shared_across_views() {
1331 let a = View::from_str("a\nb\nc");
1332 let arc = a.content_arc();
1333 let view_a = View::new_view(Arc::clone(&arc));
1334 let view_b = View::new_view(Arc::clone(&arc));
1335
1336 view_a.push_fold_op(crate::FoldOp::Add {
1337 start_row: 0,
1338 end_row: 1,
1339 closed: true,
1340 });
1341
1342 let ops = view_b.take_fold_ops();
1343 assert_eq!(ops.len(), 1);
1344 assert!(matches!(
1345 ops[0],
1346 crate::FoldOp::Add {
1347 start_row: 0,
1348 end_row: 1,
1349 closed: true
1350 }
1351 ));
1352 }
1353
1354 #[test]
1356 fn pending_content_reset_shared_across_views() {
1357 let a = View::from_str("x");
1358 let arc = a.content_arc();
1359 let view_a = View::new_view(Arc::clone(&arc));
1360 let view_b = View::new_view(Arc::clone(&arc));
1361
1362 assert!(!view_a.pending_content_reset());
1363 view_b.set_pending_content_reset(true);
1364 assert!(view_a.pending_content_reset());
1365 let taken = view_a.take_pending_content_reset();
1366 assert!(taken);
1367 assert!(!view_b.pending_content_reset());
1368 }
1369
1370 #[test]
1375 fn buffer_views_independent_cursors() {
1376 let a = View::from_str("hello\nworld");
1377 let arc = a.content_arc();
1378 let mut view_a = View::new_view(Arc::clone(&arc));
1379 let mut view_b = View::new_view(Arc::clone(&arc));
1380
1381 view_a.set_cursor(Position::new(1, 3));
1382 assert_eq!(view_b.cursor(), Position::new(0, 0));
1384
1385 view_b.set_cursor(Position::new(0, 2));
1386 assert_eq!(view_a.cursor(), Position::new(1, 3));
1388 }
1389
1390 #[test]
1394 fn last_cursor_reflects_most_recent_move_across_views() {
1395 let a = View::from_str("aaaa\nbbbb\ncccc\ndddd");
1396 let arc = a.content_arc();
1397 let mut view_a = View::new_view(Arc::clone(&arc));
1398 let mut view_b = View::new_view(Arc::clone(&arc));
1399
1400 view_a.set_cursor(Position::new(1, 2));
1401 assert_eq!(view_a.last_cursor(), (1, 2));
1402 assert_eq!(view_b.last_cursor(), (1, 2));
1404
1405 view_b.set_cursor(Position::new(3, 1));
1407 assert_eq!(view_a.last_cursor(), (3, 1));
1408 assert_eq!(view_b.last_cursor(), (3, 1));
1409
1410 view_a.set_cursor(Position::new(99, 99));
1412 assert_eq!(view_a.last_cursor(), (3, 4));
1413 }
1414
1415 #[test]
1419 fn clamp_position_restore_semantics() {
1420 let b = View::from_str("ab\ncdef\ng");
1421 assert_eq!(b.clamp_position(Position::new(99, 99)), Position::new(2, 1));
1423 assert_eq!(b.clamp_position(Position::new(1, 99)), Position::new(1, 4));
1425 assert_eq!(b.clamp_position(Position::new(1, 2)), Position::new(1, 2));
1427 }
1428
1429 #[test]
1431 fn buffer_views_share_content() {
1432 use crate::edit::Edit;
1433
1434 let a = View::from_str("foo");
1435 let arc = a.content_arc();
1436 let mut view_a = View::new_view(Arc::clone(&arc));
1437 let view_b = View::new_view(Arc::clone(&arc));
1438
1439 view_a.apply_edit(Edit::InsertStr {
1440 at: Position::new(0, 3),
1441 text: "bar".into(),
1442 });
1443
1444 assert_eq!(rope_line_str(&view_a.rope(), 0), "foobar");
1445 assert_eq!(rope_line_str(&view_b.rope(), 0), "foobar");
1446 }
1447
1448 #[test]
1453 fn line_measures_answer_zero_past_the_last_row() {
1454 let rope = ropey::Rope::from_str("café\nx");
1455 assert_eq!(rope.len_lines(), 2);
1456
1457 assert_eq!(rope_line_bytes(&rope, 0), 5);
1459 assert_eq!(rope_line_char_count(&rope, 0), 4);
1460
1461 assert_eq!(rope_line_bytes(&rope, 1), 1);
1463 assert_eq!(rope_line_char_count(&rope, 1), 1);
1464
1465 assert_eq!(rope_line_bytes(&rope, 2), 0);
1467 assert_eq!(rope_line_char_count(&rope, 2), 0);
1468 assert_eq!(rope_line_bytes(&rope, usize::MAX), 0);
1469 assert_eq!(rope_line_char_count(&rope, usize::MAX), 0);
1470 }
1471}
1472
1473#[cfg(test)]
1474mod marks_shared_content_tests {
1475 use super::*;
1476
1477 #[test]
1478 fn marks_shared_across_views() {
1479 let a = View::from_str("hello\nworld");
1481 let content = a.content_arc();
1482 let mut view_a = View::new_view(std::sync::Arc::clone(&content));
1483 let view_b = View::new_view(std::sync::Arc::clone(&content));
1484
1485 view_a.set_mark('x', (1, 3));
1487
1488 assert_eq!(view_b.mark('x'), Some((1, 3)));
1490 }
1491
1492 #[test]
1493 fn syntax_fold_ranges_shared_across_views() {
1494 let a = View::from_str("fn foo() {\n bar();\n}");
1495 let content = a.content_arc();
1496 let mut view_a = View::new_view(std::sync::Arc::clone(&content));
1497 let view_b = View::new_view(std::sync::Arc::clone(&content));
1498
1499 view_a.set_syntax_fold_ranges(vec![(0, 2)]);
1500
1501 assert_eq!(view_b.syntax_fold_ranges_cloned(), vec![(0, 2)]);
1502 }
1503}