1#![forbid(unsafe_code)]
2
3use crate::cursor::{CursorNavigator, CursorPosition};
30use crate::rope::Rope;
31
32#[derive(Debug, Clone)]
34enum EditOp {
35 Insert { byte_offset: usize, text: String },
36 Delete { byte_offset: usize, text: String },
37}
38
39impl EditOp {
40 fn inverse(&self) -> Self {
41 match self {
42 Self::Insert { byte_offset, text } => Self::Delete {
43 byte_offset: *byte_offset,
44 text: text.clone(),
45 },
46 Self::Delete { byte_offset, text } => Self::Insert {
47 byte_offset: *byte_offset,
48 text: text.clone(),
49 },
50 }
51 }
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub struct Selection {
57 pub anchor: CursorPosition,
59 pub head: CursorPosition,
61}
62
63impl Selection {
64 #[must_use]
66 pub fn byte_range(&self, nav: &CursorNavigator<'_>) -> (usize, usize) {
67 let a = nav.to_byte_index(self.anchor);
68 let b = nav.to_byte_index(self.head);
69 if a <= b { (a, b) } else { (b, a) }
70 }
71
72 #[must_use]
74 pub fn is_empty(&self) -> bool {
75 self.anchor == self.head
76 }
77}
78
79#[derive(Debug, Clone)]
85pub struct Editor {
86 rope: Rope,
88 cursor: CursorPosition,
90 selection: Option<Selection>,
92 undo_stack: Vec<(EditOp, CursorPosition)>,
94 redo_stack: Vec<(EditOp, CursorPosition)>,
96 max_history: usize,
98 current_undo_size: usize,
100 max_undo_size: usize,
102}
103
104impl Default for Editor {
105 fn default() -> Self {
106 Self::new()
107 }
108}
109
110impl Editor {
111 #[must_use]
113 pub fn new() -> Self {
114 Self {
115 rope: Rope::new(),
116 cursor: CursorPosition::default(),
117 selection: None,
118 undo_stack: Vec::new(),
119 redo_stack: Vec::new(),
120 max_history: 1000,
121 current_undo_size: 0,
122 max_undo_size: 10 * 1024 * 1024, }
124 }
125
126 #[must_use]
128 pub fn with_text(text: &str) -> Self {
129 let rope = Rope::from_text(text);
130 let nav = CursorNavigator::new(&rope);
131 let cursor = nav.document_end();
132 Self {
133 rope,
134 cursor,
135 selection: None,
136 undo_stack: Vec::new(),
137 redo_stack: Vec::new(),
138 max_history: 1000,
139 current_undo_size: 0,
140 max_undo_size: 10 * 1024 * 1024,
141 }
142 }
143
144 pub fn set_max_history(&mut self, max: usize) {
146 self.max_history = max;
147 }
148
149 pub fn set_max_undo_size(&mut self, bytes: usize) {
151 self.max_undo_size = bytes;
152 while self.current_undo_size > self.max_undo_size && !self.undo_stack.is_empty() {
154 let (op, _) = self.undo_stack.remove(0);
155 self.current_undo_size -= op.byte_len();
156 }
157 }
158
159 #[must_use]
161 pub fn text(&self) -> String {
162 self.rope.to_string()
163 }
164
165 #[must_use]
167 pub fn rope(&self) -> &Rope {
168 &self.rope
169 }
170
171 #[must_use]
173 pub fn cursor(&self) -> CursorPosition {
174 self.cursor
175 }
176
177 pub fn set_cursor(&mut self, pos: CursorPosition) {
179 let nav = CursorNavigator::new(&self.rope);
180 self.cursor = nav.clamp(pos);
181 self.selection = None;
182 }
183
184 #[must_use]
186 pub fn selection(&self) -> Option<Selection> {
187 self.selection
188 }
189
190 #[must_use]
192 pub fn can_undo(&self) -> bool {
193 !self.undo_stack.is_empty()
194 }
195
196 #[must_use]
198 pub fn can_redo(&self) -> bool {
199 !self.redo_stack.is_empty()
200 }
201
202 #[must_use]
204 pub fn is_empty(&self) -> bool {
205 self.rope.is_empty()
206 }
207
208 #[must_use]
210 pub fn line_count(&self) -> usize {
211 self.rope.len_lines()
212 }
213
214 #[must_use]
216 pub fn line_text(&self, line: usize) -> Option<String> {
217 self.rope.line(line).map(|cow| {
218 let s = cow.as_ref();
219 s.trim_end_matches('\n').trim_end_matches('\r').to_string()
220 })
221 }
222
223 pub fn insert_char(&mut self, ch: char) {
229 let mut buf = [0u8; 4];
230 let s = ch.encode_utf8(&mut buf);
231 self.insert_text(s);
232 }
233
234 pub fn insert_text(&mut self, text: &str) {
239 if text.is_empty() {
240 return;
241 }
242
243 let sanitized: String = text
245 .chars()
246 .filter(|&c| !c.is_control() || c == '\n' || c == '\t')
247 .collect();
248
249 if sanitized.is_empty() {
250 return;
251 }
252
253 self.delete_selection_inner();
254 let nav = CursorNavigator::new(&self.rope);
255 let byte_idx = nav.to_byte_index(self.cursor);
256 let char_idx = self.rope.byte_to_char(byte_idx);
257
258 self.push_undo(EditOp::Insert {
259 byte_offset: byte_idx,
260 text: sanitized.clone(),
261 });
262
263 self.rope.insert(char_idx, &sanitized);
264
265 let new_byte_idx = byte_idx + sanitized.len();
267 let nav = CursorNavigator::new(&self.rope);
268 self.cursor = nav.from_byte_index(new_byte_idx);
269 }
270
271 pub fn insert_newline(&mut self) {
273 self.insert_text("\n");
274 }
275
276 pub fn delete_backward(&mut self) -> bool {
284 if self.delete_selection_inner() {
285 return true;
286 }
287 let nav = CursorNavigator::new(&self.rope);
288 let old_pos = self.cursor;
289 let new_pos = nav.move_left(old_pos);
290
291 if new_pos == old_pos {
292 return false; }
294
295 let start_byte = nav.to_byte_index(new_pos);
296 let end_byte = nav.to_byte_index(old_pos);
297 let start_char = self.rope.byte_to_char(start_byte);
298 let end_char = self.rope.byte_to_char(end_byte);
299 let deleted = self.rope.slice(start_char..end_char).into_owned();
300
301 self.push_undo(EditOp::Delete {
302 byte_offset: start_byte,
303 text: deleted,
304 });
305
306 self.rope.remove(start_char..end_char);
307
308 let nav = CursorNavigator::new(&self.rope);
309 self.cursor = nav.from_byte_index(start_byte);
310 true
311 }
312
313 pub fn delete_forward(&mut self) -> bool {
317 if self.delete_selection_inner() {
318 return true;
319 }
320 let nav = CursorNavigator::new(&self.rope);
321 let old_pos = self.cursor;
322 let next_pos = nav.move_right(old_pos);
323
324 if next_pos == old_pos {
325 return false; }
327
328 let start_byte = nav.to_byte_index(old_pos);
329 let end_byte = nav.to_byte_index(next_pos);
330 let start_char = self.rope.byte_to_char(start_byte);
331 let end_char = self.rope.byte_to_char(end_byte);
332 let deleted = self.rope.slice(start_char..end_char).into_owned();
333
334 self.push_undo(EditOp::Delete {
335 byte_offset: start_byte,
336 text: deleted,
337 });
338
339 self.rope.remove(start_char..end_char);
340
341 let nav = CursorNavigator::new(&self.rope);
343 self.cursor = nav.clamp(self.cursor);
344 true
345 }
346
347 pub fn delete_word_backward(&mut self) -> bool {
351 if self.delete_selection_inner() {
352 return true;
353 }
354 let nav = CursorNavigator::new(&self.rope);
355 let old_pos = self.cursor;
356 let word_start = nav.move_word_left(old_pos);
357
358 if word_start == old_pos {
359 return false;
360 }
361
362 let start_byte = nav.to_byte_index(word_start);
363 let end_byte = nav.to_byte_index(old_pos);
364 let start_char = self.rope.byte_to_char(start_byte);
365 let end_char = self.rope.byte_to_char(end_byte);
366 let deleted = self.rope.slice(start_char..end_char).into_owned();
367
368 self.push_undo(EditOp::Delete {
369 byte_offset: start_byte,
370 text: deleted,
371 });
372
373 self.rope.remove(start_char..end_char);
374
375 let nav = CursorNavigator::new(&self.rope);
376 self.cursor = nav.from_byte_index(start_byte);
377 true
378 }
379
380 pub fn delete_to_end_of_line(&mut self) -> bool {
384 if self.delete_selection_inner() {
385 return true;
386 }
387 let nav = CursorNavigator::new(&self.rope);
388 let old_pos = self.cursor;
389 let line_end = nav.line_end(old_pos);
390
391 if line_end == old_pos {
392 return self.delete_forward();
394 }
395
396 let start_byte = nav.to_byte_index(old_pos);
397 let end_byte = nav.to_byte_index(line_end);
398 let start_char = self.rope.byte_to_char(start_byte);
399 let end_char = self.rope.byte_to_char(end_byte);
400 let deleted = self.rope.slice(start_char..end_char).into_owned();
401
402 self.push_undo(EditOp::Delete {
403 byte_offset: start_byte,
404 text: deleted,
405 });
406
407 self.rope.remove(start_char..end_char);
408
409 let nav = CursorNavigator::new(&self.rope);
410 self.cursor = nav.clamp(self.cursor);
411 true
412 }
413
414 fn push_undo(&mut self, op: EditOp) {
420 let op_len = op.byte_len();
421 self.undo_stack.push((op, self.cursor));
422 self.current_undo_size += op_len;
423
424 if self.undo_stack.len() > self.max_history {
426 if let Some((removed_op, _)) = self.undo_stack.first() {
427 self.current_undo_size =
428 self.current_undo_size.saturating_sub(removed_op.byte_len());
429 }
430 self.undo_stack.remove(0);
431 }
432
433 while self.current_undo_size > self.max_undo_size && !self.undo_stack.is_empty() {
435 let (removed_op, _) = self.undo_stack.remove(0);
436 self.current_undo_size = self.current_undo_size.saturating_sub(removed_op.byte_len());
437 }
438
439 self.redo_stack.clear();
440 }
441
442 pub fn undo(&mut self) -> bool {
444 let Some((op, cursor_before)) = self.undo_stack.pop() else {
445 return false;
446 };
447 self.current_undo_size = self.current_undo_size.saturating_sub(op.byte_len());
448 let inverse = op.inverse();
449 self.apply_op(&inverse);
450 self.redo_stack.push((inverse, self.cursor));
451 self.cursor = cursor_before;
452 self.selection = None;
453 true
454 }
455
456 pub fn redo(&mut self) -> bool {
458 let Some((op, cursor_before)) = self.redo_stack.pop() else {
459 return false;
460 };
461 let inverse = op.inverse();
462 self.apply_op(&inverse);
463
464 let op_len = inverse.byte_len();
465 self.undo_stack.push((inverse, self.cursor));
466 self.current_undo_size += op_len;
467
468 while self.current_undo_size > self.max_undo_size && !self.undo_stack.is_empty() {
470 let (removed_op, _) = self.undo_stack.remove(0);
471 self.current_undo_size = self.current_undo_size.saturating_sub(removed_op.byte_len());
472 }
473
474 self.cursor = cursor_before;
475 self.selection = None;
476 let nav = CursorNavigator::new(&self.rope);
478 self.cursor = nav.clamp(self.cursor);
479 true
480 }
481
482 fn apply_op(&mut self, op: &EditOp) {
484 match op {
485 EditOp::Insert { byte_offset, text } => {
486 let char_idx = self.rope.byte_to_char(*byte_offset);
487 self.rope.insert(char_idx, text);
488 }
489 EditOp::Delete { byte_offset, text } => {
490 let start_char = self.rope.byte_to_char(*byte_offset);
491 let end_char = self.rope.byte_to_char(*byte_offset + text.len());
492 self.rope.remove(start_char..end_char);
493 }
494 }
495 }
496
497 fn delete_selection_inner(&mut self) -> bool {
503 let Some(sel) = self.selection.take() else {
504 return false;
505 };
506 if sel.is_empty() {
507 return false;
508 }
509 let nav = CursorNavigator::new(&self.rope);
510 let (start_byte, end_byte) = sel.byte_range(&nav);
511 let start_char = self.rope.byte_to_char(start_byte);
512 let end_char = self.rope.byte_to_char(end_byte);
513 let deleted = self.rope.slice(start_char..end_char).into_owned();
514
515 self.push_undo(EditOp::Delete {
516 byte_offset: start_byte,
517 text: deleted,
518 });
519
520 self.rope.remove(start_char..end_char);
521 let nav = CursorNavigator::new(&self.rope);
522 self.cursor = nav.from_byte_index(start_byte);
523 true
524 }
525
526 pub fn move_left(&mut self) {
532 self.selection = None;
533 let nav = CursorNavigator::new(&self.rope);
534 self.cursor = nav.move_left(self.cursor);
535 }
536
537 pub fn move_right(&mut self) {
539 self.selection = None;
540 let nav = CursorNavigator::new(&self.rope);
541 self.cursor = nav.move_right(self.cursor);
542 }
543
544 pub fn move_up(&mut self) {
546 self.selection = None;
547 let nav = CursorNavigator::new(&self.rope);
548 self.cursor = nav.move_up(self.cursor);
549 }
550
551 pub fn move_down(&mut self) {
553 self.selection = None;
554 let nav = CursorNavigator::new(&self.rope);
555 self.cursor = nav.move_down(self.cursor);
556 }
557
558 pub fn move_word_left(&mut self) {
560 self.selection = None;
561 let nav = CursorNavigator::new(&self.rope);
562 self.cursor = nav.move_word_left(self.cursor);
563 }
564
565 pub fn move_word_right(&mut self) {
567 self.selection = None;
568 let nav = CursorNavigator::new(&self.rope);
569 self.cursor = nav.move_word_right(self.cursor);
570 }
571
572 pub fn move_to_line_start(&mut self) {
574 self.selection = None;
575 let nav = CursorNavigator::new(&self.rope);
576 self.cursor = nav.line_start(self.cursor);
577 }
578
579 pub fn move_to_line_end(&mut self) {
581 self.selection = None;
582 let nav = CursorNavigator::new(&self.rope);
583 self.cursor = nav.line_end(self.cursor);
584 }
585
586 pub fn move_to_document_start(&mut self) {
588 self.selection = None;
589 let nav = CursorNavigator::new(&self.rope);
590 self.cursor = nav.document_start();
591 }
592
593 pub fn move_to_document_end(&mut self) {
595 self.selection = None;
596 let nav = CursorNavigator::new(&self.rope);
597 self.cursor = nav.document_end();
598 }
599
600 pub fn select_left(&mut self) {
606 self.extend_selection(|nav, pos| nav.move_left(pos));
607 }
608
609 pub fn select_right(&mut self) {
611 self.extend_selection(|nav, pos| nav.move_right(pos));
612 }
613
614 pub fn select_up(&mut self) {
616 self.extend_selection(|nav, pos| nav.move_up(pos));
617 }
618
619 pub fn select_down(&mut self) {
621 self.extend_selection(|nav, pos| nav.move_down(pos));
622 }
623
624 pub fn select_word_left(&mut self) {
626 self.extend_selection(|nav, pos| nav.move_word_left(pos));
627 }
628
629 pub fn select_word_right(&mut self) {
631 self.extend_selection(|nav, pos| nav.move_word_right(pos));
632 }
633
634 pub fn select_all(&mut self) {
636 let nav = CursorNavigator::new(&self.rope);
637 let start = nav.document_start();
638 let end = nav.document_end();
639 self.selection = Some(Selection {
640 anchor: start,
641 head: end,
642 });
643 self.cursor = end;
644 }
645
646 pub fn clear_selection(&mut self) {
648 self.selection = None;
649 }
650
651 #[must_use]
653 pub fn selected_text(&self) -> Option<String> {
654 let sel = self.selection?;
655 if sel.is_empty() {
656 return None;
657 }
658 let nav = CursorNavigator::new(&self.rope);
659 let (start, end) = sel.byte_range(&nav);
660 let start_char = self.rope.byte_to_char(start);
661 let end_char = self.rope.byte_to_char(end);
662 Some(self.rope.slice(start_char..end_char).into_owned())
663 }
664
665 fn extend_selection(
666 &mut self,
667 f: impl FnOnce(&CursorNavigator<'_>, CursorPosition) -> CursorPosition,
668 ) {
669 let anchor = match self.selection {
670 Some(sel) => sel.anchor,
671 None => self.cursor,
672 };
673 let nav = CursorNavigator::new(&self.rope);
674 let new_head = f(&nav, self.cursor);
675 self.cursor = new_head;
676 self.selection = Some(Selection {
677 anchor,
678 head: new_head,
679 });
680 }
681
682 pub fn set_text(&mut self, text: &str) {
688 self.rope.replace(text);
689 let nav = CursorNavigator::new(&self.rope);
690 self.cursor = nav.document_end();
691 self.selection = None;
692 self.undo_stack.clear();
693 self.redo_stack.clear();
694 self.current_undo_size = 0;
695 }
696
697 pub fn clear(&mut self) {
699 self.rope.clear();
700 self.cursor = CursorPosition::default();
701 self.selection = None;
702 self.undo_stack.clear();
703 self.redo_stack.clear();
704 self.current_undo_size = 0;
705 }
706}
707
708impl EditOp {
709 fn byte_len(&self) -> usize {
710 match self {
711 Self::Insert { text, .. } => text.len(),
712 Self::Delete { text, .. } => text.len(),
713 }
714 }
715}
716
717#[cfg(test)]
718mod tests {
719 use super::*;
720
721 #[test]
722 fn new_editor_is_empty() {
723 let ed = Editor::new();
724 assert!(ed.is_empty());
725 assert_eq!(ed.text(), "");
726 assert_eq!(ed.cursor(), CursorPosition::default());
727 }
728
729 #[test]
730 fn with_text_cursor_at_end() {
731 let ed = Editor::with_text("hello");
732 assert_eq!(ed.text(), "hello");
733 assert_eq!(ed.cursor().line, 0);
734 assert_eq!(ed.cursor().grapheme, 5);
735 }
736
737 #[test]
738 fn insert_char_at_end() {
739 let mut ed = Editor::new();
740 ed.insert_char('a');
741 ed.insert_char('b');
742 ed.insert_char('c');
743 assert_eq!(ed.text(), "abc");
744 assert_eq!(ed.cursor().grapheme, 3);
745 }
746
747 #[test]
748 fn insert_text() {
749 let mut ed = Editor::new();
750 ed.insert_text("hello world");
751 assert_eq!(ed.text(), "hello world");
752 }
753
754 #[test]
755 fn insert_in_middle() {
756 let mut ed = Editor::with_text("helo");
757 ed.set_cursor(CursorPosition::new(0, 3, 3));
759 ed.insert_char('l');
760 assert_eq!(ed.text(), "hello");
761 }
762
763 #[test]
764 fn insert_newline() {
765 let mut ed = Editor::with_text("hello world");
766 ed.set_cursor(CursorPosition::new(0, 5, 5));
768 ed.insert_newline();
769 assert_eq!(ed.text(), "hello\n world");
770 assert_eq!(ed.cursor().line, 1);
771 assert_eq!(ed.line_count(), 2);
772 }
773
774 #[test]
775 fn delete_backward() {
776 let mut ed = Editor::with_text("hello");
777 assert!(ed.delete_backward());
778 assert_eq!(ed.text(), "hell");
779 }
780
781 #[test]
782 fn delete_backward_at_beginning() {
783 let mut ed = Editor::with_text("hello");
784 ed.set_cursor(CursorPosition::new(0, 0, 0));
785 assert!(!ed.delete_backward());
786 assert_eq!(ed.text(), "hello");
787 }
788
789 #[test]
790 fn delete_backward_joins_lines() {
791 let mut ed = Editor::with_text("hello\nworld");
792 ed.set_cursor(CursorPosition::new(1, 0, 0));
794 assert!(ed.delete_backward());
795 assert_eq!(ed.text(), "helloworld");
796 assert_eq!(ed.line_count(), 1);
797 }
798
799 #[test]
800 fn delete_forward() {
801 let mut ed = Editor::with_text("hello");
802 ed.set_cursor(CursorPosition::new(0, 0, 0));
803 assert!(ed.delete_forward());
804 assert_eq!(ed.text(), "ello");
805 }
806
807 #[test]
808 fn delete_forward_at_end() {
809 let mut ed = Editor::with_text("hello");
810 assert!(!ed.delete_forward());
811 assert_eq!(ed.text(), "hello");
812 }
813
814 #[test]
815 fn delete_forward_joins_lines() {
816 let mut ed = Editor::with_text("hello\nworld");
817 ed.set_cursor(CursorPosition::new(0, 5, 5));
819 assert!(ed.delete_forward());
820 assert_eq!(ed.text(), "helloworld");
821 }
822
823 #[test]
824 fn move_left_right() {
825 let mut ed = Editor::with_text("abc");
826 assert_eq!(ed.cursor().grapheme, 3);
827
828 ed.move_left();
829 assert_eq!(ed.cursor().grapheme, 2);
830
831 ed.move_left();
832 assert_eq!(ed.cursor().grapheme, 1);
833
834 ed.move_right();
835 assert_eq!(ed.cursor().grapheme, 2);
836 }
837
838 #[test]
839 fn move_left_at_start_is_noop() {
840 let mut ed = Editor::with_text("abc");
841 ed.set_cursor(CursorPosition::new(0, 0, 0));
842 ed.move_left();
843 assert_eq!(ed.cursor().grapheme, 0);
844 assert_eq!(ed.cursor().line, 0);
845 }
846
847 #[test]
848 fn move_right_at_end_is_noop() {
849 let mut ed = Editor::with_text("abc");
850 ed.move_right();
851 assert_eq!(ed.cursor().grapheme, 3);
852 }
853
854 #[test]
855 fn move_up_down() {
856 let mut ed = Editor::with_text("line 1\nline 2\nline 3");
857 assert_eq!(ed.cursor().line, 2);
859
860 ed.move_up();
861 assert_eq!(ed.cursor().line, 1);
862
863 ed.move_up();
864 assert_eq!(ed.cursor().line, 0);
865
866 ed.move_up();
868 assert_eq!(ed.cursor().line, 0);
869
870 ed.move_down();
871 assert_eq!(ed.cursor().line, 1);
872 }
873
874 #[test]
875 fn move_to_line_start_end() {
876 let mut ed = Editor::with_text("hello world");
877 ed.set_cursor(CursorPosition::new(0, 5, 5));
878
879 ed.move_to_line_start();
880 assert_eq!(ed.cursor().grapheme, 0);
881
882 ed.move_to_line_end();
883 assert_eq!(ed.cursor().grapheme, 11);
884 }
885
886 #[test]
887 fn move_to_document_start_end() {
888 let mut ed = Editor::with_text("line 1\nline 2\nline 3");
889
890 ed.move_to_document_start();
891 assert_eq!(ed.cursor().line, 0);
892 assert_eq!(ed.cursor().grapheme, 0);
893
894 ed.move_to_document_end();
895 assert_eq!(ed.cursor().line, 2);
896 }
897
898 #[test]
899 fn move_word_left_right() {
900 let mut ed = Editor::with_text("hello world foo");
901 let start = ed.cursor().grapheme;
903
904 ed.move_word_left();
905 let after_first = ed.cursor().grapheme;
906 assert!(after_first < start, "word_left should move cursor left");
907
908 ed.move_word_left();
909 let after_second = ed.cursor().grapheme;
910 assert!(
911 after_second < after_first,
912 "second word_left should move further left"
913 );
914
915 ed.move_word_right();
916 let after_right = ed.cursor().grapheme;
917 assert!(
918 after_right > after_second,
919 "word_right should move cursor right"
920 );
921 }
922
923 #[test]
924 fn delete_word_backward() {
925 let mut ed = Editor::with_text("hello world");
926 assert!(ed.delete_word_backward());
927 assert_eq!(ed.text(), "hello ");
928 }
929
930 #[test]
931 fn delete_to_end_of_line() {
932 let mut ed = Editor::with_text("hello world");
933 ed.set_cursor(CursorPosition::new(0, 5, 5));
934 assert!(ed.delete_to_end_of_line());
935 assert_eq!(ed.text(), "hello");
936 }
937
938 #[test]
939 fn delete_to_end_joins_when_at_line_end() {
940 let mut ed = Editor::with_text("hello\nworld");
941 ed.set_cursor(CursorPosition::new(0, 5, 5));
942 assert!(ed.delete_to_end_of_line());
943 assert_eq!(ed.text(), "helloworld");
944 }
945
946 #[test]
947 fn set_text_replaces_content() {
948 let mut ed = Editor::with_text("old");
949 ed.set_text("new content");
950 assert_eq!(ed.text(), "new content");
951 }
952
953 #[test]
954 fn clear_resets() {
955 let mut ed = Editor::with_text("hello");
956 ed.clear();
957 assert!(ed.is_empty());
958 assert_eq!(ed.cursor(), CursorPosition::default());
959 }
960
961 #[test]
962 fn line_text_works() {
963 let ed = Editor::with_text("line 0\nline 1\nline 2");
964 assert_eq!(ed.line_text(0), Some("line 0".to_string()));
965 assert_eq!(ed.line_text(1), Some("line 1".to_string()));
966 assert_eq!(ed.line_text(2), Some("line 2".to_string()));
967 assert_eq!(ed.line_text(3), None);
968 }
969
970 #[test]
971 fn cursor_stays_in_bounds_after_delete() {
972 let mut ed = Editor::with_text("a");
973 assert!(ed.delete_backward());
974 assert_eq!(ed.text(), "");
975 assert_eq!(ed.cursor(), CursorPosition::default());
976
977 assert!(!ed.delete_backward());
979 assert!(!ed.delete_forward());
980 }
981
982 #[test]
983 fn multiline_editing() {
984 let mut ed = Editor::new();
985 ed.insert_text("first");
986 ed.insert_newline();
987 ed.insert_text("second");
988 ed.insert_newline();
989 ed.insert_text("third");
990
991 assert_eq!(ed.text(), "first\nsecond\nthird");
992 assert_eq!(ed.line_count(), 3);
993 assert_eq!(ed.cursor().line, 2);
994
995 ed.move_up();
997 ed.move_to_line_start();
998 ed.insert_text(">> ");
999 assert_eq!(ed.line_text(1), Some(">> second".to_string()));
1000 }
1001
1002 #[test]
1007 fn undo_insert() {
1008 let mut ed = Editor::new();
1009 ed.insert_text("hello");
1010 assert!(ed.can_undo());
1011 assert!(ed.undo());
1012 assert_eq!(ed.text(), "");
1013 }
1014
1015 #[test]
1016 fn undo_delete() {
1017 let mut ed = Editor::with_text("hello");
1018 ed.delete_backward();
1019 assert_eq!(ed.text(), "hell");
1020 assert!(ed.undo());
1021 assert_eq!(ed.text(), "hello");
1022 }
1023
1024 #[test]
1025 fn redo_after_undo() {
1026 let mut ed = Editor::new();
1027 ed.insert_text("abc");
1028 ed.undo();
1029 assert_eq!(ed.text(), "");
1030 assert!(ed.can_redo());
1031 assert!(ed.redo());
1032 assert_eq!(ed.text(), "abc");
1033 }
1034
1035 #[test]
1036 fn redo_cleared_on_new_edit() {
1037 let mut ed = Editor::new();
1038 ed.insert_text("abc");
1039 ed.undo();
1040 ed.insert_text("xyz");
1041 assert!(!ed.can_redo());
1042 }
1043
1044 #[test]
1045 fn multiple_undo_redo() {
1046 let mut ed = Editor::new();
1047 ed.insert_text("a");
1048 ed.insert_text("b");
1049 ed.insert_text("c");
1050 assert_eq!(ed.text(), "abc");
1051
1052 ed.undo();
1053 assert_eq!(ed.text(), "ab");
1054 ed.undo();
1055 assert_eq!(ed.text(), "a");
1056 ed.undo();
1057 assert_eq!(ed.text(), "");
1058
1059 ed.redo();
1060 assert_eq!(ed.text(), "a");
1061 ed.redo();
1062 assert_eq!(ed.text(), "ab");
1063 }
1064
1065 #[test]
1066 fn undo_restores_cursor() {
1067 let mut ed = Editor::new();
1068 let before = ed.cursor();
1069 ed.insert_text("x");
1070 ed.undo();
1071 assert_eq!(ed.cursor(), before);
1072 }
1073
1074 #[test]
1075 fn max_history_respected() {
1076 let mut ed = Editor::new();
1077 ed.set_max_history(3);
1078 for c in ['a', 'b', 'c', 'd', 'e'] {
1079 ed.insert_text(&c.to_string());
1080 }
1081 assert!(ed.undo());
1082 assert!(ed.undo());
1083 assert!(ed.undo());
1084 assert!(!ed.undo());
1085 assert_eq!(ed.text(), "ab");
1086 }
1087
1088 #[test]
1089 fn set_text_clears_undo() {
1090 let mut ed = Editor::new();
1091 ed.insert_text("abc");
1092 ed.set_text("new");
1093 assert!(!ed.can_undo());
1094 assert!(!ed.can_redo());
1095 }
1096
1097 #[test]
1098 fn clear_clears_undo() {
1099 let mut ed = Editor::new();
1100 ed.insert_text("abc");
1101 ed.clear();
1102 assert!(!ed.can_undo());
1103 }
1104
1105 #[test]
1110 fn select_right_creates_selection() {
1111 let mut ed = Editor::with_text("hello");
1112 ed.set_cursor(CursorPosition::new(0, 0, 0));
1113 ed.select_right();
1114 ed.select_right();
1115 ed.select_right();
1116 let sel = ed.selection().unwrap();
1117 assert_eq!(sel.anchor, CursorPosition::new(0, 0, 0));
1118 assert_eq!(sel.head.grapheme, 3);
1119 assert_eq!(ed.selected_text(), Some("hel".to_string()));
1120 }
1121
1122 #[test]
1123 fn select_all_selects_everything() {
1124 let mut ed = Editor::with_text("abc\ndef");
1125 ed.select_all();
1126 assert_eq!(ed.selected_text(), Some("abc\ndef".to_string()));
1127 }
1128
1129 #[test]
1130 fn insert_replaces_selection() {
1131 let mut ed = Editor::with_text("hello world");
1132 ed.set_cursor(CursorPosition::new(0, 0, 0));
1133 for _ in 0..5 {
1134 ed.select_right();
1135 }
1136 ed.insert_text("goodbye");
1137 assert_eq!(ed.text(), "goodbye world");
1138 assert!(ed.selection().is_none());
1139 }
1140
1141 #[test]
1142 fn delete_backward_removes_selection() {
1143 let mut ed = Editor::with_text("hello world");
1144 ed.set_cursor(CursorPosition::new(0, 0, 0));
1145 for _ in 0..5 {
1146 ed.select_right();
1147 }
1148 ed.delete_backward();
1149 assert_eq!(ed.text(), " world");
1150 }
1151
1152 #[test]
1153 fn movement_clears_selection() {
1154 let mut ed = Editor::with_text("hello");
1155 ed.set_cursor(CursorPosition::new(0, 0, 0));
1156 ed.select_right();
1157 ed.select_right();
1158 assert!(ed.selection().is_some());
1159 ed.move_right();
1160 assert!(ed.selection().is_none());
1161 }
1162
1163 #[test]
1164 fn undo_selection_delete() {
1165 let mut ed = Editor::with_text("hello world");
1166 ed.set_cursor(CursorPosition::new(0, 0, 0));
1167 for _ in 0..5 {
1168 ed.select_right();
1169 }
1170 ed.delete_backward();
1171 assert_eq!(ed.text(), " world");
1172 ed.undo();
1173 assert_eq!(ed.text(), "hello world");
1174 }
1175
1176 #[test]
1181 fn insert_empty_text_is_noop() {
1182 let mut ed = Editor::with_text("hello");
1183 let before = ed.text();
1184 ed.insert_text("");
1185 assert_eq!(ed.text(), before);
1186 assert!(!ed.can_undo());
1188 }
1189
1190 #[test]
1191 fn unicode_emoji_handling() {
1192 let mut ed = Editor::new();
1193 ed.insert_text("hello π world");
1194 assert_eq!(ed.text(), "hello π world");
1195 ed.move_left(); ed.move_left(); ed.move_left(); ed.move_left(); ed.move_left(); ed.move_left(); ed.move_left(); ed.delete_backward(); assert_eq!(ed.text(), "helloπ world");
1205 }
1206
1207 #[test]
1208 fn unicode_combining_character() {
1209 let mut ed = Editor::new();
1210 ed.insert_text("caf\u{0065}\u{0301}");
1212 assert_eq!(ed.text(), "caf\u{0065}\u{0301}");
1214 ed.delete_backward();
1216 assert_eq!(ed.text(), "caf");
1217 }
1218
1219 #[test]
1220 fn unicode_zwj_sequence() {
1221 let mut ed = Editor::new();
1222 ed.insert_text("π©\u{200D}π");
1224 let text = ed.text();
1225 assert!(text.contains("π©"));
1226 ed.move_left();
1228 ed.insert_char('x');
1230 assert!(ed.text().starts_with('x'));
1231 }
1232
1233 #[test]
1234 fn unicode_cjk_wide_chars() {
1235 let mut ed = Editor::new();
1236 ed.insert_text("δΈη");
1237 assert_eq!(ed.text(), "δΈη");
1238 ed.move_left();
1239 assert_eq!(ed.cursor().grapheme, 1);
1240 ed.move_left();
1241 assert_eq!(ed.cursor().grapheme, 0);
1242 }
1243
1244 #[test]
1245 fn crlf_handling() {
1246 let ed = Editor::with_text("hello\r\nworld");
1247 assert_eq!(ed.line_count(), 2);
1248 assert_eq!(ed.line_text(0), Some("hello".to_string()));
1249 assert_eq!(ed.line_text(1), Some("world".to_string()));
1250 }
1251
1252 #[test]
1253 fn mixed_newlines() {
1254 let ed = Editor::with_text("line1\nline2\r\nline3");
1255 assert_eq!(ed.line_count(), 3);
1256 assert_eq!(ed.line_text(0), Some("line1".to_string()));
1257 assert_eq!(ed.line_text(1), Some("line2".to_string()));
1258 assert_eq!(ed.line_text(2), Some("line3".to_string()));
1259 }
1260
1261 #[test]
1262 fn trailing_newline() {
1263 let ed = Editor::with_text("hello\n");
1264 assert_eq!(ed.line_count(), 2);
1265 assert_eq!(ed.line_text(0), Some("hello".to_string()));
1266 assert_eq!(ed.line_text(1), Some(String::new()));
1267 }
1268
1269 #[test]
1270 fn only_newlines() {
1271 let ed = Editor::with_text("\n\n\n");
1272 assert_eq!(ed.line_count(), 4);
1273 for i in 0..4 {
1274 assert_eq!(ed.line_text(i), Some(String::new()));
1275 }
1276 }
1277
1278 #[test]
1279 fn delete_word_backward_at_start_is_noop() {
1280 let mut ed = Editor::with_text("hello");
1281 ed.set_cursor(CursorPosition::new(0, 0, 0));
1282 assert!(!ed.delete_word_backward());
1283 assert_eq!(ed.text(), "hello");
1284 }
1285
1286 #[test]
1287 fn delete_word_backward_multiple_spaces() {
1288 let mut ed = Editor::with_text("hello world");
1289 assert!(ed.delete_word_backward());
1291 let remaining = ed.text();
1293 assert!(remaining.starts_with("hello"));
1294 }
1295
1296 #[test]
1297 fn delete_to_end_at_document_end() {
1298 let mut ed = Editor::with_text("hello");
1299 assert!(!ed.delete_to_end_of_line());
1301 assert_eq!(ed.text(), "hello");
1302 }
1303
1304 #[test]
1305 fn select_word_operations() {
1306 let mut ed = Editor::with_text("hello world");
1307 ed.set_cursor(CursorPosition::new(0, 0, 0));
1308 ed.select_word_right();
1310 assert_eq!(ed.selected_text(), Some("hello ".to_string()));
1311 ed.clear_selection();
1312 ed.move_to_line_end();
1313 ed.select_word_left();
1314 assert_eq!(ed.selected_text(), Some("world".to_string()));
1315 }
1316
1317 #[test]
1318 fn select_up_down() {
1319 let mut ed = Editor::with_text("line1\nline2\nline3");
1320 ed.set_cursor(CursorPosition::new(1, 3, 3));
1321 ed.select_up();
1322 let sel = ed.selection().expect("should have selection");
1323 assert_eq!(sel.anchor.line, 1);
1324 assert_eq!(sel.head.line, 0);
1325 ed.select_down();
1326 ed.select_down();
1327 let sel = ed.selection().expect("should have selection");
1328 assert_eq!(sel.head.line, 2);
1329 }
1330
1331 #[test]
1332 fn selection_extending_preserves_anchor() {
1333 let mut ed = Editor::with_text("abcdef");
1334 ed.set_cursor(CursorPosition::new(0, 2, 2));
1335 ed.select_right();
1336 ed.select_right();
1337 ed.select_right();
1338 let sel = ed.selection().unwrap();
1339 assert_eq!(sel.anchor.grapheme, 2);
1340 assert_eq!(sel.head.grapheme, 5);
1341 ed.select_left();
1343 let sel = ed.selection().unwrap();
1344 assert_eq!(sel.anchor.grapheme, 2);
1345 assert_eq!(sel.head.grapheme, 4);
1346 }
1347
1348 #[test]
1349 fn empty_selection_returns_none() {
1350 let mut ed = Editor::with_text("hello");
1351 ed.set_cursor(CursorPosition::new(0, 2, 2));
1352 ed.select_right();
1354 ed.select_left();
1355 let sel = ed.selection().unwrap();
1357 assert!(sel.is_empty());
1358 assert_eq!(ed.selected_text(), None);
1359 }
1360
1361 #[test]
1362 fn cursor_clamp_after_set_text() {
1363 let mut ed = Editor::with_text("very long text here");
1364 ed.set_text("hi");
1365 assert_eq!(ed.cursor().line, 0);
1367 assert_eq!(ed.cursor().grapheme, 2);
1368 }
1369
1370 #[test]
1371 fn undo_redo_with_selection() {
1372 let mut ed = Editor::with_text("hello world");
1373 ed.set_cursor(CursorPosition::new(0, 6, 6));
1374 for _ in 0..5 {
1376 ed.select_right();
1377 }
1378 ed.insert_text("universe");
1379 assert_eq!(ed.text(), "hello universe");
1380 ed.undo(); assert_eq!(ed.text(), "hello ");
1384 ed.undo(); assert_eq!(ed.text(), "hello world");
1386 ed.redo();
1388 assert_eq!(ed.text(), "hello ");
1389 ed.redo();
1390 assert_eq!(ed.text(), "hello universe");
1391 }
1392
1393 #[test]
1394 fn rapid_insert_delete_cycle() {
1395 let mut ed = Editor::new();
1396 for i in 0..100 {
1397 ed.insert_char(char::from_u32('a' as u32 + (i % 26)).unwrap());
1398 if i % 3 == 0 {
1399 ed.delete_backward();
1400 }
1401 }
1402 let cursor = ed.cursor();
1404 assert!(cursor.line == 0);
1405 assert!(cursor.grapheme <= ed.text().chars().count());
1406 }
1407
1408 #[test]
1409 fn multiline_select_all_and_replace() {
1410 let mut ed = Editor::with_text("line1\nline2\nline3");
1411 ed.select_all();
1412 ed.insert_text("replaced");
1413 assert_eq!(ed.text(), "replaced");
1414 assert_eq!(ed.line_count(), 1);
1415 }
1416
1417 #[test]
1418 fn delete_forward_with_selection() {
1419 let mut ed = Editor::with_text("hello world");
1420 ed.set_cursor(CursorPosition::new(0, 0, 0));
1421 for _ in 0..5 {
1422 ed.select_right();
1423 }
1424 ed.delete_forward();
1426 assert_eq!(ed.text(), " world");
1427 }
1428
1429 #[test]
1430 fn delete_word_backward_with_selection() {
1431 let mut ed = Editor::with_text("hello world");
1432 ed.set_cursor(CursorPosition::new(0, 6, 6));
1433 for _ in 0..5 {
1434 ed.select_right();
1435 }
1436 ed.delete_word_backward();
1438 assert_eq!(ed.text(), "hello ");
1439 }
1440
1441 #[test]
1442 fn default_impl() {
1443 let ed = Editor::default();
1444 assert!(ed.is_empty());
1445 assert_eq!(ed.cursor(), CursorPosition::default());
1446 }
1447
1448 #[test]
1449 fn line_text_out_of_bounds() {
1450 let ed = Editor::with_text("hello");
1451 assert_eq!(ed.line_text(0), Some("hello".to_string()));
1452 assert_eq!(ed.line_text(1), None);
1453 assert_eq!(ed.line_text(100), None);
1454 }
1455
1456 #[test]
1457 fn rope_accessor() {
1458 let ed = Editor::with_text("test");
1459 let rope = ed.rope();
1460 assert_eq!(rope.len_bytes(), 4);
1461 }
1462
1463 #[test]
1464 fn insert_text_sanitizes_controls() {
1465 let mut ed = Editor::new();
1466 ed.insert_text("hello\x1bworld\x07\n\t!");
1468 assert_eq!(ed.text(), "helloworld\n\t!");
1470 }
1471
1472 #[test]
1473 fn cursor_position_after_multiline_insert() {
1474 let mut ed = Editor::new();
1475 ed.insert_text("hello\nworld\nfoo");
1476 assert_eq!(ed.cursor().line, 2);
1477 assert_eq!(ed.line_count(), 3);
1478 }
1479
1480 #[test]
1481 fn delete_backward_across_lines() {
1482 let mut ed = Editor::with_text("abc\ndef");
1483 ed.set_cursor(CursorPosition::new(1, 0, 0));
1484 ed.delete_backward();
1485 assert_eq!(ed.text(), "abcdef");
1486 assert_eq!(ed.cursor().line, 0);
1487 assert_eq!(ed.cursor().grapheme, 3);
1488 }
1489
1490 #[test]
1491 fn very_long_line() {
1492 let long_text: String = "a".repeat(10000);
1493 let mut ed = Editor::with_text(&long_text);
1494 assert_eq!(ed.text().len(), 10000);
1495 ed.move_to_line_start();
1496 assert_eq!(ed.cursor().grapheme, 0);
1497 ed.move_to_line_end();
1498 assert_eq!(ed.cursor().grapheme, 10000);
1499 }
1500
1501 #[test]
1502 fn many_lines() {
1503 let text: String = (0..1000)
1504 .map(|i| format!("line{i}"))
1505 .collect::<Vec<_>>()
1506 .join("\n");
1507 let ed = Editor::with_text(&text);
1508 assert_eq!(ed.line_count(), 1000);
1509 assert_eq!(ed.line_text(999), Some("line999".to_string()));
1510 }
1511
1512 #[test]
1513 fn selection_byte_range_order() {
1514 use crate::cursor::CursorNavigator;
1515
1516 let mut ed = Editor::with_text("hello world");
1517 ed.set_cursor(CursorPosition::new(0, 8, 8));
1519 ed.select_left();
1520 ed.select_left();
1521 ed.select_left();
1522
1523 let sel = ed.selection().unwrap();
1524 let nav = CursorNavigator::new(ed.rope());
1525 let (start, end) = sel.byte_range(&nav);
1526 assert!(start <= end);
1528 assert_eq!(end - start, 3);
1529 }
1530}
1531
1532#[cfg(test)]
1537mod proptests {
1538 use super::*;
1539 use proptest::prelude::*;
1540
1541 fn text_strategy() -> impl Strategy<Value = String> {
1543 prop::string::string_regex("[a-zA-Z0-9 \n]{0,100}")
1544 .unwrap()
1545 .prop_filter("non-empty or empty", |_| true)
1546 }
1547
1548 fn unicode_text_strategy() -> impl Strategy<Value = String> {
1550 prop::collection::vec(
1551 prop_oneof![
1552 Just("a".to_string()),
1553 Just(" ".to_string()),
1554 Just("\n".to_string()),
1555 Just("Γ©".to_string()),
1556 Just("δΈ".to_string()),
1557 Just("π".to_string()),
1558 ],
1559 0..50,
1560 )
1561 .prop_map(|v| v.join(""))
1562 }
1563
1564 proptest! {
1565 #![proptest_config(ProptestConfig::with_cases(100))]
1566
1567 #[test]
1569 fn cursor_always_in_bounds(text in text_strategy()) {
1570 let mut ed = Editor::with_text(&text);
1571
1572 let c = ed.cursor();
1574 prop_assert!(c.line < ed.line_count() || (c.line == 0 && ed.line_count() == 1));
1575
1576 ed.move_left();
1578 let c = ed.cursor();
1579 prop_assert!(c.line < ed.line_count() || (c.line == 0 && ed.line_count() == 1));
1580
1581 ed.move_right();
1582 let c = ed.cursor();
1583 prop_assert!(c.line < ed.line_count() || (c.line == 0 && ed.line_count() == 1));
1584
1585 ed.move_up();
1586 let c = ed.cursor();
1587 prop_assert!(c.line < ed.line_count() || (c.line == 0 && ed.line_count() == 1));
1588
1589 ed.move_down();
1590 let c = ed.cursor();
1591 prop_assert!(c.line < ed.line_count() || (c.line == 0 && ed.line_count() == 1));
1592
1593 ed.move_to_line_start();
1594 let c = ed.cursor();
1595 prop_assert_eq!(c.grapheme, 0);
1596
1597 ed.move_to_document_start();
1598 let c = ed.cursor();
1599 prop_assert_eq!(c.line, 0);
1600 prop_assert_eq!(c.grapheme, 0);
1601 }
1602
1603 #[test]
1605 fn undo_insert_restores_text(base in text_strategy(), insert in "[a-z]{1,20}") {
1606 let mut ed = Editor::with_text(&base);
1607 let original = ed.text();
1608 ed.insert_text(&insert);
1609 prop_assert!(ed.can_undo());
1610 ed.undo();
1611 prop_assert_eq!(ed.text(), original);
1612 }
1613
1614 #[test]
1616 fn undo_delete_restores_text(text in "[a-zA-Z]{5,50}") {
1617 let mut ed = Editor::with_text(&text);
1618 let original = ed.text();
1619 if ed.delete_backward() {
1620 prop_assert!(ed.can_undo());
1621 ed.undo();
1622 prop_assert_eq!(ed.text(), original);
1623 }
1624 }
1625
1626 #[test]
1628 fn redo_after_undo_restores(text in text_strategy(), insert in "[a-z]{1,10}") {
1629 let mut ed = Editor::with_text(&text);
1630 ed.insert_text(&insert);
1631 let after_insert = ed.text();
1632 ed.undo();
1633 prop_assert!(ed.can_redo());
1634 ed.redo();
1635 prop_assert_eq!(ed.text(), after_insert);
1636 }
1637
1638 #[test]
1640 fn select_all_delete_empties(text in text_strategy()) {
1641 let mut ed = Editor::with_text(&text);
1642 ed.select_all();
1643 ed.delete_backward();
1644 prop_assert!(ed.is_empty());
1645 }
1646
1647 #[test]
1649 fn line_count_matches_newlines(text in text_strategy()) {
1650 let ed = Editor::with_text(&text);
1651 let newline_count = text.matches('\n').count();
1652 prop_assert_eq!(ed.line_count(), newline_count + 1);
1654 }
1655
1656 #[test]
1658 fn set_text_roundtrip(text in text_strategy()) {
1659 let mut ed = Editor::new();
1660 ed.set_text(&text);
1661 prop_assert_eq!(ed.text(), text);
1662 }
1663
1664 #[test]
1666 fn unicode_cursor_bounds(text in unicode_text_strategy()) {
1667 let mut ed = Editor::with_text(&text);
1668
1669 for _ in 0..10 {
1671 ed.move_left();
1672 }
1673 let c = ed.cursor();
1674 prop_assert!(c.line < ed.line_count() || ed.line_count() == 1);
1675
1676 for _ in 0..10 {
1677 ed.move_right();
1678 }
1679 let c = ed.cursor();
1680 prop_assert!(c.line < ed.line_count() || ed.line_count() == 1);
1681 }
1682
1683 #[test]
1685 fn insert_delete_roundtrip(ch in prop::char::any().prop_filter("printable", |c| !c.is_control())) {
1686 let mut ed = Editor::new();
1687 ed.insert_char(ch);
1688 ed.delete_backward();
1689 prop_assert!(ed.is_empty());
1690 }
1691
1692 #[test]
1694 fn multiple_undos_safe(ops in prop::collection::vec(0..3u8, 0..20)) {
1695 let mut ed = Editor::new();
1696 for op in ops {
1697 match op {
1698 0 => { ed.insert_char('x'); }
1699 1 => { ed.delete_backward(); }
1700 _ => { ed.undo(); }
1701 }
1702 }
1703 while ed.can_undo() {
1705 prop_assert!(ed.undo());
1706 }
1707 prop_assert!(!ed.can_undo());
1708 }
1709
1710 #[test]
1712 fn selection_range_ordered(text in "[a-zA-Z]{10,50}") {
1713 use crate::cursor::CursorNavigator;
1714
1715 let mut ed = Editor::with_text(&text);
1716 ed.set_cursor(CursorPosition::new(0, 5, 5));
1717
1718 ed.select_left();
1720 ed.select_left();
1721
1722 if let Some(sel) = ed.selection() {
1723 let nav = CursorNavigator::new(ed.rope());
1724 let (start, end) = sel.byte_range(&nav);
1725 prop_assert!(start <= end);
1726 }
1727
1728 ed.select_right();
1729 ed.select_right();
1730 ed.select_right();
1731 ed.select_right();
1732
1733 if let Some(sel) = ed.selection() {
1734 let nav = CursorNavigator::new(ed.rope());
1735 let (start, end) = sel.byte_range(&nav);
1736 prop_assert!(start <= end);
1737 }
1738 }
1739
1740 #[test]
1742 fn word_movement_progress(text in "[a-zA-Z ]{5,50}") {
1743 let mut ed = Editor::with_text(&text);
1744 ed.set_cursor(CursorPosition::new(0, 0, 0));
1745
1746 let start = ed.cursor();
1747 ed.move_word_right();
1748 let after = ed.cursor();
1749 prop_assert!(after.grapheme >= start.grapheme);
1751
1752 ed.move_to_line_end();
1753 let end_pos = ed.cursor();
1754 ed.move_word_left();
1755 let after_left = ed.cursor();
1756 prop_assert!(after_left.grapheme <= end_pos.grapheme);
1758 }
1759
1760 #[test]
1762 fn document_bounds(text in text_strategy()) {
1763 let mut ed = Editor::with_text(&text);
1764
1765 ed.move_to_document_start();
1766 prop_assert_eq!(ed.cursor().line, 0);
1767 prop_assert_eq!(ed.cursor().grapheme, 0);
1768
1769 ed.move_to_document_end();
1770 let c = ed.cursor();
1771 let last_line = ed.line_count().saturating_sub(1);
1772 prop_assert_eq!(c.line, last_line);
1773 }
1774 }
1775}