1use std::sync::{Arc, Mutex, MutexGuard};
2
3use crate::content::Content;
4use crate::{Position, Viewport};
5
6pub struct Buffer {
44 pub(crate) content: Arc<Mutex<Content>>,
46 cursor: Position,
49}
50
51impl Default for Buffer {
52 fn default() -> Self {
53 Self::new()
54 }
55}
56
57impl Buffer {
58 pub fn new() -> Self {
62 Self {
63 content: Arc::new(Mutex::new(Content::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(Content::from_str(text))),
76 cursor: Position::default(),
77 }
78 }
79
80 pub fn new_view(content: Arc<Mutex<Content>>) -> Self {
100 Self {
101 content,
102 cursor: Position::default(),
103 }
104 }
105
106 pub fn content_arc(&self) -> Arc<Mutex<Content>> {
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 loop {
174 let csr = self.cursor_screen_row_from(viewport, viewport.top_row);
175 match csr {
176 Some(row) if row < height => break,
177 _ => {}
178 }
179 let next = {
180 let c = self.content.lock().unwrap();
181 let mut n = viewport.top_row + 1;
182 while n <= cursor.row && c.folds.iter().any(|f| f.hides(n)) {
183 n += 1;
184 }
185 n
186 };
187 if next > cursor.row {
188 viewport.top_row = cursor.row;
189 break;
190 }
191 viewport.top_row = next;
192 }
193 viewport.top_col = 0;
194 }
195
196 pub fn cursor_screen_row(&self, viewport: &Viewport) -> Option<usize> {
198 if matches!(viewport.wrap, crate::Wrap::None) || viewport.text_width == 0 {
199 return None;
200 }
201 self.cursor_screen_row_from(viewport, viewport.top_row)
202 }
203
204 pub fn screen_rows_between(&self, viewport: &Viewport, start: usize, end: usize) -> usize {
206 if start > end {
207 return 0;
208 }
209 let c = self.content.lock().unwrap();
210 let n = c.text.len_lines();
211 let last = n.saturating_sub(1);
212 let end = end.min(last);
213 let v = *viewport;
214 let mut total = 0usize;
215 for r in start..=end {
216 if c.folds.iter().any(|f| f.hides(r)) {
217 continue;
218 }
219 if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
220 total += 1;
221 } else {
222 let line = rope_line_str(&c.text, r);
223 total += crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len();
224 }
225 }
226 total
227 }
228
229 pub fn max_top_for_height(&self, viewport: &Viewport, height: usize) -> usize {
232 if height == 0 {
233 return 0;
234 }
235 let c = self.content.lock().unwrap();
236 let n = c.text.len_lines();
237 let last = n.saturating_sub(1);
238 let mut total = 0usize;
239 let mut row = last;
240 loop {
241 if !c.folds.iter().any(|f| f.hides(row)) {
242 let v = *viewport;
243 total += if matches!(v.wrap, crate::Wrap::None) || v.text_width == 0 {
244 1
245 } else {
246 let line = rope_line_str(&c.text, row);
247 crate::wrap::wrap_segments(&line, v.text_width, v.wrap).len()
248 };
249 }
250 if total >= height {
251 return row;
252 }
253 if row == 0 {
254 return 0;
255 }
256 row -= 1;
257 }
258 }
259
260 pub fn clamp_position(&self, pos: Position) -> Position {
262 let c = self.content.lock().unwrap();
263 let n = c.text.len_lines();
264 let last_row = n.saturating_sub(1);
265 let row = pos.row.min(last_row);
266 let line_chars = rope_line_char_count(&c.text, row);
267 let col = pos.col.min(line_chars);
268 Position::new(row, col)
269 }
270
271 pub fn replace_all(&mut self, text: &str) {
274 let new_cursor = {
275 let mut c = self.content.lock().unwrap();
276 c.text = ropey::Rope::from_str(text);
277 let n = c.text.len_lines();
278 let last_row = n.saturating_sub(1);
279 let row = self.cursor.row.min(last_row);
280 let line_chars = rope_line_char_count(&c.text, row);
281 let col = self.cursor.col.min(line_chars);
282 c.dirty_gen = c.dirty_gen.wrapping_add(1);
283 c.cached_joined = None;
284 c.cached_byte_len = None;
285 Position::new(row, col)
286 };
287 self.cursor = new_cursor;
288 }
289
290 pub(crate) fn dirty_gen_bump(&mut self) {
294 let mut c = self.content.lock().unwrap();
295 c.dirty_gen = c.dirty_gen.wrapping_add(1);
296 c.cached_joined = None;
297 c.cached_byte_len = None;
298 }
299
300 pub fn byte_len(&self) -> usize {
306 let mut c = self.content.lock().unwrap();
307 let dg = c.dirty_gen;
308 if let Some((cached_dg, len)) = c.cached_byte_len
309 && cached_dg == dg
310 {
311 return len;
312 }
313 let total = c.text.len_bytes();
314 c.cached_byte_len = Some((dg, total));
315 total
316 }
317
318 pub fn content_joined(&self) -> std::sync::Arc<String> {
327 let mut c = self.content.lock().unwrap();
328 let dg = c.dirty_gen;
329 if let Some((cached_dg, ref s)) = c.cached_joined
330 && cached_dg == dg
331 {
332 return std::sync::Arc::clone(s);
333 }
334 let joined = std::sync::Arc::new(c.text.to_string());
335 c.cached_joined = Some((dg, std::sync::Arc::clone(&joined)));
336 joined
337 }
338
339 pub fn rope(&self) -> ropey::Rope {
348 self.content.lock().unwrap().text.clone()
349 }
350
351 pub(crate) fn content_lock(&self) -> MutexGuard<'_, Content> {
353 self.content.lock().unwrap()
354 }
355
356 pub(crate) fn content_lock_mut(&mut self) -> MutexGuard<'_, Content> {
358 self.content.lock().unwrap()
359 }
360
361 fn cursor_screen_row_from(&self, viewport: &Viewport, top: usize) -> Option<usize> {
364 let cursor = self.cursor;
365 if cursor.row < top {
366 return None;
367 }
368 let c = self.content.lock().unwrap();
369 let v = *viewport;
370 let mut screen = 0usize;
371 for r in top..=cursor.row {
372 if c.folds.iter().any(|f| f.hides(r)) {
373 continue;
374 }
375 let line = rope_line_str(&c.text, r);
376 let segs = crate::wrap::wrap_segments(&line, v.text_width, v.wrap);
377 if r == cursor.row {
378 let seg_idx = crate::wrap::segment_for_col(&segs, cursor.col);
379 return Some(screen + seg_idx);
380 }
381 screen += segs.len();
382 }
383 None
384 }
385
386 pub fn undo_stack_is_empty(&self) -> bool {
389 self.content.lock().unwrap().undo_stack.is_empty()
390 }
391
392 pub fn redo_stack_is_empty(&self) -> bool {
393 self.content.lock().unwrap().redo_stack.is_empty()
394 }
395
396 pub fn undo_stack_len(&self) -> usize {
397 self.content.lock().unwrap().undo_stack.len()
398 }
399
400 pub fn push_undo_entry(&self, entry: crate::UndoEntry) {
401 self.content.lock().unwrap().undo_stack.push(entry);
402 }
403
404 pub fn push_redo_entry(&self, entry: crate::UndoEntry) {
405 self.content.lock().unwrap().redo_stack.push(entry);
406 }
407
408 pub fn pop_undo_entry(&self) -> Option<crate::UndoEntry> {
409 self.content.lock().unwrap().undo_stack.pop()
410 }
411
412 pub fn pop_redo_entry(&self) -> Option<crate::UndoEntry> {
413 self.content.lock().unwrap().redo_stack.pop()
414 }
415
416 pub fn peek_undo_timestamp(&self) -> Option<std::time::SystemTime> {
417 self.content
418 .lock()
419 .unwrap()
420 .undo_stack
421 .last()
422 .map(|e| e.timestamp)
423 }
424
425 pub fn peek_redo_timestamp(&self) -> Option<std::time::SystemTime> {
426 self.content
427 .lock()
428 .unwrap()
429 .redo_stack
430 .last()
431 .map(|e| e.timestamp)
432 }
433
434 pub fn clear_undo_redo(&self) {
435 let mut c = self.content.lock().unwrap();
436 c.undo_stack.clear();
437 c.redo_stack.clear();
438 }
439
440 pub fn clear_redo(&self) {
441 self.content.lock().unwrap().redo_stack.clear();
442 }
443
444 pub fn cap_undo(&self, cap: usize) {
445 if cap > 0 {
446 let mut c = self.content.lock().unwrap();
447 let len = c.undo_stack.len();
448 if len > cap {
449 c.undo_stack.drain(..len - cap);
450 }
451 }
452 }
453
454 pub fn content_dirty(&self) -> bool {
455 self.content.lock().unwrap().content_dirty
456 }
457
458 pub fn set_content_dirty(&self, v: bool) {
459 self.content.lock().unwrap().content_dirty = v;
460 }
461
462 pub fn mark_content_dirty(&self) {
463 let mut c = self.content.lock().unwrap();
464 c.content_dirty = true;
465 c.cached_editor_content = None;
466 }
467
468 pub fn take_dirty(&self) -> bool {
469 let mut c = self.content.lock().unwrap();
470 let v = c.content_dirty;
471 c.content_dirty = false;
472 v
473 }
474
475 pub fn cached_editor_content(&self) -> Option<std::sync::Arc<String>> {
476 self.content.lock().unwrap().cached_editor_content.clone()
477 }
478
479 pub fn set_cached_editor_content(&self, arc: std::sync::Arc<String>) {
480 self.content.lock().unwrap().cached_editor_content = Some(arc);
481 }
482
483 pub fn push_fold_op(&self, op: crate::FoldOp) {
484 self.content.lock().unwrap().pending_fold_ops.push(op);
485 }
486
487 pub fn take_fold_ops(&self) -> Vec<crate::FoldOp> {
488 std::mem::take(&mut self.content.lock().unwrap().pending_fold_ops)
489 }
490
491 pub fn extend_change_log(&self, edits: impl IntoIterator<Item = crate::EngineEdit>) {
492 self.content.lock().unwrap().change_log.extend(edits);
493 }
494
495 pub fn take_change_log(&self) -> Vec<crate::EngineEdit> {
496 std::mem::take(&mut self.content.lock().unwrap().change_log)
497 }
498
499 pub fn extend_pending_content_edits(
500 &self,
501 edits: impl IntoIterator<Item = crate::ContentEdit>,
502 ) {
503 self.content
504 .lock()
505 .unwrap()
506 .pending_content_edits
507 .extend(edits);
508 }
509
510 pub fn push_pending_content_edit(&self, edit: crate::ContentEdit) {
511 self.content
512 .lock()
513 .unwrap()
514 .pending_content_edits
515 .push(edit);
516 }
517
518 pub fn take_pending_content_edits(&self) -> Vec<crate::ContentEdit> {
519 std::mem::take(&mut self.content.lock().unwrap().pending_content_edits)
520 }
521
522 pub fn clear_pending_content_edits(&self) {
523 self.content.lock().unwrap().pending_content_edits.clear();
524 }
525
526 pub fn pending_content_reset(&self) -> bool {
527 self.content.lock().unwrap().pending_content_reset
528 }
529
530 pub fn set_pending_content_reset(&self, v: bool) {
531 self.content.lock().unwrap().pending_content_reset = v;
532 }
533
534 pub fn take_pending_content_reset(&self) -> bool {
535 let mut c = self.content.lock().unwrap();
536 let v = c.pending_content_reset;
537 c.pending_content_reset = false;
538 v
539 }
540
541 pub fn mark(&self, c: char) -> Option<(usize, usize)> {
542 self.content_lock().marks.get(&c).copied()
543 }
544 pub fn set_mark(&mut self, c: char, pos: (usize, usize)) {
545 self.content_lock_mut().marks.insert(c, pos);
546 }
547 pub fn clear_mark(&mut self, c: char) {
548 self.content_lock_mut().marks.remove(&c);
549 }
550 pub fn marks_cloned(&self) -> std::collections::BTreeMap<char, (usize, usize)> {
551 self.content_lock().marks.clone()
552 }
553 pub fn set_marks(&mut self, marks: std::collections::BTreeMap<char, (usize, usize)>) {
554 self.content_lock_mut().marks = marks;
555 }
556 pub fn rebase_marks(
560 &mut self,
561 edit_start: usize,
562 drop_end: usize,
563 shift_threshold: usize,
564 delta: isize,
565 ) {
566 let mut c = self.content_lock_mut();
567 let mut to_drop: Vec<char> = Vec::new();
568 for (ch, (row, _col)) in c.marks.iter_mut() {
569 if (edit_start..drop_end).contains(row) {
570 to_drop.push(*ch);
571 } else if *row >= shift_threshold {
572 *row = ((*row as isize) + delta).max(0) as usize;
573 }
574 }
575 for ch in to_drop {
576 c.marks.remove(&ch);
577 }
578 }
579 pub fn syntax_fold_ranges_cloned(&self) -> Vec<(usize, usize)> {
580 self.content_lock().syntax_fold_ranges.clone()
581 }
582 pub fn set_syntax_fold_ranges(&mut self, ranges: Vec<(usize, usize)>) {
583 self.content_lock_mut().syntax_fold_ranges = ranges;
584 }
585}
586
587pub fn rope_line_str(rope: &ropey::Rope, row: usize) -> String {
592 let mut s = rope.line(row).to_string();
593 if s.ends_with('\n') {
595 s.pop();
596 }
597 s
598}
599
600pub fn rope_line_bytes(rope: &ropey::Rope, row: usize) -> usize {
602 let slice = rope.line(row);
603 let bytes = slice.len_bytes();
604 if row + 1 < rope.len_lines() && bytes > 0 {
606 bytes - 1
607 } else {
608 bytes
609 }
610}
611
612pub(crate) fn rope_line_char_count(rope: &ropey::Rope, row: usize) -> usize {
614 let slice = rope.line(row);
615 let chars = slice.len_chars();
616 if row + 1 < rope.len_lines() && chars > 0 {
618 chars - 1
619 } else {
620 chars
621 }
622}
623
624pub(crate) fn pos_to_char_idx(rope: &ropey::Rope, row: usize, col: usize) -> usize {
626 let line_start = rope.line_to_char(row);
627 let line_char_count = rope_line_char_count(rope, row);
628 line_start + col.min(line_char_count)
629}
630
631#[cfg(test)]
632mod tests {
633 use super::*;
634
635 #[test]
636 fn new_has_one_empty_row() {
637 let b = Buffer::new();
638 assert_eq!(b.row_count(), 1);
639 assert_eq!(rope_line_str(&b.rope(), 0), "");
640 assert_eq!(b.cursor(), Position::default());
641 }
642
643 #[test]
644 fn from_str_splits_on_newline() {
645 let b = Buffer::from_str("foo\nbar\nbaz");
646 assert_eq!(b.row_count(), 3);
647 assert_eq!(rope_line_str(&b.rope(), 0), "foo");
648 assert_eq!(rope_line_str(&b.rope(), 2), "baz");
649 }
650
651 #[test]
652 fn from_str_trailing_newline_keeps_empty_row() {
653 let b = Buffer::from_str("foo\n");
654 assert_eq!(b.row_count(), 2);
655 assert_eq!(rope_line_str(&b.rope(), 1), "");
656 }
657
658 #[test]
659 fn from_str_empty_input_keeps_one_row() {
660 let b = Buffer::from_str("");
661 assert_eq!(b.row_count(), 1);
662 assert_eq!(rope_line_str(&b.rope(), 0), "");
663 }
664
665 #[test]
666 fn as_string_round_trips() {
667 let b = Buffer::from_str("a\nb\nc");
668 assert_eq!(b.as_string(), "a\nb\nc");
669 }
670
671 #[test]
672 fn dirty_gen_starts_at_zero() {
673 assert_eq!(Buffer::new().dirty_gen(), 0);
674 }
675
676 fn vp_wrap(width: u16, height: u16) -> Viewport {
677 Viewport {
678 top_row: 0,
679 top_col: 0,
680 width,
681 height,
682 wrap: crate::Wrap::Char,
683 text_width: width,
684 tab_width: 0,
685 }
686 }
687
688 #[test]
689 fn ensure_cursor_visible_wrap_scrolls_when_cursor_below_screen() {
690 let mut b = Buffer::from_str("aaaaaaaaaa\nb\nc");
691 let mut v = vp_wrap(4, 3);
692 b.set_cursor(Position::new(2, 0));
693 b.ensure_cursor_visible(&mut v);
694 assert_eq!(v.top_row, 1);
695 }
696
697 #[test]
698 fn ensure_cursor_visible_wrap_no_scroll_when_visible() {
699 let mut b = Buffer::from_str("aaaaaaaaaa\nb");
700 let mut v = vp_wrap(4, 4);
701 b.set_cursor(Position::new(0, 5));
702 b.ensure_cursor_visible(&mut v);
703 assert_eq!(v.top_row, 0);
704 }
705
706 #[test]
707 fn ensure_cursor_visible_wrap_snaps_top_when_cursor_above() {
708 let mut b = Buffer::from_str("a\nb\nc\nd\ne");
709 let mut v = vp_wrap(4, 2);
710 v.top_row = 3;
711 b.set_cursor(Position::new(1, 0));
712 b.ensure_cursor_visible(&mut v);
713 assert_eq!(v.top_row, 1);
714 }
715
716 #[test]
717 fn screen_rows_between_sums_segments_under_wrap() {
718 let b = Buffer::from_str("aaaaaaaaa\nb\n");
719 let v = vp_wrap(4, 0);
720 assert_eq!(b.screen_rows_between(&v, 0, 0), 3);
721 assert_eq!(b.screen_rows_between(&v, 0, 1), 4);
722 assert_eq!(b.screen_rows_between(&v, 0, 2), 5);
723 assert_eq!(b.screen_rows_between(&v, 1, 2), 2);
724 }
725
726 #[test]
727 fn screen_rows_between_one_per_doc_row_when_wrap_off() {
728 let b = Buffer::from_str("aaaaa\nb\nc");
729 let v = Viewport::default();
730 assert_eq!(b.screen_rows_between(&v, 0, 2), 3);
731 }
732
733 #[test]
734 fn max_top_for_height_walks_back_until_height_reached() {
735 let b = Buffer::from_str("a\nb\nc\nd\neeeeeeee");
736 let v = vp_wrap(4, 0);
737 assert_eq!(b.max_top_for_height(&v, 4), 2);
738 assert_eq!(b.max_top_for_height(&v, 99), 0);
739 }
740
741 #[test]
742 fn cursor_screen_row_returns_none_when_wrap_off() {
743 let b = Buffer::from_str("a");
744 let v = Viewport::default();
745 assert!(b.cursor_screen_row(&v).is_none());
746 }
747
748 #[test]
749 fn cursor_screen_row_under_wrap() {
750 let mut b = Buffer::from_str("aaaaaaaaaa\nb");
751 let v = vp_wrap(4, 0);
752 b.set_cursor(Position::new(0, 5));
753 assert_eq!(b.cursor_screen_row(&v), Some(1));
754 b.set_cursor(Position::new(1, 0));
755 assert_eq!(b.cursor_screen_row(&v), Some(3));
756 }
757
758 #[test]
759 fn ensure_cursor_visible_falls_back_when_wrap_disabled() {
760 let mut b = Buffer::from_str("a\nb\nc\nd\ne");
761 let mut v = Viewport {
762 top_row: 0,
763 top_col: 0,
764 width: 4,
765 height: 2,
766 wrap: crate::Wrap::None,
767 text_width: 4,
768 tab_width: 0,
769 };
770 b.set_cursor(Position::new(4, 0));
771 b.ensure_cursor_visible(&mut v);
772 assert_eq!(v.top_row, 3);
773 }
774
775 #[test]
781 fn undo_stack_shared_across_views() {
782 use crate::UndoEntry;
783 use std::time::SystemTime;
784
785 let a = Buffer::from_str("hello");
786 let arc = a.content_arc();
787 let view_a = Buffer::new_view(Arc::clone(&arc));
788 let view_b = Buffer::new_view(Arc::clone(&arc));
789
790 assert!(view_a.undo_stack_is_empty());
791 assert_eq!(view_a.undo_stack_len(), 0);
792
793 view_a.push_undo_entry(UndoEntry {
794 rope: view_a.rope(),
795 cursor: (0, 0),
796 timestamp: SystemTime::UNIX_EPOCH,
797 });
798
799 assert_eq!(view_b.undo_stack_len(), 1);
801 assert!(!view_b.undo_stack_is_empty());
802 }
803
804 #[test]
806 fn redo_stack_shared_across_views() {
807 use crate::UndoEntry;
808 use std::time::SystemTime;
809
810 let a = Buffer::from_str("world");
811 let arc = a.content_arc();
812 let view_a = Buffer::new_view(Arc::clone(&arc));
813 let view_b = Buffer::new_view(Arc::clone(&arc));
814
815 assert!(view_a.redo_stack_is_empty());
816
817 view_b.push_redo_entry(UndoEntry {
818 rope: view_b.rope(),
819 cursor: (0, 2),
820 timestamp: SystemTime::UNIX_EPOCH,
821 });
822
823 let entry = view_a.pop_redo_entry();
824 assert!(entry.is_some());
825 assert_eq!(entry.unwrap().cursor, (0, 2));
826 }
827
828 #[test]
830 fn clear_undo_redo_shared_across_views() {
831 use crate::UndoEntry;
832 use std::time::SystemTime;
833
834 let a = Buffer::from_str("abc");
835 let arc = a.content_arc();
836 let view_a = Buffer::new_view(Arc::clone(&arc));
837 let view_b = Buffer::new_view(Arc::clone(&arc));
838
839 view_a.push_undo_entry(UndoEntry {
840 rope: view_a.rope(),
841 cursor: (0, 0),
842 timestamp: SystemTime::UNIX_EPOCH,
843 });
844 view_a.push_redo_entry(UndoEntry {
845 rope: view_a.rope(),
846 cursor: (0, 1),
847 timestamp: SystemTime::UNIX_EPOCH,
848 });
849
850 view_b.clear_undo_redo();
851 assert!(view_a.undo_stack_is_empty());
852 assert!(view_a.redo_stack_is_empty());
853 }
854
855 #[test]
857 fn content_dirty_shared_across_views() {
858 let a = Buffer::from_str("test");
859 let arc = a.content_arc();
860 let view_a = Buffer::new_view(Arc::clone(&arc));
861 let view_b = Buffer::new_view(Arc::clone(&arc));
862
863 assert!(!view_a.content_dirty());
864
865 view_b.mark_content_dirty();
866 assert!(view_a.content_dirty());
867
868 let taken = view_a.take_dirty();
869 assert!(taken);
870 assert!(!view_b.content_dirty());
871 }
872
873 #[test]
875 fn pending_fold_ops_shared_across_views() {
876 let a = Buffer::from_str("a\nb\nc");
877 let arc = a.content_arc();
878 let view_a = Buffer::new_view(Arc::clone(&arc));
879 let view_b = Buffer::new_view(Arc::clone(&arc));
880
881 view_a.push_fold_op(crate::FoldOp::Add {
882 start_row: 0,
883 end_row: 1,
884 closed: true,
885 });
886
887 let ops = view_b.take_fold_ops();
888 assert_eq!(ops.len(), 1);
889 assert!(matches!(
890 ops[0],
891 crate::FoldOp::Add {
892 start_row: 0,
893 end_row: 1,
894 closed: true
895 }
896 ));
897 }
898
899 #[test]
901 fn pending_content_reset_shared_across_views() {
902 let a = Buffer::from_str("x");
903 let arc = a.content_arc();
904 let view_a = Buffer::new_view(Arc::clone(&arc));
905 let view_b = Buffer::new_view(Arc::clone(&arc));
906
907 assert!(!view_a.pending_content_reset());
908 view_b.set_pending_content_reset(true);
909 assert!(view_a.pending_content_reset());
910 let taken = view_a.take_pending_content_reset();
911 assert!(taken);
912 assert!(!view_b.pending_content_reset());
913 }
914
915 #[test]
920 fn buffer_views_independent_cursors() {
921 let a = Buffer::from_str("hello\nworld");
922 let arc = a.content_arc();
923 let mut view_a = Buffer::new_view(Arc::clone(&arc));
924 let mut view_b = Buffer::new_view(Arc::clone(&arc));
925
926 view_a.set_cursor(Position::new(1, 3));
927 assert_eq!(view_b.cursor(), Position::new(0, 0));
929
930 view_b.set_cursor(Position::new(0, 2));
931 assert_eq!(view_a.cursor(), Position::new(1, 3));
933 }
934
935 #[test]
937 fn buffer_views_share_content() {
938 use crate::edit::Edit;
939
940 let a = Buffer::from_str("foo");
941 let arc = a.content_arc();
942 let mut view_a = Buffer::new_view(Arc::clone(&arc));
943 let view_b = Buffer::new_view(Arc::clone(&arc));
944
945 view_a.apply_edit(Edit::InsertStr {
946 at: Position::new(0, 3),
947 text: "bar".into(),
948 });
949
950 assert_eq!(rope_line_str(&view_a.rope(), 0), "foobar");
951 assert_eq!(rope_line_str(&view_b.rope(), 0), "foobar");
952 }
953}
954
955#[cfg(test)]
956mod marks_shared_content_tests {
957 use super::*;
958
959 #[test]
960 fn marks_shared_across_views() {
961 let a = Buffer::from_str("hello\nworld");
963 let content = a.content_arc();
964 let mut view_a = Buffer::new_view(std::sync::Arc::clone(&content));
965 let view_b = Buffer::new_view(std::sync::Arc::clone(&content));
966
967 view_a.set_mark('x', (1, 3));
969
970 assert_eq!(view_b.mark('x'), Some((1, 3)));
972 }
973
974 #[test]
975 fn syntax_fold_ranges_shared_across_views() {
976 let a = Buffer::from_str("fn foo() {\n bar();\n}");
977 let content = a.content_arc();
978 let mut view_a = Buffer::new_view(std::sync::Arc::clone(&content));
979 let view_b = Buffer::new_view(std::sync::Arc::clone(&content));
980
981 view_a.set_syntax_fold_ranges(vec![(0, 2)]);
982
983 assert_eq!(view_b.syntax_fold_ranges_cloned(), vec![(0, 2)]);
984 }
985}