1use floem_editor_core::{
4 buffer::rope_text::{RopeText, RopeTextVal},
5 command::MultiSelectionCommand,
6 cursor::{ColPosition, Cursor, CursorAffinity, CursorMode},
7 mode::{Mode, MotionMode, VisualMode},
8 movement::{LinePosition, Movement},
9 register::Register,
10 selection::{SelRegion, Selection},
11 soft_tab::{SnapDirection, snap_to_soft_tab},
12};
13
14use super::{
15 Editor,
16 actions::CommonAction,
17 visual_line::{RVLine, VLineInfo},
18};
19
20fn move_region(
25 view: &Editor,
26 region: &SelRegion,
27 count: usize,
28 modify: bool,
29 movement: &Movement,
30 mode: Mode,
31) -> SelRegion {
32 let (count, region) = if count >= 1 && !modify && !region.is_caret() {
33 match movement {
39 Movement::Left | Movement::Up => {
40 let leftmost = region.min();
41 (
42 count - 1,
43 SelRegion::new(leftmost, leftmost, region.affinity, region.horiz),
44 )
45 }
46 Movement::Right | Movement::Down => {
47 let rightmost = region.max();
48 (
49 count - 1,
50 SelRegion::new(rightmost, rightmost, region.affinity, region.horiz),
51 )
52 }
53 _ => (count, *region),
54 }
55 } else {
56 (count, *region)
57 };
58
59 let mut affinity = region.affinity;
60
61 let (end, horiz) = move_offset(
62 view,
63 region.end,
64 region.horiz.as_ref(),
65 &mut affinity,
66 count,
67 movement,
68 mode,
69 );
70 let start = match modify {
71 true => region.start,
72 false => end,
73 };
74 SelRegion::new(start, end, affinity, horiz)
75}
76
77pub fn move_selection(
78 view: &Editor,
79 selection: &Selection,
80 count: usize,
81 modify: bool,
82 movement: &Movement,
83 mode: Mode,
84) -> Selection {
85 let mut new_selection = Selection::new();
86 for region in selection.regions() {
87 new_selection.add_region(move_region(view, region, count, modify, movement, mode));
88 }
89 new_selection
90}
91
92pub fn move_offset(
94 view: &Editor,
95 offset: usize,
96 horiz: Option<&ColPosition>,
97 affinity: &mut CursorAffinity,
98 count: usize,
99 movement: &Movement,
100 mode: Mode,
101) -> (usize, Option<ColPosition>) {
102 let (new_offset, horiz) = match movement {
103 Movement::Left => {
104 let new_offset = move_left(view, offset, affinity, mode, count);
105
106 (new_offset, None)
107 }
108 Movement::Right => {
109 let new_offset = move_right(view, offset, affinity, mode, count);
110
111 (new_offset, None)
112 }
113 Movement::Up => {
114 let (new_offset, horiz) = move_up(view, offset, affinity, horiz.cloned(), mode, count);
115
116 (new_offset, Some(horiz))
117 }
118 Movement::Down => {
119 let (new_offset, horiz) =
120 move_down(view, offset, affinity, horiz.cloned(), mode, count);
121
122 (new_offset, Some(horiz))
123 }
124 Movement::DocumentStart => {
125 *affinity = CursorAffinity::Backward;
127 (0, Some(ColPosition::Start))
128 }
129 Movement::DocumentEnd => {
130 let (new_offset, horiz) = document_end(view.rope_text(), affinity, mode);
131
132 (new_offset, Some(horiz))
133 }
134 Movement::FirstNonBlank => {
135 let (new_offset, horiz) = first_non_blank(view, affinity, offset);
136
137 (new_offset, Some(horiz))
138 }
139 Movement::StartOfLine => {
140 let (new_offset, horiz) = start_of_line(view, affinity, offset);
141
142 (new_offset, Some(horiz))
143 }
144 Movement::EndOfLine => {
145 let (new_offset, horiz) = end_of_line(view, affinity, offset, mode);
146
147 (new_offset, Some(horiz))
148 }
149 Movement::Line(position) => {
150 let (new_offset, horiz) = to_line(view, offset, horiz.cloned(), mode, position);
151
152 (new_offset, Some(horiz))
153 }
154 Movement::Offset(offset) => {
155 let new_offset = view.text().prev_grapheme_offset(*offset + 1).unwrap();
156 (new_offset, None)
157 }
158 Movement::WordEndForward => {
159 let new_offset =
160 view.rope_text()
161 .move_n_wordends_forward(offset, count, mode == Mode::Insert);
162 (new_offset, None)
163 }
164 Movement::WordForward => {
165 let new_offset = view.rope_text().move_n_words_forward(offset, count);
166 (new_offset, None)
167 }
168 Movement::WordBackward => {
169 let new_offset = view.rope_text().move_n_words_backward(offset, count, mode);
170 (new_offset, None)
171 }
172 Movement::NextUnmatched(char) => {
173 let new_offset = view.doc().find_unmatched(offset, false, *char);
174
175 (new_offset, None)
176 }
177 Movement::PreviousUnmatched(char) => {
178 let new_offset = view.doc().find_unmatched(offset, true, *char);
179
180 (new_offset, None)
181 }
182 Movement::MatchPairs => {
183 let new_offset = view.doc().find_matching_pair(offset);
184
185 (new_offset, None)
186 }
187 Movement::ParagraphForward => {
188 let new_offset = view.rope_text().move_n_paragraphs_forward(offset, count);
189
190 (new_offset, None)
191 }
192 Movement::ParagraphBackward => {
193 let new_offset = view.rope_text().move_n_paragraphs_backward(offset, count);
194
195 (new_offset, None)
196 }
197 };
198
199 let new_offset = correct_crlf(&view.rope_text(), new_offset);
200
201 (new_offset, horiz)
202}
203
204fn correct_crlf(text: &RopeTextVal, offset: usize) -> usize {
206 if offset == 0 || offset == text.len() {
207 return offset;
208 }
209
210 let mut cursor = lapce_xi_rope::Cursor::new(text.text(), offset);
211 if cursor.peek_next_codepoint() == Some('\n') && cursor.prev_codepoint() == Some('\r') {
212 return offset - 1;
213 }
214
215 offset
216}
217
218fn atomic_soft_tab_width_for_offset(ed: &Editor, offset: usize) -> Option<usize> {
219 let line = ed.line_of_offset(offset);
220 let style = ed.style();
221 if style.atomic_soft_tabs(ed.id(), line) {
222 Some(style.tab_width(ed.id(), line))
223 } else {
224 None
225 }
226}
227
228fn move_left(
232 ed: &Editor,
233 offset: usize,
234 affinity: &mut CursorAffinity,
235 mode: Mode,
236 count: usize,
237) -> usize {
238 let rope_text = ed.rope_text();
239 let mut new_offset = rope_text.move_left(offset, mode, count);
240
241 if let Some(soft_tab_width) = atomic_soft_tab_width_for_offset(ed, offset) {
242 if soft_tab_width > 1 {
243 new_offset = snap_to_soft_tab(
244 rope_text.text(),
245 new_offset,
246 SnapDirection::Left,
247 soft_tab_width,
248 );
249 }
250 }
251
252 *affinity = CursorAffinity::Forward;
253
254 new_offset
255}
256
257fn move_right(
260 view: &Editor,
261 offset: usize,
262 affinity: &mut CursorAffinity,
263 mode: Mode,
264 count: usize,
265) -> usize {
266 let rope_text = view.rope_text();
267 let mut new_offset = rope_text.move_right(offset, mode, count);
268
269 if let Some(soft_tab_width) = atomic_soft_tab_width_for_offset(view, offset) {
270 if soft_tab_width > 1 {
271 new_offset = snap_to_soft_tab(
272 rope_text.text(),
273 new_offset,
274 SnapDirection::Right,
275 soft_tab_width,
276 );
277 }
278 }
279
280 *affinity = CursorAffinity::Backward;
281
282 new_offset
283}
284
285fn find_prev_rvline(view: &Editor, start: RVLine, count: usize) -> Option<RVLine> {
286 if count == 0 {
287 return Some(start);
288 }
289
290 let mut info = None;
294 let mut found_count = 0;
295 for prev_info in view.iter_rvlines(true, start).skip(1) {
296 if prev_info.is_empty_phantom() {
297 continue;
299 }
300
301 found_count += 1;
303
304 if found_count == count {
305 info = Some(prev_info);
307 break;
308 }
309 }
311
312 info.map(|info| info.rvline)
313}
314
315fn move_up(
319 view: &Editor,
320 offset: usize,
321 affinity: &mut CursorAffinity,
322 horiz: Option<ColPosition>,
323 mode: Mode,
324 count: usize,
325) -> (usize, ColPosition) {
326 let rvline = view.rvline_of_offset(offset, *affinity);
327 if rvline.line == 0 && rvline.line_index == 0 {
328 let horiz = horiz
330 .unwrap_or_else(|| ColPosition::Col(view.line_point_of_offset(offset, *affinity).x));
331
332 *affinity = CursorAffinity::Backward;
333
334 return (0, horiz);
335 }
336
337 let Some(rvline) = find_prev_rvline(view, rvline, count) else {
338 let horiz = horiz
340 .unwrap_or_else(|| ColPosition::Col(view.line_point_of_offset(offset, *affinity).x));
341
342 *affinity = CursorAffinity::Backward;
343
344 return (0, horiz);
345 };
346
347 let horiz =
348 horiz.unwrap_or_else(|| ColPosition::Col(view.line_point_of_offset(offset, *affinity).x));
349 let col = view.rvline_horiz_col(rvline, &horiz, mode != Mode::Normal);
350 let new_offset = view.offset_of_line_col(rvline.line, col);
351
352 let info = view.rvline_info(rvline);
353
354 *affinity = if new_offset == info.interval.start {
355 CursorAffinity::Forward
356 } else {
357 CursorAffinity::Backward
358 };
359
360 (new_offset, horiz)
361}
362
363fn move_down_last_rvline(
365 view: &Editor,
366 offset: usize,
367 affinity: &mut CursorAffinity,
368 horiz: Option<ColPosition>,
369 mode: Mode,
370) -> (usize, ColPosition) {
371 let rope_text = view.rope_text();
372
373 let last_line = rope_text.last_line();
374 let new_offset = rope_text.line_end_offset(last_line, mode != Mode::Normal);
375
376 *affinity = CursorAffinity::Forward;
378
379 let horiz =
380 horiz.unwrap_or_else(|| ColPosition::Col(view.line_point_of_offset(offset, *affinity).x));
381
382 (new_offset, horiz)
383}
384
385fn find_next_rvline_info(
386 view: &Editor,
387 offset: usize,
388 start: RVLine,
389 count: usize,
390) -> Option<VLineInfo<()>> {
391 let mut found_count = 0;
397 for next_info in view.iter_rvlines(false, start) {
398 if count == 0 {
399 return Some(next_info);
400 }
401
402 if next_info.is_empty_phantom() {
403 continue;
406 }
407
408 if next_info.interval.start < offset || next_info.rvline == start {
409 continue;
411 }
412
413 found_count += 1;
415
416 if found_count == count {
417 return Some(next_info);
419 }
420 }
422
423 None
424}
425
426fn move_down(
430 view: &Editor,
431 offset: usize,
432 affinity: &mut CursorAffinity,
433 horiz: Option<ColPosition>,
434 mode: Mode,
435 count: usize,
436) -> (usize, ColPosition) {
437 let rvline = view.rvline_of_offset(offset, *affinity);
438
439 let Some(info) = find_next_rvline_info(view, offset, rvline, count) else {
440 return move_down_last_rvline(view, offset, affinity, horiz, mode);
443 };
444
445 let horiz =
447 horiz.unwrap_or_else(|| ColPosition::Col(view.line_point_of_offset(offset, *affinity).x));
448
449 let col = view.rvline_horiz_col(info.rvline, &horiz, mode != Mode::Normal);
450
451 let new_offset = view.offset_of_line_col(info.rvline.line, col);
452
453 *affinity = if new_offset == info.interval.start {
454 CursorAffinity::Forward
458 } else {
459 CursorAffinity::Backward
460 };
461
462 (new_offset, horiz)
463}
464
465fn document_end(
466 rope_text: impl RopeText,
467 affinity: &mut CursorAffinity,
468 mode: Mode,
469) -> (usize, ColPosition) {
470 let last_offset = rope_text.offset_line_end(rope_text.len(), mode != Mode::Normal);
471
472 *affinity = CursorAffinity::Forward;
474
475 (last_offset, ColPosition::End)
476}
477
478fn first_non_blank(
479 view: &Editor,
480 affinity: &mut CursorAffinity,
481 offset: usize,
482) -> (usize, ColPosition) {
483 let info = view.rvline_info_of_offset(offset, *affinity);
484 let non_blank_offset = info.first_non_blank_character(&view.text_prov());
485 let start_line_offset = info.interval.start;
486 *affinity = CursorAffinity::Forward;
488
489 if offset > non_blank_offset {
490 (non_blank_offset, ColPosition::FirstNonBlank)
492 } else {
493 if start_line_offset == offset {
495 (non_blank_offset, ColPosition::FirstNonBlank)
496 } else {
497 (start_line_offset, ColPosition::Start)
499 }
500 }
501}
502
503fn start_of_line(
504 view: &Editor,
505 affinity: &mut CursorAffinity,
506 offset: usize,
507) -> (usize, ColPosition) {
508 let rvline = view.rvline_of_offset(offset, *affinity);
509 let new_offset = view.offset_of_rvline(rvline);
510 *affinity = CursorAffinity::Forward;
513
514 (new_offset, ColPosition::Start)
515}
516
517fn end_of_line(
518 view: &Editor,
519 affinity: &mut CursorAffinity,
520 offset: usize,
521 mode: Mode,
522) -> (usize, ColPosition) {
523 let info = view.rvline_info_of_offset(offset, *affinity);
524 let new_col = info.last_col(&view.text_prov(), mode != Mode::Normal);
525 *affinity = if new_col == 0 {
526 CursorAffinity::Forward
527 } else {
528 CursorAffinity::Backward
529 };
530
531 let new_offset = view.offset_of_line_col(info.rvline.line, new_col);
532
533 (new_offset, ColPosition::End)
534}
535
536fn to_line(
537 view: &Editor,
538 offset: usize,
539 horiz: Option<ColPosition>,
540 mode: Mode,
541 position: &LinePosition,
542) -> (usize, ColPosition) {
543 let rope_text = view.rope_text();
544
545 let line = match position {
547 LinePosition::Line(line) => (line - 1).min(rope_text.last_line()),
548 LinePosition::First => 0,
549 LinePosition::Last => rope_text.last_line(),
550 };
551 let horiz = horiz.unwrap_or_else(|| {
553 ColPosition::Col(
554 view.line_point_of_offset(offset, CursorAffinity::Backward)
555 .x,
556 )
557 });
558 let col = view.line_horiz_col(line, &horiz, mode != Mode::Normal);
559 let new_offset = rope_text.offset_of_line_col(line, col);
560
561 (new_offset, horiz)
562}
563
564pub fn move_cursor(
568 ed: &Editor,
569 action: &dyn CommonAction,
570 cursor: &mut Cursor,
571 movement: &Movement,
572 count: usize,
573 modify: bool,
574 register: &mut Register,
575) {
576 match cursor.mode {
577 CursorMode::Normal {
578 offset,
579 mut affinity,
580 } => {
581 let count = if let Some(motion_mode) = cursor.motion_mode.as_ref() {
582 count.max(motion_mode.count())
583 } else {
584 count
585 };
586 let (new_offset, horiz) = move_offset(
587 ed,
588 offset,
589 cursor.horiz.as_ref(),
590 &mut affinity,
591 count,
592 movement,
593 Mode::Normal,
594 );
595 if let Some(motion_mode) = cursor.motion_mode.clone() {
596 let (moved_new_offset, _) = move_offset(
597 ed,
598 new_offset,
599 None,
600 &mut affinity,
601 1,
602 &Movement::Right,
603 Mode::Insert,
604 );
605 let range = match movement {
606 Movement::EndOfLine | Movement::WordEndForward => offset..moved_new_offset,
607 Movement::MatchPairs => {
608 if new_offset > offset {
609 offset..moved_new_offset
610 } else {
611 moved_new_offset..new_offset
612 }
613 }
614 _ => offset..new_offset,
615 };
616 action.exec_motion_mode(
617 ed,
618 cursor,
619 motion_mode,
620 range,
621 movement.is_vertical(),
622 register,
623 );
624 cursor.motion_mode = None;
625 } else {
626 cursor.mode = CursorMode::Normal {
627 offset: new_offset,
628 affinity,
629 };
630 cursor.horiz = horiz;
631 }
632 }
633 CursorMode::Visual {
634 start,
635 end,
636 mode,
637 mut affinity,
638 } => {
639 let (new_offset, horiz) = move_offset(
640 ed,
641 end,
642 cursor.horiz.as_ref(),
643 &mut affinity,
644 count,
645 movement,
646 Mode::Visual(VisualMode::Normal),
647 );
648 cursor.mode = CursorMode::Visual {
649 start,
650 end: new_offset,
651 mode,
652 affinity,
653 };
654 cursor.horiz = horiz;
655 }
656 CursorMode::Insert(ref selection) => {
657 let selection = move_selection(ed, selection, count, modify, movement, Mode::Insert);
658 cursor.set_insert(selection);
659 }
660 }
661}
662
663pub fn do_multi_selection(view: &Editor, cursor: &mut Cursor, cmd: &MultiSelectionCommand) {
664 use MultiSelectionCommand::*;
665 let rope_text = view.rope_text();
666
667 match cmd {
668 SelectUndo => {
669 if let CursorMode::Insert(_) = cursor.mode.clone() {
670 if let Some(selection) = cursor.history_selections.last().cloned() {
671 cursor.mode = CursorMode::Insert(selection);
672 }
673 cursor.history_selections.pop();
674 }
675 }
676 InsertCursorAbove => {
677 if let CursorMode::Insert(mut selection) = cursor.mode.clone() {
678 let (offset, mut affinity) = selection
679 .first()
680 .map(|s| (s.end, s.affinity))
681 .unwrap_or((0, CursorAffinity::Backward));
682
683 let (new_offset, _) = move_offset(
684 view,
685 offset,
686 cursor.horiz.as_ref(),
687 &mut affinity,
688 1,
689 &Movement::Up,
690 Mode::Insert,
691 );
692 if new_offset != offset {
693 selection.add_region(SelRegion::new(new_offset, new_offset, affinity, None));
694 }
695 cursor.set_insert(selection);
696 }
697 }
698 InsertCursorBelow => {
699 if let CursorMode::Insert(mut selection) = cursor.mode.clone() {
700 let (offset, mut affinity) = selection
701 .last()
702 .map(|s| (s.end, s.affinity))
703 .unwrap_or((0, CursorAffinity::Backward));
704
705 let (new_offset, _) = move_offset(
706 view,
707 offset,
708 cursor.horiz.as_ref(),
709 &mut affinity,
710 1,
711 &Movement::Down,
712 Mode::Insert,
713 );
714 if new_offset != offset {
715 selection.add_region(SelRegion::new(new_offset, new_offset, affinity, None));
716 }
717 cursor.set_insert(selection);
718 }
719 }
720 InsertCursorEndOfLine => {
721 if let CursorMode::Insert(selection) = cursor.mode.clone() {
722 let mut new_selection = Selection::new();
723 for region in selection.regions() {
724 let (start_line, _) = rope_text.offset_to_line_col(region.min());
725 let (end_line, end_col) = rope_text.offset_to_line_col(region.max());
726 for line in start_line..end_line + 1 {
727 let offset = if line == end_line {
728 rope_text.offset_of_line_col(line, end_col)
729 } else {
730 rope_text.line_end_offset(line, true)
731 };
732 new_selection.add_region(SelRegion::new(
733 offset,
734 offset,
735 CursorAffinity::Backward,
736 None,
737 ));
738 }
739 }
740 cursor.set_insert(new_selection);
741 }
742 }
743 SelectCurrentLine => {
744 if let CursorMode::Insert(selection) = cursor.mode.clone() {
745 let mut new_selection = Selection::new();
746 for region in selection.regions() {
747 let start_line = rope_text.line_of_offset(region.min());
748 let start = rope_text.offset_of_line(start_line);
749 let end_line = rope_text.line_of_offset(region.max());
750 let end = rope_text.offset_of_line(end_line + 1);
751 new_selection.add_region(SelRegion::new(
752 start,
753 end,
754 CursorAffinity::Backward,
755 None,
756 ));
757 }
758 cursor.set_insert(new_selection);
759 }
760 }
761 SelectAllCurrent | SelectNextCurrent | SelectSkipCurrent => {
762 }
767 SelectAll => {
768 let new_selection = Selection::region(0, rope_text.len(), CursorAffinity::Forward);
769 cursor.set_insert(new_selection);
770 }
771 }
772}
773
774pub fn do_motion_mode(
775 ed: &Editor,
776 action: &dyn CommonAction,
777 cursor: &mut Cursor,
778 motion_mode: MotionMode,
779 register: &mut Register,
780) {
781 if let Some(cached_motion_mode) = cursor.motion_mode.take() {
782 if core::mem::discriminant(&cached_motion_mode) == core::mem::discriminant(&motion_mode) {
784 let offset = cursor.offset();
785 action.exec_motion_mode(
786 ed,
787 cursor,
788 cached_motion_mode,
789 offset..offset,
790 true,
791 register,
792 );
793 }
794 } else {
795 cursor.motion_mode = Some(motion_mode);
796 }
797}
798
799#[cfg(test)]
800mod tests {
801 use std::rc::Rc;
802
803 use floem_editor_core::{
804 buffer::rope_text::{RopeText, RopeTextVal},
805 cursor::{ColPosition, CursorAffinity},
806 mode::Mode,
807 };
808 use floem_reactive::{Scope, SignalUpdate};
809 use lapce_xi_rope::Rope;
810 use peniko::kurbo::{Rect, Size};
811
812 use crate::views::editor::{
813 movement::{correct_crlf, end_of_line, move_down, move_up},
814 text::SimpleStyling,
815 text_document::TextDocument,
816 };
817
818 use super::Editor;
819
820 fn make_ed(text: &str) -> Editor {
821 let cx = Scope::new();
822 let doc = Rc::new(TextDocument::new(cx, text));
823 let style = Rc::new(SimpleStyling::new());
824 let editor = Editor::new(cx, doc, style, false);
825 editor
826 .viewport
827 .set(Rect::ZERO.with_size(Size::new(f64::MAX, f64::MAX)));
828 editor
829 }
830
831 #[test]
836 fn test_correct_crlf() {
837 let text = Rope::from("hello\nworld");
838 let text = RopeTextVal::new(text);
839 assert_eq!(correct_crlf(&text, 0), 0);
840 assert_eq!(correct_crlf(&text, 5), 5);
841 assert_eq!(correct_crlf(&text, 6), 6);
842 assert_eq!(correct_crlf(&text, text.len()), text.len());
843
844 let text = Rope::from("hello\r\nworld");
845 let text = RopeTextVal::new(text);
846 assert_eq!(correct_crlf(&text, 0), 0);
847 assert_eq!(correct_crlf(&text, 5), 5);
848 assert_eq!(correct_crlf(&text, 6), 5);
849 assert_eq!(correct_crlf(&text, 7), 7);
850 assert_eq!(correct_crlf(&text, text.len()), text.len());
851 }
852
853 #[test]
854 fn test_end_of_line() {
855 let ed = make_ed("abc\ndef\nghi");
856 let mut aff = CursorAffinity::Backward;
857 assert_eq!(end_of_line(&ed, &mut aff, 0, Mode::Insert).0, 3);
858 assert_eq!(aff, CursorAffinity::Backward);
859 assert_eq!(end_of_line(&ed, &mut aff, 1, Mode::Insert).0, 3);
860 assert_eq!(aff, CursorAffinity::Backward);
861 assert_eq!(end_of_line(&ed, &mut aff, 3, Mode::Insert).0, 3);
862 assert_eq!(aff, CursorAffinity::Backward);
863
864 assert_eq!(end_of_line(&ed, &mut aff, 4, Mode::Insert).0, 7);
865 assert_eq!(end_of_line(&ed, &mut aff, 5, Mode::Insert).0, 7);
866 assert_eq!(end_of_line(&ed, &mut aff, 7, Mode::Insert).0, 7);
867
868 let ed = make_ed("abc\r\ndef\r\nghi");
869 let mut aff = CursorAffinity::Forward;
870 assert_eq!(end_of_line(&ed, &mut aff, 0, Mode::Insert).0, 3);
871 assert_eq!(aff, CursorAffinity::Backward);
872
873 assert_eq!(end_of_line(&ed, &mut aff, 1, Mode::Insert).0, 3);
874 assert_eq!(aff, CursorAffinity::Backward);
875 assert_eq!(end_of_line(&ed, &mut aff, 3, Mode::Insert).0, 3);
876 assert_eq!(aff, CursorAffinity::Backward);
877
878 assert_eq!(end_of_line(&ed, &mut aff, 5, Mode::Insert).0, 8);
879 assert_eq!(end_of_line(&ed, &mut aff, 6, Mode::Insert).0, 8);
880 assert_eq!(end_of_line(&ed, &mut aff, 7, Mode::Insert).0, 8);
881 assert_eq!(end_of_line(&ed, &mut aff, 8, Mode::Insert).0, 8);
882
883 let ed = make_ed("testing\r\nAbout\r\nblah");
884 let mut aff = CursorAffinity::Backward;
885 assert_eq!(end_of_line(&ed, &mut aff, 0, Mode::Insert).0, 7);
886 }
887
888 #[test]
889 fn test_move_down() {
890 let ed = make_ed("abc\n\n\ndef\n\nghi");
891
892 let mut aff = CursorAffinity::Forward;
893
894 assert_eq!(move_down(&ed, 0, &mut aff, None, Mode::Insert, 1).0, 4);
895
896 let (offset, horiz) = move_down(&ed, 1, &mut aff, None, Mode::Insert, 1);
897 assert_eq!(offset, 4);
898 assert!(matches!(horiz, ColPosition::Col(_)));
899 let (offset, horiz) = move_down(&ed, 4, &mut aff, Some(horiz), Mode::Insert, 1);
900 assert_eq!(offset, 5);
901 assert!(matches!(horiz, ColPosition::Col(_)));
902 let (offset, _) = move_down(&ed, 5, &mut aff, Some(horiz), Mode::Insert, 1);
903 assert_eq!(offset, 7);
906 }
907
908 #[test]
909 fn test_move_up() {
910 let ed = make_ed("abc\n\n\ndef\n\nghi");
911
912 let mut aff = CursorAffinity::Forward;
913
914 assert_eq!(move_up(&ed, 0, &mut aff, None, Mode::Insert, 1).0, 0);
915
916 let (offset, horiz) = move_up(&ed, 7, &mut aff, None, Mode::Insert, 1);
917 assert_eq!(offset, 5);
918 assert!(matches!(horiz, ColPosition::Col(_)));
919 let (offset, horiz) = move_up(&ed, 5, &mut aff, Some(horiz), Mode::Insert, 1);
920 assert_eq!(offset, 4);
921 assert!(matches!(horiz, ColPosition::Col(_)));
922 let (offset, _) = move_up(&ed, 4, &mut aff, Some(horiz), Mode::Insert, 1);
923 assert_eq!(offset, 1);
926 }
927}