1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum PendingState {
6 Replace {
7 count: usize,
8 },
9 Find {
13 count: usize,
14 forward: bool,
15 till: bool,
16 },
17 AfterG {
22 count: usize,
23 },
24 AfterZ {
29 count: usize,
30 },
31 AfterOp {
42 op: crate::operator::OperatorKind,
43 count1: usize,
44 inner_count: usize,
45 },
46 OpFind {
58 op: crate::operator::OperatorKind,
59 total_count: usize,
60 forward: bool,
61 till: bool,
62 },
63 OpTextObj {
68 op: crate::operator::OperatorKind,
69 total_count: usize,
70 inner: bool,
71 },
72 OpG {
77 op: crate::operator::OperatorKind,
78 total_count: usize,
79 },
80 }
82
83#[derive(Debug, Clone, PartialEq, Eq)]
85pub enum Outcome {
86 Wait(PendingState),
88 Commit(crate::cmd::EngineCmd),
90 Cancel,
92 Forward,
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum Key {
101 Char(char),
102 Esc,
103 Enter,
104 Backspace,
105 Tab,
106 }
108
109pub fn step(state: PendingState, key: Key) -> Outcome {
110 match state {
111 PendingState::Replace { count } => match key {
112 Key::Esc => Outcome::Cancel,
113 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::ReplaceChar { ch, count }),
114 Key::Enter => Outcome::Commit(crate::cmd::EngineCmd::ReplaceChar { ch: '\n', count }),
115 _ => Outcome::Cancel,
116 },
117 PendingState::Find {
118 count,
119 forward,
120 till,
121 } => match key {
122 Key::Esc => Outcome::Cancel,
123 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::FindChar {
124 ch,
125 forward,
126 till,
127 count,
128 }),
129 _ => Outcome::Cancel,
131 },
132 PendingState::AfterG { count } => match key {
133 Key::Esc => Outcome::Cancel,
134 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::AfterGChord { ch, count }),
135 _ => Outcome::Cancel,
137 },
138 PendingState::AfterZ { count } => match key {
139 Key::Esc => Outcome::Cancel,
140 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::AfterZChord { ch, count }),
141 _ => Outcome::Cancel,
143 },
144 PendingState::AfterOp {
145 op,
146 count1,
147 inner_count,
148 } => match key {
149 Key::Esc => Outcome::Cancel,
150 Key::Char(d @ '0'..='9') => {
151 if d == '0' && inner_count == 0 {
153 let total = count1.max(1);
155 Outcome::Commit(crate::cmd::EngineCmd::ApplyOpMotion {
156 op,
157 motion_key: '0',
158 total_count: total,
159 })
160 } else {
161 let new_inner = inner_count
162 .saturating_mul(10)
163 .saturating_add(d as usize - '0' as usize);
164 Outcome::Wait(PendingState::AfterOp {
165 op,
166 count1,
167 inner_count: new_inner,
168 })
169 }
170 }
171 Key::Char(ch) => {
172 let total = count1.max(1) * inner_count.max(1);
173 if ch == op.double_char() {
175 Outcome::Commit(crate::cmd::EngineCmd::ApplyOpDouble {
176 op,
177 total_count: total,
178 })
179 } else if ch == 'i' {
183 Outcome::Wait(PendingState::OpTextObj {
184 op,
185 total_count: count1.max(1) * inner_count.max(1),
186 inner: true,
187 })
188 } else if ch == 'a' {
189 Outcome::Wait(PendingState::OpTextObj {
190 op,
191 total_count: count1.max(1) * inner_count.max(1),
192 inner: false,
193 })
194 } else if ch == 'g' {
199 Outcome::Wait(PendingState::OpG {
200 op,
201 total_count: count1.max(1) * inner_count.max(1),
202 })
203 } else if ch == 'f' {
207 Outcome::Wait(PendingState::OpFind {
208 op,
209 total_count: count1.max(1) * inner_count.max(1),
210 forward: true,
211 till: false,
212 })
213 } else if ch == 'F' {
214 Outcome::Wait(PendingState::OpFind {
215 op,
216 total_count: count1.max(1) * inner_count.max(1),
217 forward: false,
218 till: false,
219 })
220 } else if ch == 't' {
221 Outcome::Wait(PendingState::OpFind {
222 op,
223 total_count: count1.max(1) * inner_count.max(1),
224 forward: true,
225 till: true,
226 })
227 } else if ch == 'T' {
228 Outcome::Wait(PendingState::OpFind {
229 op,
230 total_count: count1.max(1) * inner_count.max(1),
231 forward: false,
232 till: true,
233 })
234 } else {
235 Outcome::Commit(crate::cmd::EngineCmd::ApplyOpMotion {
238 op,
239 motion_key: ch,
240 total_count: total,
241 })
242 }
243 }
244 _ => Outcome::Cancel,
246 },
247 PendingState::OpFind {
248 op,
249 total_count,
250 forward,
251 till,
252 } => match key {
253 Key::Esc => Outcome::Cancel,
254 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::ApplyOpFind {
255 op,
256 ch,
257 forward,
258 till,
259 total_count,
260 }),
261 _ => Outcome::Cancel,
263 },
264 PendingState::OpTextObj {
265 op,
266 total_count,
267 inner,
268 } => match key {
269 Key::Esc => Outcome::Cancel,
270 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::ApplyOpTextObj {
271 op,
272 ch,
273 inner,
274 total_count,
275 }),
276 _ => Outcome::Cancel,
278 },
279 PendingState::OpG { op, total_count } => match key {
280 Key::Esc => Outcome::Cancel,
281 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::ApplyOpG {
282 op,
283 ch,
284 total_count,
285 }),
286 _ => Outcome::Cancel,
289 },
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 use crate::cmd::EngineCmd;
297 use crate::operator::OperatorKind;
298
299 #[test]
302 fn after_g_gg_commits() {
303 let state = PendingState::AfterG { count: 1 };
304 assert_eq!(
305 step(state, Key::Char('g')),
306 Outcome::Commit(EngineCmd::AfterGChord { ch: 'g', count: 1 })
307 );
308 }
309
310 #[test]
311 fn after_g_gv_commits() {
312 let state = PendingState::AfterG { count: 1 };
313 assert_eq!(
314 step(state, Key::Char('v')),
315 Outcome::Commit(EngineCmd::AfterGChord { ch: 'v', count: 1 })
316 );
317 }
318
319 #[test]
320 fn after_g_gu_operator_commits() {
321 let state = PendingState::AfterG { count: 1 };
323 assert_eq!(
324 step(state, Key::Char('U')),
325 Outcome::Commit(EngineCmd::AfterGChord { ch: 'U', count: 1 })
326 );
327 }
328
329 #[test]
330 fn after_g_gi_commits() {
331 let state = PendingState::AfterG { count: 1 };
332 assert_eq!(
333 step(state, Key::Char('i')),
334 Outcome::Commit(EngineCmd::AfterGChord { ch: 'i', count: 1 })
335 );
336 }
337
338 #[test]
339 fn after_g_esc_cancels() {
340 let state = PendingState::AfterG { count: 1 };
341 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
342 }
343
344 #[test]
345 fn after_g_count_carry_through() {
346 let state = PendingState::AfterG { count: 5 };
348 assert_eq!(
349 step(state, Key::Char('g')),
350 Outcome::Commit(EngineCmd::AfterGChord { ch: 'g', count: 5 })
351 );
352 }
353
354 #[test]
355 fn after_g_non_char_cancels() {
356 let state = PendingState::AfterG { count: 1 };
358 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
359 }
360
361 #[test]
364 fn after_z_zz_commits() {
365 let state = PendingState::AfterZ { count: 1 };
366 assert_eq!(
367 step(state, Key::Char('z')),
368 Outcome::Commit(EngineCmd::AfterZChord { ch: 'z', count: 1 })
369 );
370 }
371
372 #[test]
373 fn after_z_zf_commits() {
374 let state = PendingState::AfterZ { count: 1 };
375 assert_eq!(
376 step(state, Key::Char('f')),
377 Outcome::Commit(EngineCmd::AfterZChord { ch: 'f', count: 1 })
378 );
379 }
380
381 #[test]
382 fn after_z_esc_cancels() {
383 let state = PendingState::AfterZ { count: 1 };
384 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
385 }
386
387 #[test]
388 fn after_z_count_carry_through() {
389 let state = PendingState::AfterZ { count: 3 };
391 assert_eq!(
392 step(state, Key::Char('z')),
393 Outcome::Commit(EngineCmd::AfterZChord { ch: 'z', count: 3 })
394 );
395 }
396
397 #[test]
398 fn after_z_non_char_cancels() {
399 let state = PendingState::AfterZ { count: 1 };
401 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
402 }
403
404 fn after_op(op: OperatorKind, count1: usize) -> PendingState {
407 PendingState::AfterOp {
408 op,
409 count1,
410 inner_count: 0,
411 }
412 }
413
414 #[test]
415 fn op_d_then_w_commits_motion() {
416 let state = after_op(OperatorKind::Delete, 1);
417 assert_eq!(
418 step(state, Key::Char('w')),
419 Outcome::Commit(EngineCmd::ApplyOpMotion {
420 op: OperatorKind::Delete,
421 motion_key: 'w',
422 total_count: 1,
423 })
424 );
425 }
426
427 #[test]
428 fn op_d_then_d_commits_double() {
429 let state = after_op(OperatorKind::Delete, 1);
430 assert_eq!(
431 step(state, Key::Char('d')),
432 Outcome::Commit(EngineCmd::ApplyOpDouble {
433 op: OperatorKind::Delete,
434 total_count: 1,
435 })
436 );
437 }
438
439 #[test]
440 fn op_d_inner_count_d3w_commits_motion_with_count_3() {
441 let state = after_op(OperatorKind::Delete, 1);
443 let Outcome::Wait(state2) = step(state, Key::Char('3')) else {
445 panic!("expected Wait");
446 };
447 assert_eq!(
448 state2,
449 PendingState::AfterOp {
450 op: OperatorKind::Delete,
451 count1: 1,
452 inner_count: 3
453 }
454 );
455 assert_eq!(
457 step(state2, Key::Char('w')),
458 Outcome::Commit(EngineCmd::ApplyOpMotion {
459 op: OperatorKind::Delete,
460 motion_key: 'w',
461 total_count: 3,
462 })
463 );
464 }
465
466 #[test]
467 fn op_2d_d_commits_double_with_count_2() {
468 let state = after_op(OperatorKind::Delete, 2);
470 assert_eq!(
471 step(state, Key::Char('d')),
472 Outcome::Commit(EngineCmd::ApplyOpDouble {
473 op: OperatorKind::Delete,
474 total_count: 2,
475 })
476 );
477 }
478
479 #[test]
480 fn op_2d_3w_commits_motion_with_total_6() {
481 let state = after_op(OperatorKind::Delete, 2);
483 let Outcome::Wait(state2) = step(state, Key::Char('3')) else {
484 panic!("expected Wait");
485 };
486 assert_eq!(
487 step(state2, Key::Char('w')),
488 Outcome::Commit(EngineCmd::ApplyOpMotion {
489 op: OperatorKind::Delete,
490 motion_key: 'w',
491 total_count: 6,
492 })
493 );
494 }
495
496 #[test]
497 fn op_d_then_i_transitions_to_op_text_obj_inner() {
498 let state = after_op(OperatorKind::Delete, 1);
500 assert_eq!(
501 step(state, Key::Char('i')),
502 Outcome::Wait(PendingState::OpTextObj {
503 op: OperatorKind::Delete,
504 total_count: 1,
505 inner: true,
506 })
507 );
508 }
509
510 #[test]
511 fn op_d_then_a_transitions_to_op_text_obj_around() {
512 let state = after_op(OperatorKind::Delete, 1);
514 assert_eq!(
515 step(state, Key::Char('a')),
516 Outcome::Wait(PendingState::OpTextObj {
517 op: OperatorKind::Delete,
518 total_count: 1,
519 inner: false,
520 })
521 );
522 }
523
524 #[test]
525 fn op_d_then_g_transitions_to_op_g() {
526 let state = after_op(OperatorKind::Delete, 1);
527 assert_eq!(
528 step(state, Key::Char('g')),
529 Outcome::Wait(PendingState::OpG {
530 op: OperatorKind::Delete,
531 total_count: 1,
532 })
533 );
534 }
535
536 #[test]
537 fn op_d_then_f_transitions_to_op_find_forward_not_till() {
538 let state = after_op(OperatorKind::Delete, 1);
540 assert_eq!(
541 step(state, Key::Char('f')),
542 Outcome::Wait(PendingState::OpFind {
543 op: OperatorKind::Delete,
544 total_count: 1,
545 forward: true,
546 till: false,
547 })
548 );
549 }
550
551 #[test]
552 fn op_d_then_cap_f_transitions_to_op_find_backward_not_till() {
553 let state = after_op(OperatorKind::Delete, 1);
555 assert_eq!(
556 step(state, Key::Char('F')),
557 Outcome::Wait(PendingState::OpFind {
558 op: OperatorKind::Delete,
559 total_count: 1,
560 forward: false,
561 till: false,
562 })
563 );
564 }
565
566 #[test]
567 fn op_d_then_t_transitions_to_op_find_forward_till() {
568 let state = after_op(OperatorKind::Delete, 1);
570 assert_eq!(
571 step(state, Key::Char('t')),
572 Outcome::Wait(PendingState::OpFind {
573 op: OperatorKind::Delete,
574 total_count: 1,
575 forward: true,
576 till: true,
577 })
578 );
579 }
580
581 #[test]
582 fn op_d_then_cap_t_transitions_to_op_find_backward_till() {
583 let state = after_op(OperatorKind::Delete, 1);
585 assert_eq!(
586 step(state, Key::Char('T')),
587 Outcome::Wait(PendingState::OpFind {
588 op: OperatorKind::Delete,
589 total_count: 1,
590 forward: false,
591 till: true,
592 })
593 );
594 }
595
596 fn op_find(op: OperatorKind, total_count: usize, forward: bool, till: bool) -> PendingState {
599 PendingState::OpFind {
600 op,
601 total_count,
602 forward,
603 till,
604 }
605 }
606
607 #[test]
608 fn op_d_then_f_then_x_commits_apply_op_find() {
609 let state = op_find(OperatorKind::Delete, 1, true, false);
611 assert_eq!(
612 step(state, Key::Char('x')),
613 Outcome::Commit(EngineCmd::ApplyOpFind {
614 op: OperatorKind::Delete,
615 ch: 'x',
616 forward: true,
617 till: false,
618 total_count: 1,
619 })
620 );
621 }
622
623 #[test]
624 fn op_d_then_cap_f_then_x_commits_apply_op_find_backward() {
625 let state = op_find(OperatorKind::Delete, 1, false, false);
627 assert_eq!(
628 step(state, Key::Char('x')),
629 Outcome::Commit(EngineCmd::ApplyOpFind {
630 op: OperatorKind::Delete,
631 ch: 'x',
632 forward: false,
633 till: false,
634 total_count: 1,
635 })
636 );
637 }
638
639 #[test]
640 fn op_d_then_t_then_x_commits_apply_op_find_till() {
641 let state = op_find(OperatorKind::Delete, 1, true, true);
643 assert_eq!(
644 step(state, Key::Char('x')),
645 Outcome::Commit(EngineCmd::ApplyOpFind {
646 op: OperatorKind::Delete,
647 ch: 'x',
648 forward: true,
649 till: true,
650 total_count: 1,
651 })
652 );
653 }
654
655 #[test]
656 fn op_d_then_cap_t_then_x_commits_apply_op_find_backward_till() {
657 let state = op_find(OperatorKind::Delete, 1, false, true);
659 assert_eq!(
660 step(state, Key::Char('x')),
661 Outcome::Commit(EngineCmd::ApplyOpFind {
662 op: OperatorKind::Delete,
663 ch: 'x',
664 forward: false,
665 till: true,
666 total_count: 1,
667 })
668 );
669 }
670
671 #[test]
672 fn op_2d_3f_x_commits_total_count_6() {
673 let state = PendingState::AfterOp {
676 op: OperatorKind::Delete,
677 count1: 2,
678 inner_count: 3,
679 };
680 let Outcome::Wait(op_find_state) = step(state, Key::Char('f')) else {
681 panic!("expected Wait(OpFind)");
682 };
683 assert_eq!(
684 op_find_state,
685 PendingState::OpFind {
686 op: OperatorKind::Delete,
687 total_count: 6,
688 forward: true,
689 till: false,
690 }
691 );
692 assert_eq!(
693 step(op_find_state, Key::Char('x')),
694 Outcome::Commit(EngineCmd::ApplyOpFind {
695 op: OperatorKind::Delete,
696 ch: 'x',
697 forward: true,
698 till: false,
699 total_count: 6,
700 })
701 );
702 }
703
704 #[test]
705 fn op_d_f_then_esc_cancels() {
706 let state = op_find(OperatorKind::Delete, 1, true, false);
708 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
709 }
710
711 #[test]
712 fn op_d_f_then_enter_cancels() {
713 let state = op_find(OperatorKind::Delete, 1, true, false);
715 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
716 }
717
718 #[test]
719 fn op_d_then_esc_cancels() {
720 let state = after_op(OperatorKind::Delete, 1);
721 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
722 }
723
724 #[test]
725 fn op_d_non_char_cancels() {
726 let state = after_op(OperatorKind::Delete, 1);
727 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
728 }
729
730 fn op_text_obj(op: OperatorKind, total_count: usize, inner: bool) -> PendingState {
733 PendingState::OpTextObj {
734 op,
735 total_count,
736 inner,
737 }
738 }
739
740 #[test]
741 fn op_d_then_i_then_w_commits_apply_op_text_obj_inner() {
742 let state = op_text_obj(OperatorKind::Delete, 1, true);
744 assert_eq!(
745 step(state, Key::Char('w')),
746 Outcome::Commit(EngineCmd::ApplyOpTextObj {
747 op: OperatorKind::Delete,
748 ch: 'w',
749 inner: true,
750 total_count: 1,
751 })
752 );
753 }
754
755 #[test]
756 fn op_d_then_a_then_w_commits_apply_op_text_obj_around() {
757 let state = op_text_obj(OperatorKind::Delete, 1, false);
759 assert_eq!(
760 step(state, Key::Char('w')),
761 Outcome::Commit(EngineCmd::ApplyOpTextObj {
762 op: OperatorKind::Delete,
763 ch: 'w',
764 inner: false,
765 total_count: 1,
766 })
767 );
768 }
769
770 #[test]
771 fn op_d_then_i_then_quote_commits_with_quote_char() {
772 let state = op_text_obj(OperatorKind::Delete, 1, true);
774 assert_eq!(
775 step(state, Key::Char('"')),
776 Outcome::Commit(EngineCmd::ApplyOpTextObj {
777 op: OperatorKind::Delete,
778 ch: '"',
779 inner: true,
780 total_count: 1,
781 })
782 );
783 }
784
785 #[test]
786 fn op_d_then_i_then_paren_commits_with_paren() {
787 let state = op_text_obj(OperatorKind::Delete, 1, true);
789 assert_eq!(
790 step(state, Key::Char('(')),
791 Outcome::Commit(EngineCmd::ApplyOpTextObj {
792 op: OperatorKind::Delete,
793 ch: '(',
794 inner: true,
795 total_count: 1,
796 })
797 );
798 }
799
800 #[test]
801 fn op_c_then_i_then_p_commits_change_paragraph_inner() {
802 let state = op_text_obj(OperatorKind::Change, 1, true);
804 assert_eq!(
805 step(state, Key::Char('p')),
806 Outcome::Commit(EngineCmd::ApplyOpTextObj {
807 op: OperatorKind::Change,
808 ch: 'p',
809 inner: true,
810 total_count: 1,
811 })
812 );
813 }
814
815 #[test]
816 fn op_d_i_then_esc_cancels() {
817 let state = op_text_obj(OperatorKind::Delete, 1, true);
819 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
820 }
821
822 #[test]
823 fn op_d_i_then_enter_cancels() {
824 let state = op_text_obj(OperatorKind::Delete, 1, true);
826 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
827 }
828
829 #[test]
830 fn op_2d_i_w_total_count_2_preserved() {
831 let state = PendingState::AfterOp {
834 op: OperatorKind::Delete,
835 count1: 2,
836 inner_count: 0,
837 };
838 let Outcome::Wait(obj_state) = step(state, Key::Char('i')) else {
839 panic!("expected Wait(OpTextObj)");
840 };
841 assert_eq!(
842 obj_state,
843 PendingState::OpTextObj {
844 op: OperatorKind::Delete,
845 total_count: 2,
846 inner: true,
847 }
848 );
849 assert_eq!(
850 step(obj_state, Key::Char('w')),
851 Outcome::Commit(EngineCmd::ApplyOpTextObj {
852 op: OperatorKind::Delete,
853 ch: 'w',
854 inner: true,
855 total_count: 2,
856 })
857 );
858 }
859
860 #[test]
861 fn op_d_bare_zero_is_line_start_motion() {
862 let state = after_op(OperatorKind::Delete, 1);
864 assert_eq!(
865 step(state, Key::Char('0')),
866 Outcome::Commit(EngineCmd::ApplyOpMotion {
867 op: OperatorKind::Delete,
868 motion_key: '0',
869 total_count: 1,
870 })
871 );
872 }
873
874 #[test]
875 fn op_d_zero_accumulates_when_inner_count_nonzero() {
876 let state = after_op(OperatorKind::Delete, 1);
878 let Outcome::Wait(s2) = step(state, Key::Char('1')) else {
879 panic!("expected Wait");
880 };
881 let Outcome::Wait(s3) = step(s2, Key::Char('0')) else {
882 panic!("expected Wait");
883 };
884 assert_eq!(
885 s3,
886 PendingState::AfterOp {
887 op: OperatorKind::Delete,
888 count1: 1,
889 inner_count: 10,
890 }
891 );
892 assert_eq!(
893 step(s3, Key::Char('w')),
894 Outcome::Commit(EngineCmd::ApplyOpMotion {
895 op: OperatorKind::Delete,
896 motion_key: 'w',
897 total_count: 10,
898 })
899 );
900 }
901
902 #[test]
905 fn op_yank_doubled() {
906 let state = after_op(OperatorKind::Yank, 1);
907 assert_eq!(
908 step(state, Key::Char('y')),
909 Outcome::Commit(EngineCmd::ApplyOpDouble {
910 op: OperatorKind::Yank,
911 total_count: 1,
912 })
913 );
914 }
915
916 #[test]
917 fn op_change_doubled() {
918 let state = after_op(OperatorKind::Change, 1);
919 assert_eq!(
920 step(state, Key::Char('c')),
921 Outcome::Commit(EngineCmd::ApplyOpDouble {
922 op: OperatorKind::Change,
923 total_count: 1,
924 })
925 );
926 }
927
928 #[test]
929 fn op_indent_doubled() {
930 let state = after_op(OperatorKind::Indent, 1);
931 assert_eq!(
932 step(state, Key::Char('>')),
933 Outcome::Commit(EngineCmd::ApplyOpDouble {
934 op: OperatorKind::Indent,
935 total_count: 1,
936 })
937 );
938 }
939
940 #[test]
941 fn op_outdent_doubled() {
942 let state = after_op(OperatorKind::Outdent, 1);
943 assert_eq!(
944 step(state, Key::Char('<')),
945 Outcome::Commit(EngineCmd::ApplyOpDouble {
946 op: OperatorKind::Outdent,
947 total_count: 1,
948 })
949 );
950 }
951
952 #[test]
953 fn op_yank_motion() {
954 let state = after_op(OperatorKind::Yank, 1);
955 assert_eq!(
956 step(state, Key::Char('$')),
957 Outcome::Commit(EngineCmd::ApplyOpMotion {
958 op: OperatorKind::Yank,
959 motion_key: '$',
960 total_count: 1,
961 })
962 );
963 }
964
965 #[test]
966 fn op_change_motion() {
967 let state = after_op(OperatorKind::Change, 1);
968 assert_eq!(
969 step(state, Key::Char('w')),
970 Outcome::Commit(EngineCmd::ApplyOpMotion {
971 op: OperatorKind::Change,
972 motion_key: 'w',
973 total_count: 1,
974 })
975 );
976 }
977
978 #[test]
979 fn op_indent_motion() {
980 let state = after_op(OperatorKind::Indent, 1);
981 assert_eq!(
982 step(state, Key::Char('j')),
983 Outcome::Commit(EngineCmd::ApplyOpMotion {
984 op: OperatorKind::Indent,
985 motion_key: 'j',
986 total_count: 1,
987 })
988 );
989 }
990
991 #[test]
992 fn op_outdent_motion() {
993 let state = after_op(OperatorKind::Outdent, 1);
994 assert_eq!(
995 step(state, Key::Char('k')),
996 Outcome::Commit(EngineCmd::ApplyOpMotion {
997 op: OperatorKind::Outdent,
998 motion_key: 'k',
999 total_count: 1,
1000 })
1001 );
1002 }
1003
1004 fn op_g(op: OperatorKind, total_count: usize) -> PendingState {
1007 PendingState::OpG { op, total_count }
1008 }
1009
1010 #[test]
1011 fn op_d_then_g_then_g_commits_apply_op_g_for_gg() {
1012 let state = op_g(OperatorKind::Delete, 1);
1014 assert_eq!(
1015 step(state, Key::Char('g')),
1016 Outcome::Commit(EngineCmd::ApplyOpG {
1017 op: OperatorKind::Delete,
1018 ch: 'g',
1019 total_count: 1,
1020 })
1021 );
1022 }
1023
1024 #[test]
1025 fn op_d_then_g_then_e_commits_for_ge() {
1026 let state = op_g(OperatorKind::Delete, 1);
1028 assert_eq!(
1029 step(state, Key::Char('e')),
1030 Outcome::Commit(EngineCmd::ApplyOpG {
1031 op: OperatorKind::Delete,
1032 ch: 'e',
1033 total_count: 1,
1034 })
1035 );
1036 }
1037
1038 #[test]
1039 fn op_d_then_g_then_j_commits_for_gj() {
1040 let state = op_g(OperatorKind::Delete, 1);
1042 assert_eq!(
1043 step(state, Key::Char('j')),
1044 Outcome::Commit(EngineCmd::ApplyOpG {
1045 op: OperatorKind::Delete,
1046 ch: 'j',
1047 total_count: 1,
1048 })
1049 );
1050 }
1051
1052 #[test]
1053 fn op_2d_3g_g_total_count_6() {
1054 let state = PendingState::AfterOp {
1057 op: OperatorKind::Delete,
1058 count1: 2,
1059 inner_count: 3,
1060 };
1061 let Outcome::Wait(op_g_state) = step(state, Key::Char('g')) else {
1062 panic!("expected Wait(OpG)");
1063 };
1064 assert_eq!(
1065 op_g_state,
1066 PendingState::OpG {
1067 op: OperatorKind::Delete,
1068 total_count: 6,
1069 }
1070 );
1071 assert_eq!(
1072 step(op_g_state, Key::Char('g')),
1073 Outcome::Commit(EngineCmd::ApplyOpG {
1074 op: OperatorKind::Delete,
1075 ch: 'g',
1076 total_count: 6,
1077 })
1078 );
1079 }
1080
1081 #[test]
1082 fn op_d_g_then_esc_cancels() {
1083 let state = op_g(OperatorKind::Delete, 1);
1085 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
1086 }
1087
1088 #[test]
1089 fn op_d_g_then_enter_cancels() {
1090 let state = op_g(OperatorKind::Delete, 1);
1092 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
1093 }
1094
1095 #[test]
1096 fn op_c_then_g_then_g_commits_change_op_g() {
1097 let state = op_g(OperatorKind::Change, 1);
1099 assert_eq!(
1100 step(state, Key::Char('g')),
1101 Outcome::Commit(EngineCmd::ApplyOpG {
1102 op: OperatorKind::Change,
1103 ch: 'g',
1104 total_count: 1,
1105 })
1106 );
1107 }
1108}