1use std::sync::{Arc, Mutex, MutexGuard};
2
3use crate::content::Buffer;
4use crate::{Position, Viewport};
5
6pub struct View {
44 pub(crate) content: Arc<Mutex<Buffer>>,
46 cursor: Position,
49}
50
51impl Default for View {
52 fn default() -> Self {
53 Self::new()
54 }
55}
56
57impl View {
58 pub fn new() -> Self {
62 Self {
63 content: Arc::new(Mutex::new(Buffer::new())),
64 cursor: Position::default(),
65 }
66 }
67
68 #[allow(clippy::should_implement_trait)]
73 pub fn from_str(text: &str) -> Self {
74 Self {
75 content: Arc::new(Mutex::new(Buffer::from_str(text))),
76 cursor: Position::default(),
77 }
78 }
79
80 pub fn new_view(content: Arc<Mutex<Buffer>>) -> Self {
100 Self {
101 content,
102 cursor: Position::default(),
103 }
104 }
105
106 pub fn content_arc(&self) -> Arc<Mutex<Buffer>> {
109 Arc::clone(&self.content)
110 }
111
112 pub fn cursor(&self) -> Position {
115 self.cursor
116 }
117
118 pub fn dirty_gen(&self) -> u64 {
119 self.content.lock().unwrap().dirty_gen
120 }
121
122 pub fn row_count(&self) -> usize {
124 self.content.lock().unwrap().text.len_lines()
125 }
126
127 pub fn as_string(&self) -> String {
132 self.content.lock().unwrap().text.to_string()
133 }
134
135 pub fn set_cursor(&mut self, pos: Position) {
142 let c = self.content.lock().unwrap();
143 let n = c.text.len_lines();
144 let last_row = n.saturating_sub(1);
145 let row = pos.row.min(last_row);
146 let line_chars = rope_line_char_count(&c.text, row);
147 let col = pos.col.min(line_chars);
148 drop(c);
149 self.cursor = Position::new(row, col);
150 }
151
152 pub fn ensure_cursor_visible(&mut self, viewport: &mut Viewport) {
155 let cursor = self.cursor;
156 let v = *viewport;
157 let wrap_active = !matches!(v.wrap, crate::Wrap::None) && v.text_width > 0;
158 if !wrap_active {
159 viewport.ensure_visible(cursor);
160 return;
161 }
162 if v.height == 0 {
163 return;
164 }
165 if cursor.row < v.top_row {
167 viewport.top_row = cursor.row;
168 viewport.top_col = 0;
169 return;
170 }
171 let height = v.height as usize;
172 let mut screen = match self.cursor_screen_row_from(viewport, viewport.top_row) {
178 Some(s) => s,
179 None => {
180 viewport.top_col = 0;
181 return;
182 }
183 };
184 while screen >= height {
185 let c = self.content.lock().unwrap();
186 let mut next = viewport.top_row + 1;
187 while next <= cursor.row && c.folds.iter().any(|f| f.hides(next)) {
188 next += 1;
189 }
190 if next > cursor.row {
191 drop(c);
192 viewport.top_row = cursor.row;
193 break;
194 }
195 for r in viewport.top_row..next {
199 if c.folds.iter().any(|f| f.hides(r)) {
200 continue;
201 }
202 let line = rope_line_str(&c.text, r);
203 screen -= crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len();
204 }
205 drop(c);
206 viewport.top_row = next;
207 }
208 viewport.top_col = 0;
209 }
210
211 pub fn cursor_screen_row(&self, viewport: &Viewport) -> Option<usize> {
213 if matches!(viewport.wrap, crate::Wrap::None) || viewport.text_width == 0 {
214 return None;
215 }
216 self.cursor_screen_row_from(viewport, viewport.top_row)
217 }
218
219 pub fn screen_rows_between(&self, viewport: &Viewport, start: usize, end: usize) -> usize {
221 if start > end {
222 return 0;
223 }
224 let c = self.content.lock().unwrap();
225 let n = c.text.len_lines();
226 let last = n.saturating_sub(1);
227 let end = end.min(last);
228 let v = *viewport;
229 let mut total = 0usize;
230 for r in start..=end {
231 if c.folds.iter().any(|f| f.hides(r)) {
232 continue;
233 }
234 if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
235 total += 1;
236 } else {
237 let line = rope_line_str(&c.text, r);
238 total += crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len();
239 }
240 }
241 total
242 }
243
244 pub fn max_top_for_height(&self, viewport: &Viewport, height: usize) -> usize {
247 if height == 0 {
248 return 0;
249 }
250 let c = self.content.lock().unwrap();
251 let n = c.text.len_lines();
252 let last = n.saturating_sub(1);
253 let mut total = 0usize;
254 let mut row = last;
255 loop {
256 if !c.folds.iter().any(|f| f.hides(row)) {
257 let v = *viewport;
258 total += if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
259 1
260 } else {
261 let line = rope_line_str(&c.text, row);
262 crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len()
263 };
264 }
265 if total >= height {
266 return row;
267 }
268 if row == 0 {
269 return 0;
270 }
271 row -= 1;
272 }
273 }
274
275 pub fn clamp_position(&self, pos: Position) -> Position {
277 let c = self.content.lock().unwrap();
278 let n = c.text.len_lines();
279 let last_row = n.saturating_sub(1);
280 let row = pos.row.min(last_row);
281 let line_chars = rope_line_char_count(&c.text, row);
282 let col = pos.col.min(line_chars);
283 Position::new(row, col)
284 }
285
286 pub fn replace_all(&mut self, text: &str) {
289 let new_cursor = {
290 let mut c = self.content.lock().unwrap();
291 c.text = ropey::Rope::from_str(text);
292 let n = c.text.len_lines();
293 let last_row = n.saturating_sub(1);
294 let row = self.cursor.row.min(last_row);
295 let line_chars = rope_line_char_count(&c.text, row);
296 let col = self.cursor.col.min(line_chars);
297 c.dirty_gen = c.dirty_gen.wrapping_add(1);
298 c.cached_joined = None;
299 c.cached_byte_len = None;
300 Position::new(row, col)
301 };
302 self.cursor = new_cursor;
303 }
304
305 pub(crate) fn dirty_gen_bump(&mut self) {
309 let mut c = self.content.lock().unwrap();
310 c.dirty_gen = c.dirty_gen.wrapping_add(1);
311 c.cached_joined = None;
312 c.cached_byte_len = None;
313 }
314
315 pub fn byte_len(&self) -> usize {
321 let mut c = self.content.lock().unwrap();
322 let dg = c.dirty_gen;
323 if let Some((cached_dg, len)) = c.cached_byte_len
324 && cached_dg == dg
325 {
326 return len;
327 }
328 let total = c.text.len_bytes();
329 c.cached_byte_len = Some((dg, total));
330 total
331 }
332
333 pub fn content_joined(&self) -> std::sync::Arc<String> {
342 let mut c = self.content.lock().unwrap();
343 let dg = c.dirty_gen;
344 if let Some((cached_dg, ref s)) = c.cached_joined
345 && cached_dg == dg
346 {
347 return std::sync::Arc::clone(s);
348 }
349 let joined = std::sync::Arc::new(c.text.to_string());
350 c.cached_joined = Some((dg, std::sync::Arc::clone(&joined)));
351 joined
352 }
353
354 pub fn rope(&self) -> ropey::Rope {
363 self.content.lock().unwrap().text.clone()
364 }
365
366 pub(crate) fn content_lock(&self) -> MutexGuard<'_, Buffer> {
368 self.content.lock().unwrap()
369 }
370
371 pub(crate) fn content_lock_mut(&mut self) -> MutexGuard<'_, Buffer> {
373 self.content.lock().unwrap()
374 }
375
376 fn cursor_screen_row_from(&self, viewport: &Viewport, top: usize) -> Option<usize> {
379 let cursor = self.cursor;
380 if cursor.row < top {
381 return None;
382 }
383 let c = self.content.lock().unwrap();
384 let cursor_row = cursor.row.min(c.text.len_lines().saturating_sub(1));
388 if cursor_row < top {
389 return None;
390 }
391 let v = *viewport;
392 let mut screen = 0usize;
393 for r in top..=cursor_row {
394 if c.folds.iter().any(|f| f.hides(r)) {
395 continue;
396 }
397 let line = rope_line_str(&c.text, r);
398 let segs = crate::wrap::wrap_segments(&line, v.text_width, v.wrap);
399 if r == cursor_row {
400 let seg_idx = crate::wrap::segment_for_col(&segs, cursor.col);
401 return Some(screen + seg_idx);
402 }
403 screen += segs.len();
404 }
405 None
406 }
407
408 pub fn undo_stack_is_empty(&self) -> bool {
411 self.content.lock().unwrap().undo_stack.is_empty()
412 }
413
414 pub fn redo_stack_is_empty(&self) -> bool {
415 self.content.lock().unwrap().redo_stack.is_empty()
416 }
417
418 pub fn undo_stack_len(&self) -> usize {
419 self.content.lock().unwrap().undo_stack.len()
420 }
421
422 pub fn push_undo_entry(&self, entry: crate::UndoEntry) {
423 self.content.lock().unwrap().undo_stack.push(entry);
424 }
425
426 pub fn push_redo_entry(&self, entry: crate::UndoEntry) {
427 self.content.lock().unwrap().redo_stack.push(entry);
428 }
429
430 pub fn pop_undo_entry(&self) -> Option<crate::UndoEntry> {
431 self.content.lock().unwrap().undo_stack.pop()
432 }
433
434 pub fn pop_redo_entry(&self) -> Option<crate::UndoEntry> {
435 self.content.lock().unwrap().redo_stack.pop()
436 }
437
438 pub fn peek_undo_timestamp(&self) -> Option<std::time::SystemTime> {
439 self.content
440 .lock()
441 .unwrap()
442 .undo_stack
443 .last()
444 .map(|e| e.timestamp)
445 }
446
447 pub fn peek_redo_timestamp(&self) -> Option<std::time::SystemTime> {
448 self.content
449 .lock()
450 .unwrap()
451 .redo_stack
452 .last()
453 .map(|e| e.timestamp)
454 }
455
456 pub fn clear_undo_redo(&self) {
457 let mut c = self.content.lock().unwrap();
458 c.undo_stack.clear();
459 c.redo_stack.clear();
460 }
461
462 pub fn clear_redo(&self) {
463 self.content.lock().unwrap().redo_stack.clear();
464 }
465
466 pub fn cap_undo(&self, cap: usize) {
467 if cap > 0 {
468 let mut c = self.content.lock().unwrap();
469 let len = c.undo_stack.len();
470 if len > cap {
471 c.undo_stack.drain(..len - cap);
472 }
473 }
474 }
475
476 pub fn content_dirty(&self) -> bool {
477 self.content.lock().unwrap().content_dirty
478 }
479
480 pub fn set_content_dirty(&self, v: bool) {
481 self.content.lock().unwrap().content_dirty = v;
482 }
483
484 pub fn mark_content_dirty(&self) {
485 let mut c = self.content.lock().unwrap();
486 c.content_dirty = true;
487 c.cached_editor_content = None;
488 }
489
490 pub fn take_dirty(&self) -> bool {
491 let mut c = self.content.lock().unwrap();
492 let v = c.content_dirty;
493 c.content_dirty = false;
494 v
495 }
496
497 pub fn cached_editor_content(&self) -> Option<std::sync::Arc<String>> {
498 self.content.lock().unwrap().cached_editor_content.clone()
499 }
500
501 pub fn set_cached_editor_content(&self, arc: std::sync::Arc<String>) {
502 self.content.lock().unwrap().cached_editor_content = Some(arc);
503 }
504
505 pub fn push_fold_op(&self, op: crate::FoldOp) {
506 self.content.lock().unwrap().pending_fold_ops.push(op);
507 }
508
509 pub fn take_fold_ops(&self) -> Vec<crate::FoldOp> {
510 std::mem::take(&mut self.content.lock().unwrap().pending_fold_ops)
511 }
512
513 pub fn extend_change_log(&self, edits: impl IntoIterator<Item = crate::EngineEdit>) {
514 self.content.lock().unwrap().change_log.extend(edits);
515 }
516
517 pub fn take_change_log(&self) -> Vec<crate::EngineEdit> {
518 std::mem::take(&mut self.content.lock().unwrap().change_log)
519 }
520
521 pub fn extend_pending_content_edits(
522 &self,
523 edits: impl IntoIterator<Item = crate::ContentEdit>,
524 ) {
525 self.content
526 .lock()
527 .unwrap()
528 .pending_content_edits
529 .extend(edits);
530 }
531
532 pub fn push_pending_content_edit(&self, edit: crate::ContentEdit) {
533 self.content
534 .lock()
535 .unwrap()
536 .pending_content_edits
537 .push(edit);
538 }
539
540 pub fn take_pending_content_edits(&self) -> Vec<crate::ContentEdit> {
541 std::mem::take(&mut self.content.lock().unwrap().pending_content_edits)
542 }
543
544 pub fn clear_pending_content_edits(&self) {
545 self.content.lock().unwrap().pending_content_edits.clear();
546 }
547
548 pub fn pending_content_reset(&self) -> bool {
549 self.content.lock().unwrap().pending_content_reset
550 }
551
552 pub fn set_pending_content_reset(&self, v: bool) {
553 self.content.lock().unwrap().pending_content_reset = v;
554 }
555
556 pub fn take_pending_content_reset(&self) -> bool {
557 let mut c = self.content.lock().unwrap();
558 let v = c.pending_content_reset;
559 c.pending_content_reset = false;
560 v
561 }
562
563 pub fn mark(&self, c: char) -> Option<(usize, usize)> {
564 self.content_lock().marks.get(&c).copied()
565 }
566 pub fn set_mark(&mut self, c: char, pos: (usize, usize)) {
567 self.content_lock_mut().marks.insert(c, pos);
568 }
569 pub fn clear_mark(&mut self, c: char) {
570 self.content_lock_mut().marks.remove(&c);
571 }
572 pub fn marks_cloned(&self) -> std::collections::BTreeMap<char, (usize, usize)> {
573 self.content_lock().marks.clone()
574 }
575 pub fn set_marks(&mut self, marks: std::collections::BTreeMap<char, (usize, usize)>) {
576 self.content_lock_mut().marks = marks;
577 }
578 pub fn rebase_marks(
582 &mut self,
583 edit_start: usize,
584 drop_end: usize,
585 shift_threshold: usize,
586 delta: isize,
587 ) {
588 let mut c = self.content_lock_mut();
589 let mut to_drop: Vec<char> = Vec::new();
590 for (ch, (row, _col)) in c.marks.iter_mut() {
591 if (edit_start..drop_end).contains(row) {
592 to_drop.push(*ch);
593 } else if *row >= shift_threshold {
594 *row = ((*row as isize) + delta).max(0) as usize;
595 }
596 }
597 for ch in to_drop {
598 c.marks.remove(&ch);
599 }
600 }
601 pub fn syntax_fold_ranges_cloned(&self) -> Vec<(usize, usize)> {
602 self.content_lock().syntax_fold_ranges.clone()
603 }
604 pub fn set_syntax_fold_ranges(&mut self, ranges: Vec<(usize, usize)>) {
605 self.content_lock_mut().syntax_fold_ranges = ranges;
606 }
607}
608
609pub fn rope_line_str(rope: &ropey::Rope, row: usize) -> String {
614 let mut s = rope.line(row).to_string();
615 if s.ends_with('\n') {
617 s.pop();
618 }
619 s
620}
621
622pub fn rope_line_bytes(rope: &ropey::Rope, row: usize) -> usize {
624 let slice = rope.line(row);
625 let bytes = slice.len_bytes();
626 if row + 1 < rope.len_lines() && bytes > 0 {
628 bytes - 1
629 } else {
630 bytes
631 }
632}
633
634pub(crate) fn rope_line_char_count(rope: &ropey::Rope, row: usize) -> usize {
636 let slice = rope.line(row);
637 let chars = slice.len_chars();
638 if row + 1 < rope.len_lines() && chars > 0 {
640 chars - 1
641 } else {
642 chars
643 }
644}
645
646pub(crate) fn pos_to_char_idx(rope: &ropey::Rope, row: usize, col: usize) -> usize {
651 let row = row.min(rope.len_lines().saturating_sub(1));
652 let line_start = rope.line_to_char(row);
653 let line_char_count = rope_line_char_count(rope, row);
654 line_start + col.min(line_char_count)
655}
656
657#[cfg(test)]
658mod tests {
659 use super::*;
660
661 #[test]
662 fn new_has_one_empty_row() {
663 let b = View::new();
664 assert_eq!(b.row_count(), 1);
665 assert_eq!(rope_line_str(&b.rope(), 0), "");
666 assert_eq!(b.cursor(), Position::default());
667 }
668
669 #[test]
670 fn from_str_splits_on_newline() {
671 let b = View::from_str("foo\nbar\nbaz");
672 assert_eq!(b.row_count(), 3);
673 assert_eq!(rope_line_str(&b.rope(), 0), "foo");
674 assert_eq!(rope_line_str(&b.rope(), 2), "baz");
675 }
676
677 #[test]
678 fn from_str_trailing_newline_keeps_empty_row() {
679 let b = View::from_str("foo\n");
680 assert_eq!(b.row_count(), 2);
681 assert_eq!(rope_line_str(&b.rope(), 1), "");
682 }
683
684 #[test]
685 fn from_str_empty_input_keeps_one_row() {
686 let b = View::from_str("");
687 assert_eq!(b.row_count(), 1);
688 assert_eq!(rope_line_str(&b.rope(), 0), "");
689 }
690
691 #[test]
692 fn as_string_round_trips() {
693 let b = View::from_str("a\nb\nc");
694 assert_eq!(b.as_string(), "a\nb\nc");
695 }
696
697 #[test]
698 fn dirty_gen_starts_at_zero() {
699 assert_eq!(View::new().dirty_gen(), 0);
700 }
701
702 fn vp_wrap(width: u16, height: u16) -> Viewport {
703 Viewport {
704 top_row: 0,
705 top_col: 0,
706 width,
707 height,
708 wrap: crate::Wrap::Char,
709 text_width: width,
710 tab_width: 0,
711 }
712 }
713
714 #[test]
715 fn ensure_cursor_visible_wrap_scrolls_when_cursor_below_screen() {
716 let mut b = View::from_str("aaaaaaaaaa\nb\nc");
717 let mut v = vp_wrap(4, 3);
718 b.set_cursor(Position::new(2, 0));
719 b.ensure_cursor_visible(&mut v);
720 assert_eq!(v.top_row, 1);
721 }
722
723 #[test]
724 fn ensure_cursor_visible_wrap_no_scroll_when_visible() {
725 let mut b = View::from_str("aaaaaaaaaa\nb");
726 let mut v = vp_wrap(4, 4);
727 b.set_cursor(Position::new(0, 5));
728 b.ensure_cursor_visible(&mut v);
729 assert_eq!(v.top_row, 0);
730 }
731
732 #[test]
733 fn ensure_cursor_visible_wrap_snaps_top_when_cursor_above() {
734 let mut b = View::from_str("a\nb\nc\nd\ne");
735 let mut v = vp_wrap(4, 2);
736 v.top_row = 3;
737 b.set_cursor(Position::new(1, 0));
738 b.ensure_cursor_visible(&mut v);
739 assert_eq!(v.top_row, 1);
740 }
741
742 #[test]
743 fn screen_rows_between_sums_segments_under_wrap() {
744 let b = View::from_str("aaaaaaaaa\nb\n");
745 let v = vp_wrap(4, 0);
746 assert_eq!(b.screen_rows_between(&v, 0, 0), 3);
747 assert_eq!(b.screen_rows_between(&v, 0, 1), 4);
748 assert_eq!(b.screen_rows_between(&v, 0, 2), 5);
749 assert_eq!(b.screen_rows_between(&v, 1, 2), 2);
750 }
751
752 #[test]
753 fn screen_rows_between_one_per_doc_row_when_wrap_off() {
754 let b = View::from_str("aaaaa\nb\nc");
755 let v = Viewport::default();
756 assert_eq!(b.screen_rows_between(&v, 0, 2), 3);
757 }
758
759 #[test]
760 fn max_top_for_height_walks_back_until_height_reached() {
761 let b = View::from_str("a\nb\nc\nd\neeeeeeee");
762 let v = vp_wrap(4, 0);
763 assert_eq!(b.max_top_for_height(&v, 4), 2);
764 assert_eq!(b.max_top_for_height(&v, 99), 0);
765 }
766
767 #[test]
768 fn cursor_screen_row_returns_none_when_wrap_off() {
769 let b = View::from_str("a");
770 let v = Viewport::default();
771 assert!(b.cursor_screen_row(&v).is_none());
772 }
773
774 #[test]
775 fn cursor_screen_row_under_wrap() {
776 let mut b = View::from_str("aaaaaaaaaa\nb");
777 let v = vp_wrap(4, 0);
778 b.set_cursor(Position::new(0, 5));
779 assert_eq!(b.cursor_screen_row(&v), Some(1));
780 b.set_cursor(Position::new(1, 0));
781 assert_eq!(b.cursor_screen_row(&v), Some(3));
782 }
783
784 #[test]
788 fn cursor_screen_row_survives_shrink_from_other_view() {
789 let a = View::from_str("a\nb\nc\nd\ne");
790 let arc = a.content_arc();
791 let mut view_a = View::new_view(Arc::clone(&arc));
792 let mut view_b = View::new_view(Arc::clone(&arc));
793 view_b.set_cursor(Position::new(4, 0));
794 view_a.replace_all("a");
796 let v = vp_wrap(4, 3);
797 assert_eq!(view_b.cursor_screen_row(&v), Some(0));
798 let mut v2 = vp_wrap(4, 3);
799 view_b.ensure_cursor_visible(&mut v2); }
801
802 #[test]
803 fn ensure_cursor_visible_falls_back_when_wrap_disabled() {
804 let mut b = View::from_str("a\nb\nc\nd\ne");
805 let mut v = Viewport {
806 top_row: 0,
807 top_col: 0,
808 width: 4,
809 height: 2,
810 wrap: crate::Wrap::None,
811 text_width: 4,
812 tab_width: 0,
813 };
814 b.set_cursor(Position::new(4, 0));
815 b.ensure_cursor_visible(&mut v);
816 assert_eq!(v.top_row, 3);
817 }
818
819 #[test]
825 fn undo_stack_shared_across_views() {
826 use crate::UndoEntry;
827 use std::time::SystemTime;
828
829 let a = View::from_str("hello");
830 let arc = a.content_arc();
831 let view_a = View::new_view(Arc::clone(&arc));
832 let view_b = View::new_view(Arc::clone(&arc));
833
834 assert!(view_a.undo_stack_is_empty());
835 assert_eq!(view_a.undo_stack_len(), 0);
836
837 view_a.push_undo_entry(UndoEntry {
838 rope: view_a.rope(),
839 cursor: (0, 0),
840 timestamp: SystemTime::UNIX_EPOCH,
841 marks: Default::default(),
842 });
843
844 assert_eq!(view_b.undo_stack_len(), 1);
846 assert!(!view_b.undo_stack_is_empty());
847 }
848
849 #[test]
851 fn redo_stack_shared_across_views() {
852 use crate::UndoEntry;
853 use std::time::SystemTime;
854
855 let a = View::from_str("world");
856 let arc = a.content_arc();
857 let view_a = View::new_view(Arc::clone(&arc));
858 let view_b = View::new_view(Arc::clone(&arc));
859
860 assert!(view_a.redo_stack_is_empty());
861
862 view_b.push_redo_entry(UndoEntry {
863 rope: view_b.rope(),
864 cursor: (0, 2),
865 timestamp: SystemTime::UNIX_EPOCH,
866 marks: Default::default(),
867 });
868
869 let entry = view_a.pop_redo_entry();
870 assert!(entry.is_some());
871 assert_eq!(entry.unwrap().cursor, (0, 2));
872 }
873
874 #[test]
876 fn clear_undo_redo_shared_across_views() {
877 use crate::UndoEntry;
878 use std::time::SystemTime;
879
880 let a = View::from_str("abc");
881 let arc = a.content_arc();
882 let view_a = View::new_view(Arc::clone(&arc));
883 let view_b = View::new_view(Arc::clone(&arc));
884
885 view_a.push_undo_entry(UndoEntry {
886 rope: view_a.rope(),
887 cursor: (0, 0),
888 timestamp: SystemTime::UNIX_EPOCH,
889 marks: Default::default(),
890 });
891 view_a.push_redo_entry(UndoEntry {
892 rope: view_a.rope(),
893 cursor: (0, 1),
894 timestamp: SystemTime::UNIX_EPOCH,
895 marks: Default::default(),
896 });
897
898 view_b.clear_undo_redo();
899 assert!(view_a.undo_stack_is_empty());
900 assert!(view_a.redo_stack_is_empty());
901 }
902
903 #[test]
905 fn content_dirty_shared_across_views() {
906 let a = View::from_str("test");
907 let arc = a.content_arc();
908 let view_a = View::new_view(Arc::clone(&arc));
909 let view_b = View::new_view(Arc::clone(&arc));
910
911 assert!(!view_a.content_dirty());
912
913 view_b.mark_content_dirty();
914 assert!(view_a.content_dirty());
915
916 let taken = view_a.take_dirty();
917 assert!(taken);
918 assert!(!view_b.content_dirty());
919 }
920
921 #[test]
923 fn pending_fold_ops_shared_across_views() {
924 let a = View::from_str("a\nb\nc");
925 let arc = a.content_arc();
926 let view_a = View::new_view(Arc::clone(&arc));
927 let view_b = View::new_view(Arc::clone(&arc));
928
929 view_a.push_fold_op(crate::FoldOp::Add {
930 start_row: 0,
931 end_row: 1,
932 closed: true,
933 });
934
935 let ops = view_b.take_fold_ops();
936 assert_eq!(ops.len(), 1);
937 assert!(matches!(
938 ops[0],
939 crate::FoldOp::Add {
940 start_row: 0,
941 end_row: 1,
942 closed: true
943 }
944 ));
945 }
946
947 #[test]
949 fn pending_content_reset_shared_across_views() {
950 let a = View::from_str("x");
951 let arc = a.content_arc();
952 let view_a = View::new_view(Arc::clone(&arc));
953 let view_b = View::new_view(Arc::clone(&arc));
954
955 assert!(!view_a.pending_content_reset());
956 view_b.set_pending_content_reset(true);
957 assert!(view_a.pending_content_reset());
958 let taken = view_a.take_pending_content_reset();
959 assert!(taken);
960 assert!(!view_b.pending_content_reset());
961 }
962
963 #[test]
968 fn buffer_views_independent_cursors() {
969 let a = View::from_str("hello\nworld");
970 let arc = a.content_arc();
971 let mut view_a = View::new_view(Arc::clone(&arc));
972 let mut view_b = View::new_view(Arc::clone(&arc));
973
974 view_a.set_cursor(Position::new(1, 3));
975 assert_eq!(view_b.cursor(), Position::new(0, 0));
977
978 view_b.set_cursor(Position::new(0, 2));
979 assert_eq!(view_a.cursor(), Position::new(1, 3));
981 }
982
983 #[test]
985 fn buffer_views_share_content() {
986 use crate::edit::Edit;
987
988 let a = View::from_str("foo");
989 let arc = a.content_arc();
990 let mut view_a = View::new_view(Arc::clone(&arc));
991 let view_b = View::new_view(Arc::clone(&arc));
992
993 view_a.apply_edit(Edit::InsertStr {
994 at: Position::new(0, 3),
995 text: "bar".into(),
996 });
997
998 assert_eq!(rope_line_str(&view_a.rope(), 0), "foobar");
999 assert_eq!(rope_line_str(&view_b.rope(), 0), "foobar");
1000 }
1001}
1002
1003#[cfg(test)]
1004mod marks_shared_content_tests {
1005 use super::*;
1006
1007 #[test]
1008 fn marks_shared_across_views() {
1009 let a = View::from_str("hello\nworld");
1011 let content = a.content_arc();
1012 let mut view_a = View::new_view(std::sync::Arc::clone(&content));
1013 let view_b = View::new_view(std::sync::Arc::clone(&content));
1014
1015 view_a.set_mark('x', (1, 3));
1017
1018 assert_eq!(view_b.mark('x'), Some((1, 3)));
1020 }
1021
1022 #[test]
1023 fn syntax_fold_ranges_shared_across_views() {
1024 let a = View::from_str("fn foo() {\n bar();\n}");
1025 let content = a.content_arc();
1026 let mut view_a = View::new_view(std::sync::Arc::clone(&content));
1027 let view_b = View::new_view(std::sync::Arc::clone(&content));
1028
1029 view_a.set_syntax_fold_ranges(vec![(0, 2)]);
1030
1031 assert_eq!(view_b.syntax_fold_ranges_cloned(), vec![(0, 2)]);
1032 }
1033}