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 Some(mut screen) = self.cursor_screen_row_from(viewport, viewport.top_row) else {
207 let last_row = {
218 let c = self.content.lock().unwrap();
219 c.text.len_lines().saturating_sub(1)
220 };
221 viewport.top_row = cursor.row.min(last_row);
222 viewport.top_col = 0;
223 return;
224 };
225 while screen >= height {
226 let c = self.content.lock().unwrap();
227 let mut next = viewport.top_row + 1;
228 while next <= cursor.row && c.folds.iter().any(|f| f.hides(next)) {
229 next += 1;
230 }
231 if next > cursor.row {
232 drop(c);
233 viewport.top_row = cursor.row;
234 break;
235 }
236 for r in viewport.top_row..next {
240 if c.folds.iter().any(|f| f.hides(r)) {
241 continue;
242 }
243 let line = rope_line_str(&c.text, r);
244 screen -= crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len();
245 }
246 drop(c);
247 viewport.top_row = next;
248 }
249 viewport.top_col = 0;
250 }
251
252 pub fn cursor_screen_row(&self, viewport: &Viewport) -> Option<usize> {
254 if matches!(viewport.wrap, crate::Wrap::None) || viewport.text_width == 0 {
255 return None;
256 }
257 self.cursor_screen_row_from(viewport, viewport.top_row)
258 }
259
260 pub fn screen_rows_between(&self, viewport: &Viewport, start: usize, end: usize) -> usize {
262 if start > end {
263 return 0;
264 }
265 let c = self.content.lock().unwrap();
266 let n = c.text.len_lines();
267 let last = n.saturating_sub(1);
268 let end = end.min(last);
269 let v = *viewport;
270 let mut total = 0usize;
271 for r in start..=end {
272 if c.folds.iter().any(|f| f.hides(r)) {
273 continue;
274 }
275 if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
276 total += 1;
277 } else {
278 let line = rope_line_str(&c.text, r);
279 total += crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len();
280 }
281 }
282 total
283 }
284
285 pub fn max_top_for_height(&self, viewport: &Viewport, height: usize) -> usize {
288 if height == 0 {
289 return 0;
290 }
291 let c = self.content.lock().unwrap();
292 let n = c.text.len_lines();
293 let last = n.saturating_sub(1);
294 let mut total = 0usize;
295 let mut row = last;
296 loop {
297 if !c.folds.iter().any(|f| f.hides(row)) {
298 let v = *viewport;
299 total += if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
300 1
301 } else {
302 let line = rope_line_str(&c.text, row);
303 crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len()
304 };
305 }
306 if total >= height {
307 return row;
308 }
309 if row == 0 {
310 return 0;
311 }
312 row -= 1;
313 }
314 }
315
316 pub fn clamp_position(&self, pos: Position) -> Position {
318 let c = self.content.lock().unwrap();
319 let n = c.text.len_lines();
320 let last_row = n.saturating_sub(1);
321 let row = pos.row.min(last_row);
322 let line_chars = rope_line_char_count(&c.text, row);
323 let col = pos.col.min(line_chars);
324 Position::new(row, col)
325 }
326
327 pub fn replace_all(&mut self, text: &str) {
330 let new_cursor = {
331 let mut c = self.content.lock().unwrap();
332 c.text = ropey::Rope::from_str(text);
333 let n = c.text.len_lines();
334 let last_row = n.saturating_sub(1);
335 let row = self.cursor.row.min(last_row);
336 let line_chars = rope_line_char_count(&c.text, row);
337 let col = self.cursor.col.min(line_chars);
338 c.dirty_gen = c.dirty_gen.wrapping_add(1);
339 c.cached_joined = None;
340 c.cached_byte_len = None;
341 Position::new(row, col)
342 };
343 self.cursor = new_cursor;
344 }
345
346 pub(crate) fn fold_gen_bump(&mut self) {
354 let mut c = self.content.lock().unwrap();
355 c.fold_gen = c.fold_gen.wrapping_add(1);
356 }
357
358 pub(crate) fn dirty_gen_bump(&mut self) {
360 let mut c = self.content.lock().unwrap();
361 c.dirty_gen = c.dirty_gen.wrapping_add(1);
362 c.cached_joined = None;
363 c.cached_byte_len = None;
364 }
365
366 pub fn byte_len(&self) -> usize {
372 let mut c = self.content.lock().unwrap();
373 let dg = c.dirty_gen;
374 if let Some((cached_dg, len)) = c.cached_byte_len
375 && cached_dg == dg
376 {
377 return len;
378 }
379 let total = c.text.len_bytes();
380 c.cached_byte_len = Some((dg, total));
381 total
382 }
383
384 pub fn content_joined(&self) -> std::sync::Arc<String> {
393 let mut c = self.content.lock().unwrap();
394 let dg = c.dirty_gen;
395 if let Some((cached_dg, ref s)) = c.cached_joined
396 && cached_dg == dg
397 {
398 return std::sync::Arc::clone(s);
399 }
400 let joined = std::sync::Arc::new(c.text.to_string());
401 c.cached_joined = Some((dg, std::sync::Arc::clone(&joined)));
402 joined
403 }
404
405 pub fn rope(&self) -> ropey::Rope {
414 self.content.lock().unwrap().text.clone()
415 }
416
417 pub(crate) fn content_lock(&self) -> MutexGuard<'_, Buffer> {
419 self.content.lock().unwrap()
420 }
421
422 pub(crate) fn content_lock_mut(&mut self) -> MutexGuard<'_, Buffer> {
424 self.content.lock().unwrap()
425 }
426
427 fn cursor_screen_row_from(&self, viewport: &Viewport, top: usize) -> Option<usize> {
430 let cursor = self.cursor;
431 if cursor.row < top {
432 return None;
433 }
434 let c = self.content.lock().unwrap();
435 let cursor_row = cursor.row.min(c.text.len_lines().saturating_sub(1));
439 if cursor_row < top {
440 return None;
441 }
442 let v = *viewport;
443 let mut screen = 0usize;
444 for r in top..=cursor_row {
445 if c.folds.iter().any(|f| f.hides(r)) {
446 continue;
447 }
448 let line = rope_line_str(&c.text, r);
449 let segs = crate::wrap::wrap_segments(&line, v.text_width, v.wrap);
450 if r == cursor_row {
451 let seg_idx = crate::wrap::segment_for_col(&segs, cursor.col);
452 return Some(screen + seg_idx);
453 }
454 screen += segs.len();
455 }
456 None
457 }
458
459 pub fn undo_stack_is_empty(&self) -> bool {
469 self.content.lock().unwrap().undo.is_at_root()
470 }
471
472 pub fn redo_stack_is_empty(&self) -> bool {
473 !self.content.lock().unwrap().undo.has_redo()
474 }
475
476 pub fn undo_stack_len(&self) -> usize {
477 self.content.lock().unwrap().undo.depth_from_root()
478 }
479
480 pub fn push_undo_entry(&self, entry: crate::UndoEntry) {
484 self.content.lock().unwrap().undo.push(entry);
485 }
486
487 pub fn undo_step(
491 &self,
492 rope: ropey::Rope,
493 cursor: (usize, usize),
494 marks: crate::MarkSnapshot,
495 ) -> Option<crate::UndoEntry> {
496 self.content
497 .lock()
498 .unwrap()
499 .undo
500 .undo_step(rope, cursor, marks)
501 }
502
503 pub fn redo_step(
506 &self,
507 rope: ropey::Rope,
508 cursor: (usize, usize),
509 marks: crate::MarkSnapshot,
510 ) -> Option<crate::UndoEntry> {
511 self.content
512 .lock()
513 .unwrap()
514 .undo
515 .redo_step(rope, cursor, marks)
516 }
517
518 pub fn pop_committed(&self) -> bool {
522 self.content.lock().unwrap().undo.pop_committed()
523 }
524
525 pub fn seq_earlier_step(
530 &self,
531 rope: ropey::Rope,
532 cursor: (usize, usize),
533 marks: crate::MarkSnapshot,
534 ) -> Option<crate::UndoEntry> {
535 self.content
536 .lock()
537 .unwrap()
538 .undo
539 .seq_earlier_step(rope, cursor, marks)
540 }
541
542 pub fn seq_later_step(
545 &self,
546 rope: ropey::Rope,
547 cursor: (usize, usize),
548 marks: crate::MarkSnapshot,
549 ) -> Option<crate::UndoEntry> {
550 self.content
551 .lock()
552 .unwrap()
553 .undo
554 .seq_later_step(rope, cursor, marks)
555 }
556
557 pub fn seq_earlier_timestamp(&self) -> Option<std::time::SystemTime> {
559 self.content.lock().unwrap().undo.seq_earlier_timestamp()
560 }
561
562 pub fn seq_later_timestamp(&self) -> Option<std::time::SystemTime> {
564 self.content.lock().unwrap().undo.seq_later_timestamp()
565 }
566
567 pub fn undo_leaves(&self) -> Vec<(u64, usize, std::time::SystemTime, bool)> {
570 self.content.lock().unwrap().undo.leaves()
571 }
572
573 pub fn peek_undo_timestamp(&self) -> Option<std::time::SystemTime> {
574 self.content.lock().unwrap().undo.parent_timestamp()
575 }
576
577 pub fn peek_redo_timestamp(&self) -> Option<std::time::SystemTime> {
578 self.content.lock().unwrap().undo.child_timestamp()
579 }
580
581 pub fn clear_undo_redo(&self) {
582 self.content.lock().unwrap().undo.clear_all();
583 }
584
585 pub fn undo_to_serializable(&self) -> (crate::SerTree, u64) {
592 let mut c = self.content.lock().unwrap();
593 let rope = c.text.clone();
594 c.undo.sync_current(rope);
595 let seq = c.undo.current_node_seq();
596 (c.undo.to_serializable(), seq)
597 }
598
599 pub fn install_undo_tree(&self, ser: &crate::SerTree) -> bool {
605 match crate::UndoTree::from_serializable(ser) {
606 Some(tree) => {
607 self.content.lock().unwrap().undo = tree;
608 true
609 }
610 None => false,
611 }
612 }
613
614 pub fn install_recovered_undo_tree(&self, ser: &crate::SerTree, current_seq: u64) -> bool {
628 let Some(mut tree) = crate::UndoTree::from_serializable(ser) else {
629 return false;
630 };
631 if tree.current_node_seq() != current_seq {
632 return false;
633 }
634 let mut c = self.content.lock().unwrap();
635 let materialized = tree.current_content();
636 if materialized.to_string().trim_end_matches('\n')
637 != c.text.to_string().trim_end_matches('\n')
638 {
639 return false;
640 }
641 c.undo = tree;
642 true
643 }
644
645 pub fn clear_redo(&self) {
646 self.content.lock().unwrap().undo.clear_redo();
647 }
648
649 pub fn undo_group_active(&self) -> bool {
652 self.content.lock().unwrap().undo_group_active()
653 }
654
655 pub fn undo_group_arm(&self) -> bool {
657 self.content.lock().unwrap().undo_group_arm()
658 }
659
660 pub fn cap_undo(&self, cap: usize) {
661 self.content.lock().unwrap().undo.cap(cap);
662 }
663
664 pub fn content_dirty(&self) -> bool {
665 self.content.lock().unwrap().content_dirty
666 }
667
668 pub fn set_content_dirty(&self, v: bool) {
669 self.content.lock().unwrap().content_dirty = v;
670 }
671
672 pub fn mark_content_dirty(&self) {
673 let mut c = self.content.lock().unwrap();
674 c.content_dirty = true;
675 c.cached_editor_content = None;
676 }
677
678 pub fn take_dirty(&self) -> bool {
679 let mut c = self.content.lock().unwrap();
680 let v = c.content_dirty;
681 c.content_dirty = false;
682 v
683 }
684
685 pub fn cached_editor_content(&self) -> Option<std::sync::Arc<String>> {
686 self.content.lock().unwrap().cached_editor_content.clone()
687 }
688
689 pub fn set_cached_editor_content(&self, arc: std::sync::Arc<String>) {
690 self.content.lock().unwrap().cached_editor_content = Some(arc);
691 }
692
693 pub fn push_fold_op(&self, op: crate::FoldOp) {
694 self.content.lock().unwrap().pending_fold_ops.push(op);
695 }
696
697 pub fn take_fold_ops(&self) -> Vec<crate::FoldOp> {
698 std::mem::take(&mut self.content.lock().unwrap().pending_fold_ops)
699 }
700
701 pub fn extend_change_log(&self, edits: impl IntoIterator<Item = crate::EngineEdit>) {
702 self.content.lock().unwrap().change_log.extend(edits);
703 }
704
705 pub fn take_change_log(&self) -> Vec<crate::EngineEdit> {
706 std::mem::take(&mut self.content.lock().unwrap().change_log)
707 }
708
709 pub fn extend_pending_content_edits(
710 &self,
711 edits: impl IntoIterator<Item = crate::ContentEdit>,
712 ) {
713 self.content
714 .lock()
715 .unwrap()
716 .pending_content_edits
717 .extend(edits);
718 }
719
720 pub fn push_pending_content_edit(&self, edit: crate::ContentEdit) {
721 self.content
722 .lock()
723 .unwrap()
724 .pending_content_edits
725 .push(edit);
726 }
727
728 pub fn take_pending_content_edits(&self) -> Vec<crate::ContentEdit> {
729 std::mem::take(&mut self.content.lock().unwrap().pending_content_edits)
730 }
731
732 pub fn clear_pending_content_edits(&self) {
733 self.content.lock().unwrap().pending_content_edits.clear();
734 }
735
736 pub fn pending_content_reset(&self) -> bool {
737 self.content.lock().unwrap().pending_content_reset
738 }
739
740 pub fn set_pending_content_reset(&self, v: bool) {
741 self.content.lock().unwrap().pending_content_reset = v;
742 }
743
744 pub fn take_pending_content_reset(&self) -> bool {
745 let mut c = self.content.lock().unwrap();
746 let v = c.pending_content_reset;
747 c.pending_content_reset = false;
748 v
749 }
750
751 pub fn mark(&self, c: char) -> Option<(usize, usize)> {
752 self.content_lock().marks.get(&c).copied()
753 }
754 pub fn set_mark(&mut self, c: char, pos: (usize, usize)) {
755 self.content_lock_mut().marks.insert(c, pos);
756 }
757 pub fn clear_mark(&mut self, c: char) {
758 self.content_lock_mut().marks.remove(&c);
759 }
760 pub fn marks_cloned(&self) -> std::collections::BTreeMap<char, (usize, usize)> {
761 self.content_lock().marks.clone()
762 }
763 pub fn set_marks(&mut self, marks: std::collections::BTreeMap<char, (usize, usize)>) {
764 self.content_lock_mut().marks = marks;
765 }
766 pub fn rebase_marks(
770 &mut self,
771 edit_start: usize,
772 drop_end: usize,
773 shift_threshold: usize,
774 delta: isize,
775 ) {
776 let mut c = self.content_lock_mut();
777 let mut to_drop: Vec<char> = Vec::new();
778 for (ch, (row, _col)) in c.marks.iter_mut() {
779 if (edit_start..drop_end).contains(row) {
780 to_drop.push(*ch);
781 } else if *row >= shift_threshold {
782 *row = ((*row as isize) + delta).max(0) as usize;
783 }
784 }
785 for ch in to_drop {
786 c.marks.remove(&ch);
787 }
788 }
789 pub fn syntax_fold_ranges_cloned(&self) -> Vec<(usize, usize)> {
790 self.content_lock().syntax_fold_ranges.clone()
791 }
792 pub fn set_syntax_fold_ranges(&mut self, ranges: Vec<(usize, usize)>) {
793 self.content_lock_mut().syntax_fold_ranges = ranges;
794 }
795}
796
797pub fn floor_char_boundary(rope: &ropey::Rope, byte_idx: usize) -> usize {
810 let byte_idx = byte_idx.min(rope.len_bytes());
811 rope.char_to_byte(rope.byte_to_char(byte_idx))
812}
813
814fn rope_line_content_end(rope: &ropey::Rope, row: usize) -> usize {
826 if row + 1 >= rope.len_lines() {
827 return rope.len_bytes();
828 }
829 let step_back = rope.line_to_byte(row + 1).saturating_sub(1);
830 floor_char_boundary(rope, step_back)
831}
832
833pub fn rope_line_str(rope: &ropey::Rope, row: usize) -> String {
836 let start = rope.line_to_byte(row);
838 rope.byte_slice(start..rope_line_content_end(rope, row))
839 .to_string()
840}
841
842pub fn rope_line_bytes(rope: &ropey::Rope, row: usize) -> usize {
848 if row >= rope.len_lines() {
849 return 0;
850 }
851 rope_line_content_end(rope, row).saturating_sub(rope.line_to_byte(row))
852}
853
854pub fn rope_line_char_count(rope: &ropey::Rope, row: usize) -> usize {
859 if row >= rope.len_lines() {
860 return 0;
861 }
862 let end = rope_line_content_end(rope, row);
863 rope.byte_to_char(end)
864 .saturating_sub(rope.byte_to_char(rope.line_to_byte(row)))
865}
866
867pub fn pos_to_char_idx(rope: &ropey::Rope, row: usize, col: usize) -> usize {
872 let row = row.min(rope.len_lines().saturating_sub(1));
873 let line_start = rope.line_to_char(row);
874 let line_char_count = rope_line_char_count(rope, row);
875 line_start + col.min(line_char_count)
876}
877
878#[cfg(test)]
879mod tests {
880 use super::*;
881
882 #[test]
883 fn new_has_one_empty_row() {
884 let b = View::new();
885 assert_eq!(b.row_count(), 1);
886 assert_eq!(rope_line_str(&b.rope(), 0), "");
887 assert_eq!(b.cursor(), Position::default());
888 }
889
890 #[test]
891 fn from_str_splits_on_newline() {
892 let b = View::from_str("foo\nbar\nbaz");
893 assert_eq!(b.row_count(), 3);
894 assert_eq!(rope_line_str(&b.rope(), 0), "foo");
895 assert_eq!(rope_line_str(&b.rope(), 2), "baz");
896 }
897
898 #[test]
899 fn from_str_trailing_newline_keeps_empty_row() {
900 let b = View::from_str("foo\n");
901 assert_eq!(b.row_count(), 2);
902 assert_eq!(rope_line_str(&b.rope(), 1), "");
903 }
904
905 #[test]
911 fn line_helpers_strip_every_line_separator() {
912 for sep in ["\r", "\u{0b}", "\u{0c}", "\u{85}", "\u{2028}", "\u{2029}"] {
915 let rope = ropey::Rope::from_str(&format!("ab{sep}cd{sep}ef"));
916 assert_eq!(rope.len_lines(), 3, "separator {sep:?} must split rows");
917 for row in 0..3 {
918 let expect = ["ab", "cd", "ef"][row];
919 assert_eq!(
920 rope_line_str(&rope, row),
921 expect,
922 "rope_line_str kept separator {sep:?} in row {row}"
923 );
924 assert_eq!(
925 rope_line_bytes(&rope, row),
926 expect.len(),
927 "rope_line_bytes counted separator {sep:?} in row {row}"
928 );
929 assert_eq!(
930 rope_line_char_count(&rope, row),
931 expect.chars().count(),
932 "rope_line_char_count counted separator {sep:?} in row {row}"
933 );
934 }
935 }
936 }
937
938 #[test]
942 fn line_helpers_agree_with_each_other() {
943 let rope = ropey::Rope::from_str("ab\r\ncd\re\u{2028}f\u{85}\u{0c}g\nh");
944 for row in 0..rope.len_lines() {
945 let s = rope_line_str(&rope, row);
946 assert_eq!(
947 rope_line_bytes(&rope, row),
948 s.len(),
949 "bytes differ on {row}"
950 );
951 assert_eq!(
952 rope_line_char_count(&rope, row),
953 s.chars().count(),
954 "char count differs on row {row}"
955 );
956 }
957 }
958
959 #[test]
960 fn from_str_empty_input_keeps_one_row() {
961 let b = View::from_str("");
962 assert_eq!(b.row_count(), 1);
963 assert_eq!(rope_line_str(&b.rope(), 0), "");
964 }
965
966 #[test]
967 fn as_string_round_trips() {
968 let b = View::from_str("a\nb\nc");
969 assert_eq!(b.as_string(), "a\nb\nc");
970 }
971
972 #[test]
973 fn dirty_gen_starts_at_zero() {
974 assert_eq!(View::new().dirty_gen(), 0);
975 }
976
977 fn single_node_tree(base: &str, seq: u64) -> crate::SerTree {
980 crate::SerTree {
981 base: base.to_string(),
982 nodes: vec![crate::SerNode {
983 parent: None,
984 children: Vec::new(),
985 last_child: None,
986 delta: None,
987 cursor: (0, 0),
988 timestamp_unix_ms: 0,
989 marks: crate::MarkSnapshot::default(),
990 seq,
991 }],
992 root: 0,
993 current: 0,
994 next_seq: seq + 1,
995 }
996 }
997
998 #[test]
1002 fn install_recovered_undo_tree_guards_seq_and_content() {
1003 let v = View::from_str("alpha\nhello");
1005 assert!(v.install_recovered_undo_tree(&single_node_tree("alpha\nhello", 3), 3));
1006
1007 let v = View::from_str("alpha\nhello");
1009 assert!(!v.install_recovered_undo_tree(&single_node_tree("alpha\nhello", 3), 4));
1010
1011 let v = View::from_str("alpha\nhello");
1013 assert!(!v.install_recovered_undo_tree(&single_node_tree("something else", 3), 3));
1014
1015 let v = View::from_str("alpha\nhello");
1017 assert!(v.install_recovered_undo_tree(&single_node_tree("alpha\nhello\n", 3), 3));
1018 }
1019
1020 fn vp_wrap(width: u16, height: u16) -> Viewport {
1021 Viewport {
1022 top_row: 0,
1023 top_col: 0,
1024 width,
1025 height,
1026 wrap: crate::Wrap::Char,
1027 text_width: width,
1028 tab_width: 0,
1029 }
1030 }
1031
1032 #[test]
1033 fn ensure_cursor_visible_wrap_scrolls_when_cursor_below_screen() {
1034 let mut b = View::from_str("aaaaaaaaaa\nb\nc");
1035 let mut v = vp_wrap(4, 3);
1036 b.set_cursor(Position::new(2, 0));
1037 b.ensure_cursor_visible(&mut v);
1038 assert_eq!(v.top_row, 1);
1039 }
1040
1041 #[test]
1042 fn ensure_cursor_visible_wrap_no_scroll_when_visible() {
1043 let mut b = View::from_str("aaaaaaaaaa\nb");
1044 let mut v = vp_wrap(4, 4);
1045 b.set_cursor(Position::new(0, 5));
1046 b.ensure_cursor_visible(&mut v);
1047 assert_eq!(v.top_row, 0);
1048 }
1049
1050 #[test]
1051 fn ensure_cursor_visible_wrap_snaps_top_when_cursor_above() {
1052 let mut b = View::from_str("a\nb\nc\nd\ne");
1053 let mut v = vp_wrap(4, 2);
1054 v.top_row = 3;
1055 b.set_cursor(Position::new(1, 0));
1056 b.ensure_cursor_visible(&mut v);
1057 assert_eq!(v.top_row, 1);
1058 }
1059
1060 #[test]
1061 fn screen_rows_between_sums_segments_under_wrap() {
1062 let b = View::from_str("aaaaaaaaa\nb\n");
1063 let v = vp_wrap(4, 0);
1064 assert_eq!(b.screen_rows_between(&v, 0, 0), 3);
1065 assert_eq!(b.screen_rows_between(&v, 0, 1), 4);
1066 assert_eq!(b.screen_rows_between(&v, 0, 2), 5);
1067 assert_eq!(b.screen_rows_between(&v, 1, 2), 2);
1068 }
1069
1070 #[test]
1071 fn screen_rows_between_one_per_doc_row_when_wrap_off() {
1072 let b = View::from_str("aaaaa\nb\nc");
1073 let v = Viewport::default();
1074 assert_eq!(b.screen_rows_between(&v, 0, 2), 3);
1075 }
1076
1077 #[test]
1078 fn max_top_for_height_walks_back_until_height_reached() {
1079 let b = View::from_str("a\nb\nc\nd\neeeeeeee");
1080 let v = vp_wrap(4, 0);
1081 assert_eq!(b.max_top_for_height(&v, 4), 2);
1082 assert_eq!(b.max_top_for_height(&v, 99), 0);
1083 }
1084
1085 #[test]
1086 fn cursor_screen_row_returns_none_when_wrap_off() {
1087 let b = View::from_str("a");
1088 let v = Viewport::default();
1089 assert!(b.cursor_screen_row(&v).is_none());
1090 }
1091
1092 #[test]
1093 fn cursor_screen_row_under_wrap() {
1094 let mut b = View::from_str("aaaaaaaaaa\nb");
1095 let v = vp_wrap(4, 0);
1096 b.set_cursor(Position::new(0, 5));
1097 assert_eq!(b.cursor_screen_row(&v), Some(1));
1098 b.set_cursor(Position::new(1, 0));
1099 assert_eq!(b.cursor_screen_row(&v), Some(3));
1100 }
1101
1102 #[test]
1106 fn cursor_screen_row_survives_shrink_from_other_view() {
1107 let a = View::from_str("a\nb\nc\nd\ne");
1108 let arc = a.content_arc();
1109 let mut view_a = View::new_view(Arc::clone(&arc));
1110 let mut view_b = View::new_view(Arc::clone(&arc));
1111 view_b.set_cursor(Position::new(4, 0));
1112 view_a.replace_all("a");
1114 let v = vp_wrap(4, 3);
1115 assert_eq!(view_b.cursor_screen_row(&v), Some(0));
1116 let mut v2 = vp_wrap(4, 3);
1117 view_b.ensure_cursor_visible(&mut v2); }
1119
1120 #[test]
1125 fn ensure_cursor_visible_clamps_stale_top_row_after_shrink() {
1126 let a = View::from_str("a\nb\nc\nd\ne");
1127 let arc = a.content_arc();
1128 let mut view_a = View::new_view(Arc::clone(&arc));
1129 let mut view_b = View::new_view(Arc::clone(&arc));
1130 view_b.set_cursor(Position::new(4, 0));
1131 view_a.replace_all("a");
1132 let mut v = vp_wrap(4, 3);
1133 v.top_row = 3;
1136 view_b.ensure_cursor_visible(&mut v);
1137 assert_eq!(v.top_row, 0, "top_row must clamp into the live rope");
1138 assert_eq!(v.top_col, 0);
1139 }
1140
1141 #[test]
1145 fn ensure_cursor_visible_snaps_when_cursor_row_folded() {
1146 let mut b = View::from_str("a\nb\nc\nd\ne");
1147 b.set_folds(&[crate::Fold {
1148 start_row: 2,
1149 end_row: 4,
1150 closed: true,
1151 auto_generated: false,
1152 }]);
1153 let mut v = vp_wrap(4, 2);
1154 v.top_row = 1;
1155 b.set_cursor(Position::new(3, 0));
1156 b.ensure_cursor_visible(&mut v);
1157 assert_eq!(v.top_row, 3);
1158 }
1159
1160 #[test]
1161 fn ensure_cursor_visible_falls_back_when_wrap_disabled() {
1162 let mut b = View::from_str("a\nb\nc\nd\ne");
1163 let mut v = Viewport {
1164 top_row: 0,
1165 top_col: 0,
1166 width: 4,
1167 height: 2,
1168 wrap: crate::Wrap::None,
1169 text_width: 4,
1170 tab_width: 0,
1171 };
1172 b.set_cursor(Position::new(4, 0));
1173 b.ensure_cursor_visible(&mut v);
1174 assert_eq!(v.top_row, 3);
1175 }
1176
1177 #[test]
1183 fn undo_stack_shared_across_views() {
1184 use crate::UndoEntry;
1185 use std::time::SystemTime;
1186
1187 let a = View::from_str("hello");
1188 let arc = a.content_arc();
1189 let view_a = View::new_view(Arc::clone(&arc));
1190 let view_b = View::new_view(Arc::clone(&arc));
1191
1192 assert!(view_a.undo_stack_is_empty());
1193 assert_eq!(view_a.undo_stack_len(), 0);
1194
1195 view_a.push_undo_entry(UndoEntry {
1196 rope: view_a.rope(),
1197 cursor: (0, 0),
1198 timestamp: SystemTime::UNIX_EPOCH,
1199 marks: Default::default(),
1200 });
1201
1202 assert_eq!(view_b.undo_stack_len(), 1);
1204 assert!(!view_b.undo_stack_is_empty());
1205 }
1206
1207 #[test]
1212 fn redo_stack_shared_across_views() {
1213 use crate::UndoEntry;
1214 use std::time::SystemTime;
1215
1216 let a = View::from_str("world");
1217 let arc = a.content_arc();
1218 let view_a = View::new_view(Arc::clone(&arc));
1219 let view_b = View::new_view(Arc::clone(&arc));
1220
1221 assert!(view_a.redo_stack_is_empty());
1222
1223 view_b.push_undo_entry(UndoEntry {
1227 rope: view_b.rope(),
1228 cursor: (0, 0),
1229 timestamp: SystemTime::UNIX_EPOCH,
1230 marks: Default::default(),
1231 });
1232 view_b.undo_step(view_b.rope(), (0, 2), Default::default());
1233
1234 assert!(!view_a.redo_stack_is_empty());
1236 let entry = view_a.redo_step(view_a.rope(), (0, 0), Default::default());
1237 assert!(entry.is_some());
1238 assert_eq!(entry.unwrap().cursor, (0, 2));
1239 }
1240
1241 #[test]
1246 fn clear_undo_redo_shared_across_views() {
1247 use crate::UndoEntry;
1248 use std::time::SystemTime;
1249
1250 let a = View::from_str("abc");
1251 let arc = a.content_arc();
1252 let view_a = View::new_view(Arc::clone(&arc));
1253 let view_b = View::new_view(Arc::clone(&arc));
1254
1255 for _ in 0..2 {
1257 view_a.push_undo_entry(UndoEntry {
1258 rope: view_a.rope(),
1259 cursor: (0, 0),
1260 timestamp: SystemTime::UNIX_EPOCH,
1261 marks: Default::default(),
1262 });
1263 }
1264 view_a.undo_step(view_a.rope(), (0, 1), Default::default());
1265 assert!(!view_a.undo_stack_is_empty());
1266 assert!(!view_a.redo_stack_is_empty());
1267
1268 view_b.clear_undo_redo();
1269 assert!(view_a.undo_stack_is_empty());
1270 assert!(view_a.redo_stack_is_empty());
1271 }
1272
1273 #[test]
1275 fn content_dirty_shared_across_views() {
1276 let a = View::from_str("test");
1277 let arc = a.content_arc();
1278 let view_a = View::new_view(Arc::clone(&arc));
1279 let view_b = View::new_view(Arc::clone(&arc));
1280
1281 assert!(!view_a.content_dirty());
1282
1283 view_b.mark_content_dirty();
1284 assert!(view_a.content_dirty());
1285
1286 let taken = view_a.take_dirty();
1287 assert!(taken);
1288 assert!(!view_b.content_dirty());
1289 }
1290
1291 #[test]
1293 fn pending_fold_ops_shared_across_views() {
1294 let a = View::from_str("a\nb\nc");
1295 let arc = a.content_arc();
1296 let view_a = View::new_view(Arc::clone(&arc));
1297 let view_b = View::new_view(Arc::clone(&arc));
1298
1299 view_a.push_fold_op(crate::FoldOp::Add {
1300 start_row: 0,
1301 end_row: 1,
1302 closed: true,
1303 });
1304
1305 let ops = view_b.take_fold_ops();
1306 assert_eq!(ops.len(), 1);
1307 assert!(matches!(
1308 ops[0],
1309 crate::FoldOp::Add {
1310 start_row: 0,
1311 end_row: 1,
1312 closed: true
1313 }
1314 ));
1315 }
1316
1317 #[test]
1319 fn pending_content_reset_shared_across_views() {
1320 let a = View::from_str("x");
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 assert!(!view_a.pending_content_reset());
1326 view_b.set_pending_content_reset(true);
1327 assert!(view_a.pending_content_reset());
1328 let taken = view_a.take_pending_content_reset();
1329 assert!(taken);
1330 assert!(!view_b.pending_content_reset());
1331 }
1332
1333 #[test]
1338 fn buffer_views_independent_cursors() {
1339 let a = View::from_str("hello\nworld");
1340 let arc = a.content_arc();
1341 let mut view_a = View::new_view(Arc::clone(&arc));
1342 let mut view_b = View::new_view(Arc::clone(&arc));
1343
1344 view_a.set_cursor(Position::new(1, 3));
1345 assert_eq!(view_b.cursor(), Position::new(0, 0));
1347
1348 view_b.set_cursor(Position::new(0, 2));
1349 assert_eq!(view_a.cursor(), Position::new(1, 3));
1351 }
1352
1353 #[test]
1357 fn last_cursor_reflects_most_recent_move_across_views() {
1358 let a = View::from_str("aaaa\nbbbb\ncccc\ndddd");
1359 let arc = a.content_arc();
1360 let mut view_a = View::new_view(Arc::clone(&arc));
1361 let mut view_b = View::new_view(Arc::clone(&arc));
1362
1363 view_a.set_cursor(Position::new(1, 2));
1364 assert_eq!(view_a.last_cursor(), (1, 2));
1365 assert_eq!(view_b.last_cursor(), (1, 2));
1367
1368 view_b.set_cursor(Position::new(3, 1));
1370 assert_eq!(view_a.last_cursor(), (3, 1));
1371 assert_eq!(view_b.last_cursor(), (3, 1));
1372
1373 view_a.set_cursor(Position::new(99, 99));
1375 assert_eq!(view_a.last_cursor(), (3, 4));
1376 }
1377
1378 #[test]
1382 fn clamp_position_restore_semantics() {
1383 let b = View::from_str("ab\ncdef\ng");
1384 assert_eq!(b.clamp_position(Position::new(99, 99)), Position::new(2, 1));
1386 assert_eq!(b.clamp_position(Position::new(1, 99)), Position::new(1, 4));
1388 assert_eq!(b.clamp_position(Position::new(1, 2)), Position::new(1, 2));
1390 }
1391
1392 #[test]
1394 fn buffer_views_share_content() {
1395 use crate::edit::Edit;
1396
1397 let a = View::from_str("foo");
1398 let arc = a.content_arc();
1399 let mut view_a = View::new_view(Arc::clone(&arc));
1400 let view_b = View::new_view(Arc::clone(&arc));
1401
1402 view_a.apply_edit(Edit::InsertStr {
1403 at: Position::new(0, 3),
1404 text: "bar".into(),
1405 });
1406
1407 assert_eq!(rope_line_str(&view_a.rope(), 0), "foobar");
1408 assert_eq!(rope_line_str(&view_b.rope(), 0), "foobar");
1409 }
1410
1411 #[test]
1416 fn line_measures_answer_zero_past_the_last_row() {
1417 let rope = ropey::Rope::from_str("café\nx");
1418 assert_eq!(rope.len_lines(), 2);
1419
1420 assert_eq!(rope_line_bytes(&rope, 0), 5);
1422 assert_eq!(rope_line_char_count(&rope, 0), 4);
1423
1424 assert_eq!(rope_line_bytes(&rope, 1), 1);
1426 assert_eq!(rope_line_char_count(&rope, 1), 1);
1427
1428 assert_eq!(rope_line_bytes(&rope, 2), 0);
1430 assert_eq!(rope_line_char_count(&rope, 2), 0);
1431 assert_eq!(rope_line_bytes(&rope, usize::MAX), 0);
1432 assert_eq!(rope_line_char_count(&rope, usize::MAX), 0);
1433 }
1434}
1435
1436#[cfg(test)]
1437mod marks_shared_content_tests {
1438 use super::*;
1439
1440 #[test]
1441 fn marks_shared_across_views() {
1442 let a = View::from_str("hello\nworld");
1444 let content = a.content_arc();
1445 let mut view_a = View::new_view(std::sync::Arc::clone(&content));
1446 let view_b = View::new_view(std::sync::Arc::clone(&content));
1447
1448 view_a.set_mark('x', (1, 3));
1450
1451 assert_eq!(view_b.mark('x'), Some((1, 3)));
1453 }
1454
1455 #[test]
1456 fn syntax_fold_ranges_shared_across_views() {
1457 let a = View::from_str("fn foo() {\n bar();\n}");
1458 let content = a.content_arc();
1459 let mut view_a = View::new_view(std::sync::Arc::clone(&content));
1460 let view_b = View::new_view(std::sync::Arc::clone(&content));
1461
1462 view_a.set_syntax_fold_ranges(vec![(0, 2)]);
1463
1464 assert_eq!(view_b.syntax_fold_ranges_cloned(), vec![(0, 2)]);
1465 }
1466}