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 SelectRegister,
84 SetMark,
88 GotoMarkLine,
92 GotoMarkChar,
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
99pub enum Outcome {
100 Wait(PendingState),
102 Commit(crate::cmd::EngineCmd),
104 Cancel,
106 Forward,
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum Key {
115 Char(char),
116 Esc,
117 Enter,
118 Backspace,
119 Tab,
120 }
122
123pub fn step(state: PendingState, key: Key) -> Outcome {
124 match state {
125 PendingState::Replace { count } => match key {
126 Key::Esc => Outcome::Cancel,
127 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::ReplaceChar { ch, count }),
128 Key::Enter => Outcome::Commit(crate::cmd::EngineCmd::ReplaceChar { ch: '\n', count }),
129 _ => Outcome::Cancel,
130 },
131 PendingState::Find {
132 count,
133 forward,
134 till,
135 } => match key {
136 Key::Esc => Outcome::Cancel,
137 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::FindChar {
138 ch,
139 forward,
140 till,
141 count,
142 }),
143 _ => Outcome::Cancel,
145 },
146 PendingState::AfterG { count } => match key {
147 Key::Esc => Outcome::Cancel,
148 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::AfterGChord { ch, count }),
149 _ => Outcome::Cancel,
151 },
152 PendingState::AfterZ { count } => match key {
153 Key::Esc => Outcome::Cancel,
154 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::AfterZChord { ch, count }),
155 _ => Outcome::Cancel,
157 },
158 PendingState::AfterOp {
159 op,
160 count1,
161 inner_count,
162 } => match key {
163 Key::Esc => Outcome::Cancel,
164 Key::Char(d @ '0'..='9') => {
165 if d == '0' && inner_count == 0 {
167 let total = count1.max(1);
169 Outcome::Commit(crate::cmd::EngineCmd::ApplyOpMotion {
170 op,
171 motion_key: '0',
172 total_count: total,
173 })
174 } else {
175 let new_inner = inner_count
176 .saturating_mul(10)
177 .saturating_add(d as usize - '0' as usize);
178 Outcome::Wait(PendingState::AfterOp {
179 op,
180 count1,
181 inner_count: new_inner,
182 })
183 }
184 }
185 Key::Char(ch) => {
186 let total = count1.max(1) * inner_count.max(1);
187 if ch == op.double_char() {
189 Outcome::Commit(crate::cmd::EngineCmd::ApplyOpDouble {
190 op,
191 total_count: total,
192 })
193 } else if ch == 'i' {
197 Outcome::Wait(PendingState::OpTextObj {
198 op,
199 total_count: count1.max(1) * inner_count.max(1),
200 inner: true,
201 })
202 } else if ch == 'a' {
203 Outcome::Wait(PendingState::OpTextObj {
204 op,
205 total_count: count1.max(1) * inner_count.max(1),
206 inner: false,
207 })
208 } else if ch == 'g' {
213 Outcome::Wait(PendingState::OpG {
214 op,
215 total_count: count1.max(1) * inner_count.max(1),
216 })
217 } else if ch == 'f' {
221 Outcome::Wait(PendingState::OpFind {
222 op,
223 total_count: count1.max(1) * inner_count.max(1),
224 forward: true,
225 till: false,
226 })
227 } else if ch == 'F' {
228 Outcome::Wait(PendingState::OpFind {
229 op,
230 total_count: count1.max(1) * inner_count.max(1),
231 forward: false,
232 till: false,
233 })
234 } else if ch == 't' {
235 Outcome::Wait(PendingState::OpFind {
236 op,
237 total_count: count1.max(1) * inner_count.max(1),
238 forward: true,
239 till: true,
240 })
241 } else if ch == 'T' {
242 Outcome::Wait(PendingState::OpFind {
243 op,
244 total_count: count1.max(1) * inner_count.max(1),
245 forward: false,
246 till: true,
247 })
248 } else {
249 Outcome::Commit(crate::cmd::EngineCmd::ApplyOpMotion {
252 op,
253 motion_key: ch,
254 total_count: total,
255 })
256 }
257 }
258 _ => Outcome::Cancel,
260 },
261 PendingState::OpFind {
262 op,
263 total_count,
264 forward,
265 till,
266 } => match key {
267 Key::Esc => Outcome::Cancel,
268 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::ApplyOpFind {
269 op,
270 ch,
271 forward,
272 till,
273 total_count,
274 }),
275 _ => Outcome::Cancel,
277 },
278 PendingState::OpTextObj {
279 op,
280 total_count,
281 inner,
282 } => match key {
283 Key::Esc => Outcome::Cancel,
284 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::ApplyOpTextObj {
285 op,
286 ch,
287 inner,
288 total_count,
289 }),
290 _ => Outcome::Cancel,
292 },
293 PendingState::OpG { op, total_count } => match key {
294 Key::Esc => Outcome::Cancel,
295 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::ApplyOpG {
296 op,
297 ch,
298 total_count,
299 }),
300 _ => Outcome::Cancel,
303 },
304 PendingState::SelectRegister => match key {
305 Key::Esc => Outcome::Cancel,
306 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::SetPendingRegister { reg: ch }),
307 _ => Outcome::Cancel,
309 },
310 PendingState::SetMark => match key {
311 Key::Esc => Outcome::Cancel,
312 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::SetMark { ch }),
313 _ => Outcome::Cancel,
315 },
316 PendingState::GotoMarkLine => match key {
317 Key::Esc => Outcome::Cancel,
318 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::GotoMarkLine { ch }),
319 _ => Outcome::Cancel,
321 },
322 PendingState::GotoMarkChar => match key {
323 Key::Esc => Outcome::Cancel,
324 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::GotoMarkChar { ch }),
325 _ => Outcome::Cancel,
327 },
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334 use crate::cmd::EngineCmd;
335 use crate::operator::OperatorKind;
336
337 #[test]
340 fn after_g_gg_commits() {
341 let state = PendingState::AfterG { count: 1 };
342 assert_eq!(
343 step(state, Key::Char('g')),
344 Outcome::Commit(EngineCmd::AfterGChord { ch: 'g', count: 1 })
345 );
346 }
347
348 #[test]
349 fn after_g_gv_commits() {
350 let state = PendingState::AfterG { count: 1 };
351 assert_eq!(
352 step(state, Key::Char('v')),
353 Outcome::Commit(EngineCmd::AfterGChord { ch: 'v', count: 1 })
354 );
355 }
356
357 #[test]
358 fn after_g_gu_operator_commits() {
359 let state = PendingState::AfterG { count: 1 };
361 assert_eq!(
362 step(state, Key::Char('U')),
363 Outcome::Commit(EngineCmd::AfterGChord { ch: 'U', count: 1 })
364 );
365 }
366
367 #[test]
368 fn after_g_gi_commits() {
369 let state = PendingState::AfterG { count: 1 };
370 assert_eq!(
371 step(state, Key::Char('i')),
372 Outcome::Commit(EngineCmd::AfterGChord { ch: 'i', count: 1 })
373 );
374 }
375
376 #[test]
377 fn after_g_esc_cancels() {
378 let state = PendingState::AfterG { count: 1 };
379 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
380 }
381
382 #[test]
383 fn after_g_count_carry_through() {
384 let state = PendingState::AfterG { count: 5 };
386 assert_eq!(
387 step(state, Key::Char('g')),
388 Outcome::Commit(EngineCmd::AfterGChord { ch: 'g', count: 5 })
389 );
390 }
391
392 #[test]
393 fn after_g_non_char_cancels() {
394 let state = PendingState::AfterG { count: 1 };
396 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
397 }
398
399 #[test]
402 fn after_z_zz_commits() {
403 let state = PendingState::AfterZ { count: 1 };
404 assert_eq!(
405 step(state, Key::Char('z')),
406 Outcome::Commit(EngineCmd::AfterZChord { ch: 'z', count: 1 })
407 );
408 }
409
410 #[test]
411 fn after_z_zf_commits() {
412 let state = PendingState::AfterZ { count: 1 };
413 assert_eq!(
414 step(state, Key::Char('f')),
415 Outcome::Commit(EngineCmd::AfterZChord { ch: 'f', count: 1 })
416 );
417 }
418
419 #[test]
420 fn after_z_esc_cancels() {
421 let state = PendingState::AfterZ { count: 1 };
422 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
423 }
424
425 #[test]
426 fn after_z_count_carry_through() {
427 let state = PendingState::AfterZ { count: 3 };
429 assert_eq!(
430 step(state, Key::Char('z')),
431 Outcome::Commit(EngineCmd::AfterZChord { ch: 'z', count: 3 })
432 );
433 }
434
435 #[test]
436 fn after_z_non_char_cancels() {
437 let state = PendingState::AfterZ { count: 1 };
439 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
440 }
441
442 fn after_op(op: OperatorKind, count1: usize) -> PendingState {
445 PendingState::AfterOp {
446 op,
447 count1,
448 inner_count: 0,
449 }
450 }
451
452 #[test]
453 fn op_d_then_w_commits_motion() {
454 let state = after_op(OperatorKind::Delete, 1);
455 assert_eq!(
456 step(state, Key::Char('w')),
457 Outcome::Commit(EngineCmd::ApplyOpMotion {
458 op: OperatorKind::Delete,
459 motion_key: 'w',
460 total_count: 1,
461 })
462 );
463 }
464
465 #[test]
466 fn op_d_then_d_commits_double() {
467 let state = after_op(OperatorKind::Delete, 1);
468 assert_eq!(
469 step(state, Key::Char('d')),
470 Outcome::Commit(EngineCmd::ApplyOpDouble {
471 op: OperatorKind::Delete,
472 total_count: 1,
473 })
474 );
475 }
476
477 #[test]
478 fn op_d_inner_count_d3w_commits_motion_with_count_3() {
479 let state = after_op(OperatorKind::Delete, 1);
481 let Outcome::Wait(state2) = step(state, Key::Char('3')) else {
483 panic!("expected Wait");
484 };
485 assert_eq!(
486 state2,
487 PendingState::AfterOp {
488 op: OperatorKind::Delete,
489 count1: 1,
490 inner_count: 3
491 }
492 );
493 assert_eq!(
495 step(state2, Key::Char('w')),
496 Outcome::Commit(EngineCmd::ApplyOpMotion {
497 op: OperatorKind::Delete,
498 motion_key: 'w',
499 total_count: 3,
500 })
501 );
502 }
503
504 #[test]
505 fn op_2d_d_commits_double_with_count_2() {
506 let state = after_op(OperatorKind::Delete, 2);
508 assert_eq!(
509 step(state, Key::Char('d')),
510 Outcome::Commit(EngineCmd::ApplyOpDouble {
511 op: OperatorKind::Delete,
512 total_count: 2,
513 })
514 );
515 }
516
517 #[test]
518 fn op_2d_3w_commits_motion_with_total_6() {
519 let state = after_op(OperatorKind::Delete, 2);
521 let Outcome::Wait(state2) = step(state, Key::Char('3')) else {
522 panic!("expected Wait");
523 };
524 assert_eq!(
525 step(state2, Key::Char('w')),
526 Outcome::Commit(EngineCmd::ApplyOpMotion {
527 op: OperatorKind::Delete,
528 motion_key: 'w',
529 total_count: 6,
530 })
531 );
532 }
533
534 #[test]
535 fn op_d_then_i_transitions_to_op_text_obj_inner() {
536 let state = after_op(OperatorKind::Delete, 1);
538 assert_eq!(
539 step(state, Key::Char('i')),
540 Outcome::Wait(PendingState::OpTextObj {
541 op: OperatorKind::Delete,
542 total_count: 1,
543 inner: true,
544 })
545 );
546 }
547
548 #[test]
549 fn op_d_then_a_transitions_to_op_text_obj_around() {
550 let state = after_op(OperatorKind::Delete, 1);
552 assert_eq!(
553 step(state, Key::Char('a')),
554 Outcome::Wait(PendingState::OpTextObj {
555 op: OperatorKind::Delete,
556 total_count: 1,
557 inner: false,
558 })
559 );
560 }
561
562 #[test]
563 fn op_d_then_g_transitions_to_op_g() {
564 let state = after_op(OperatorKind::Delete, 1);
565 assert_eq!(
566 step(state, Key::Char('g')),
567 Outcome::Wait(PendingState::OpG {
568 op: OperatorKind::Delete,
569 total_count: 1,
570 })
571 );
572 }
573
574 #[test]
575 fn op_d_then_f_transitions_to_op_find_forward_not_till() {
576 let state = after_op(OperatorKind::Delete, 1);
578 assert_eq!(
579 step(state, Key::Char('f')),
580 Outcome::Wait(PendingState::OpFind {
581 op: OperatorKind::Delete,
582 total_count: 1,
583 forward: true,
584 till: false,
585 })
586 );
587 }
588
589 #[test]
590 fn op_d_then_cap_f_transitions_to_op_find_backward_not_till() {
591 let state = after_op(OperatorKind::Delete, 1);
593 assert_eq!(
594 step(state, Key::Char('F')),
595 Outcome::Wait(PendingState::OpFind {
596 op: OperatorKind::Delete,
597 total_count: 1,
598 forward: false,
599 till: false,
600 })
601 );
602 }
603
604 #[test]
605 fn op_d_then_t_transitions_to_op_find_forward_till() {
606 let state = after_op(OperatorKind::Delete, 1);
608 assert_eq!(
609 step(state, Key::Char('t')),
610 Outcome::Wait(PendingState::OpFind {
611 op: OperatorKind::Delete,
612 total_count: 1,
613 forward: true,
614 till: true,
615 })
616 );
617 }
618
619 #[test]
620 fn op_d_then_cap_t_transitions_to_op_find_backward_till() {
621 let state = after_op(OperatorKind::Delete, 1);
623 assert_eq!(
624 step(state, Key::Char('T')),
625 Outcome::Wait(PendingState::OpFind {
626 op: OperatorKind::Delete,
627 total_count: 1,
628 forward: false,
629 till: true,
630 })
631 );
632 }
633
634 fn op_find(op: OperatorKind, total_count: usize, forward: bool, till: bool) -> PendingState {
637 PendingState::OpFind {
638 op,
639 total_count,
640 forward,
641 till,
642 }
643 }
644
645 #[test]
646 fn op_d_then_f_then_x_commits_apply_op_find() {
647 let state = op_find(OperatorKind::Delete, 1, true, false);
649 assert_eq!(
650 step(state, Key::Char('x')),
651 Outcome::Commit(EngineCmd::ApplyOpFind {
652 op: OperatorKind::Delete,
653 ch: 'x',
654 forward: true,
655 till: false,
656 total_count: 1,
657 })
658 );
659 }
660
661 #[test]
662 fn op_d_then_cap_f_then_x_commits_apply_op_find_backward() {
663 let state = op_find(OperatorKind::Delete, 1, false, false);
665 assert_eq!(
666 step(state, Key::Char('x')),
667 Outcome::Commit(EngineCmd::ApplyOpFind {
668 op: OperatorKind::Delete,
669 ch: 'x',
670 forward: false,
671 till: false,
672 total_count: 1,
673 })
674 );
675 }
676
677 #[test]
678 fn op_d_then_t_then_x_commits_apply_op_find_till() {
679 let state = op_find(OperatorKind::Delete, 1, true, true);
681 assert_eq!(
682 step(state, Key::Char('x')),
683 Outcome::Commit(EngineCmd::ApplyOpFind {
684 op: OperatorKind::Delete,
685 ch: 'x',
686 forward: true,
687 till: true,
688 total_count: 1,
689 })
690 );
691 }
692
693 #[test]
694 fn op_d_then_cap_t_then_x_commits_apply_op_find_backward_till() {
695 let state = op_find(OperatorKind::Delete, 1, false, true);
697 assert_eq!(
698 step(state, Key::Char('x')),
699 Outcome::Commit(EngineCmd::ApplyOpFind {
700 op: OperatorKind::Delete,
701 ch: 'x',
702 forward: false,
703 till: true,
704 total_count: 1,
705 })
706 );
707 }
708
709 #[test]
710 fn op_2d_3f_x_commits_total_count_6() {
711 let state = PendingState::AfterOp {
714 op: OperatorKind::Delete,
715 count1: 2,
716 inner_count: 3,
717 };
718 let Outcome::Wait(op_find_state) = step(state, Key::Char('f')) else {
719 panic!("expected Wait(OpFind)");
720 };
721 assert_eq!(
722 op_find_state,
723 PendingState::OpFind {
724 op: OperatorKind::Delete,
725 total_count: 6,
726 forward: true,
727 till: false,
728 }
729 );
730 assert_eq!(
731 step(op_find_state, Key::Char('x')),
732 Outcome::Commit(EngineCmd::ApplyOpFind {
733 op: OperatorKind::Delete,
734 ch: 'x',
735 forward: true,
736 till: false,
737 total_count: 6,
738 })
739 );
740 }
741
742 #[test]
743 fn op_d_f_then_esc_cancels() {
744 let state = op_find(OperatorKind::Delete, 1, true, false);
746 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
747 }
748
749 #[test]
750 fn op_d_f_then_enter_cancels() {
751 let state = op_find(OperatorKind::Delete, 1, true, false);
753 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
754 }
755
756 #[test]
757 fn op_d_then_esc_cancels() {
758 let state = after_op(OperatorKind::Delete, 1);
759 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
760 }
761
762 #[test]
763 fn op_d_non_char_cancels() {
764 let state = after_op(OperatorKind::Delete, 1);
765 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
766 }
767
768 fn op_text_obj(op: OperatorKind, total_count: usize, inner: bool) -> PendingState {
771 PendingState::OpTextObj {
772 op,
773 total_count,
774 inner,
775 }
776 }
777
778 #[test]
779 fn op_d_then_i_then_w_commits_apply_op_text_obj_inner() {
780 let state = op_text_obj(OperatorKind::Delete, 1, true);
782 assert_eq!(
783 step(state, Key::Char('w')),
784 Outcome::Commit(EngineCmd::ApplyOpTextObj {
785 op: OperatorKind::Delete,
786 ch: 'w',
787 inner: true,
788 total_count: 1,
789 })
790 );
791 }
792
793 #[test]
794 fn op_d_then_a_then_w_commits_apply_op_text_obj_around() {
795 let state = op_text_obj(OperatorKind::Delete, 1, false);
797 assert_eq!(
798 step(state, Key::Char('w')),
799 Outcome::Commit(EngineCmd::ApplyOpTextObj {
800 op: OperatorKind::Delete,
801 ch: 'w',
802 inner: false,
803 total_count: 1,
804 })
805 );
806 }
807
808 #[test]
809 fn op_d_then_i_then_quote_commits_with_quote_char() {
810 let state = op_text_obj(OperatorKind::Delete, 1, true);
812 assert_eq!(
813 step(state, Key::Char('"')),
814 Outcome::Commit(EngineCmd::ApplyOpTextObj {
815 op: OperatorKind::Delete,
816 ch: '"',
817 inner: true,
818 total_count: 1,
819 })
820 );
821 }
822
823 #[test]
824 fn op_d_then_i_then_paren_commits_with_paren() {
825 let state = op_text_obj(OperatorKind::Delete, 1, true);
827 assert_eq!(
828 step(state, Key::Char('(')),
829 Outcome::Commit(EngineCmd::ApplyOpTextObj {
830 op: OperatorKind::Delete,
831 ch: '(',
832 inner: true,
833 total_count: 1,
834 })
835 );
836 }
837
838 #[test]
839 fn op_c_then_i_then_p_commits_change_paragraph_inner() {
840 let state = op_text_obj(OperatorKind::Change, 1, true);
842 assert_eq!(
843 step(state, Key::Char('p')),
844 Outcome::Commit(EngineCmd::ApplyOpTextObj {
845 op: OperatorKind::Change,
846 ch: 'p',
847 inner: true,
848 total_count: 1,
849 })
850 );
851 }
852
853 #[test]
854 fn op_d_i_then_esc_cancels() {
855 let state = op_text_obj(OperatorKind::Delete, 1, true);
857 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
858 }
859
860 #[test]
861 fn op_d_i_then_enter_cancels() {
862 let state = op_text_obj(OperatorKind::Delete, 1, true);
864 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
865 }
866
867 #[test]
868 fn op_2d_i_w_total_count_2_preserved() {
869 let state = PendingState::AfterOp {
872 op: OperatorKind::Delete,
873 count1: 2,
874 inner_count: 0,
875 };
876 let Outcome::Wait(obj_state) = step(state, Key::Char('i')) else {
877 panic!("expected Wait(OpTextObj)");
878 };
879 assert_eq!(
880 obj_state,
881 PendingState::OpTextObj {
882 op: OperatorKind::Delete,
883 total_count: 2,
884 inner: true,
885 }
886 );
887 assert_eq!(
888 step(obj_state, Key::Char('w')),
889 Outcome::Commit(EngineCmd::ApplyOpTextObj {
890 op: OperatorKind::Delete,
891 ch: 'w',
892 inner: true,
893 total_count: 2,
894 })
895 );
896 }
897
898 #[test]
899 fn op_d_bare_zero_is_line_start_motion() {
900 let state = after_op(OperatorKind::Delete, 1);
902 assert_eq!(
903 step(state, Key::Char('0')),
904 Outcome::Commit(EngineCmd::ApplyOpMotion {
905 op: OperatorKind::Delete,
906 motion_key: '0',
907 total_count: 1,
908 })
909 );
910 }
911
912 #[test]
913 fn op_d_zero_accumulates_when_inner_count_nonzero() {
914 let state = after_op(OperatorKind::Delete, 1);
916 let Outcome::Wait(s2) = step(state, Key::Char('1')) else {
917 panic!("expected Wait");
918 };
919 let Outcome::Wait(s3) = step(s2, Key::Char('0')) else {
920 panic!("expected Wait");
921 };
922 assert_eq!(
923 s3,
924 PendingState::AfterOp {
925 op: OperatorKind::Delete,
926 count1: 1,
927 inner_count: 10,
928 }
929 );
930 assert_eq!(
931 step(s3, Key::Char('w')),
932 Outcome::Commit(EngineCmd::ApplyOpMotion {
933 op: OperatorKind::Delete,
934 motion_key: 'w',
935 total_count: 10,
936 })
937 );
938 }
939
940 #[test]
943 fn op_yank_doubled() {
944 let state = after_op(OperatorKind::Yank, 1);
945 assert_eq!(
946 step(state, Key::Char('y')),
947 Outcome::Commit(EngineCmd::ApplyOpDouble {
948 op: OperatorKind::Yank,
949 total_count: 1,
950 })
951 );
952 }
953
954 #[test]
955 fn op_change_doubled() {
956 let state = after_op(OperatorKind::Change, 1);
957 assert_eq!(
958 step(state, Key::Char('c')),
959 Outcome::Commit(EngineCmd::ApplyOpDouble {
960 op: OperatorKind::Change,
961 total_count: 1,
962 })
963 );
964 }
965
966 #[test]
967 fn op_indent_doubled() {
968 let state = after_op(OperatorKind::Indent, 1);
969 assert_eq!(
970 step(state, Key::Char('>')),
971 Outcome::Commit(EngineCmd::ApplyOpDouble {
972 op: OperatorKind::Indent,
973 total_count: 1,
974 })
975 );
976 }
977
978 #[test]
979 fn op_outdent_doubled() {
980 let state = after_op(OperatorKind::Outdent, 1);
981 assert_eq!(
982 step(state, Key::Char('<')),
983 Outcome::Commit(EngineCmd::ApplyOpDouble {
984 op: OperatorKind::Outdent,
985 total_count: 1,
986 })
987 );
988 }
989
990 #[test]
993 fn op_uppercase_then_cap_u_commits_double() {
994 let state = after_op(OperatorKind::Uppercase, 1);
996 assert_eq!(
997 step(state, Key::Char('U')),
998 Outcome::Commit(EngineCmd::ApplyOpDouble {
999 op: OperatorKind::Uppercase,
1000 total_count: 1,
1001 })
1002 );
1003 }
1004
1005 #[test]
1006 fn op_lowercase_then_u_commits_double() {
1007 let state = after_op(OperatorKind::Lowercase, 1);
1009 assert_eq!(
1010 step(state, Key::Char('u')),
1011 Outcome::Commit(EngineCmd::ApplyOpDouble {
1012 op: OperatorKind::Lowercase,
1013 total_count: 1,
1014 })
1015 );
1016 }
1017
1018 #[test]
1019 fn op_togglecase_then_tilde_commits_double() {
1020 let state = after_op(OperatorKind::ToggleCase, 1);
1022 assert_eq!(
1023 step(state, Key::Char('~')),
1024 Outcome::Commit(EngineCmd::ApplyOpDouble {
1025 op: OperatorKind::ToggleCase,
1026 total_count: 1,
1027 })
1028 );
1029 }
1030
1031 #[test]
1032 fn op_reflow_then_q_commits_double() {
1033 let state = after_op(OperatorKind::Reflow, 1);
1035 assert_eq!(
1036 step(state, Key::Char('q')),
1037 Outcome::Commit(EngineCmd::ApplyOpDouble {
1038 op: OperatorKind::Reflow,
1039 total_count: 1,
1040 })
1041 );
1042 }
1043
1044 #[test]
1045 fn op_uppercase_then_w_commits_motion() {
1046 let state = after_op(OperatorKind::Uppercase, 1);
1048 assert_eq!(
1049 step(state, Key::Char('w')),
1050 Outcome::Commit(EngineCmd::ApplyOpMotion {
1051 op: OperatorKind::Uppercase,
1052 motion_key: 'w',
1053 total_count: 1,
1054 })
1055 );
1056 }
1057
1058 #[test]
1059 fn op_reflow_then_ap_commits_text_obj() {
1060 let state = after_op(OperatorKind::Reflow, 1);
1063 let Outcome::Wait(obj_state) = step(state, Key::Char('a')) else {
1064 panic!("expected Wait(OpTextObj)");
1065 };
1066 assert_eq!(
1067 obj_state,
1068 PendingState::OpTextObj {
1069 op: OperatorKind::Reflow,
1070 total_count: 1,
1071 inner: false,
1072 }
1073 );
1074 assert_eq!(
1076 step(obj_state, Key::Char('p')),
1077 Outcome::Commit(EngineCmd::ApplyOpTextObj {
1078 op: OperatorKind::Reflow,
1079 ch: 'p',
1080 inner: false,
1081 total_count: 1,
1082 })
1083 );
1084 }
1085
1086 #[test]
1087 fn op_yank_motion() {
1088 let state = after_op(OperatorKind::Yank, 1);
1089 assert_eq!(
1090 step(state, Key::Char('$')),
1091 Outcome::Commit(EngineCmd::ApplyOpMotion {
1092 op: OperatorKind::Yank,
1093 motion_key: '$',
1094 total_count: 1,
1095 })
1096 );
1097 }
1098
1099 #[test]
1100 fn op_change_motion() {
1101 let state = after_op(OperatorKind::Change, 1);
1102 assert_eq!(
1103 step(state, Key::Char('w')),
1104 Outcome::Commit(EngineCmd::ApplyOpMotion {
1105 op: OperatorKind::Change,
1106 motion_key: 'w',
1107 total_count: 1,
1108 })
1109 );
1110 }
1111
1112 #[test]
1113 fn op_indent_motion() {
1114 let state = after_op(OperatorKind::Indent, 1);
1115 assert_eq!(
1116 step(state, Key::Char('j')),
1117 Outcome::Commit(EngineCmd::ApplyOpMotion {
1118 op: OperatorKind::Indent,
1119 motion_key: 'j',
1120 total_count: 1,
1121 })
1122 );
1123 }
1124
1125 #[test]
1126 fn op_outdent_motion() {
1127 let state = after_op(OperatorKind::Outdent, 1);
1128 assert_eq!(
1129 step(state, Key::Char('k')),
1130 Outcome::Commit(EngineCmd::ApplyOpMotion {
1131 op: OperatorKind::Outdent,
1132 motion_key: 'k',
1133 total_count: 1,
1134 })
1135 );
1136 }
1137
1138 fn op_g(op: OperatorKind, total_count: usize) -> PendingState {
1141 PendingState::OpG { op, total_count }
1142 }
1143
1144 #[test]
1145 fn op_d_then_g_then_g_commits_apply_op_g_for_gg() {
1146 let state = op_g(OperatorKind::Delete, 1);
1148 assert_eq!(
1149 step(state, Key::Char('g')),
1150 Outcome::Commit(EngineCmd::ApplyOpG {
1151 op: OperatorKind::Delete,
1152 ch: 'g',
1153 total_count: 1,
1154 })
1155 );
1156 }
1157
1158 #[test]
1159 fn op_d_then_g_then_e_commits_for_ge() {
1160 let state = op_g(OperatorKind::Delete, 1);
1162 assert_eq!(
1163 step(state, Key::Char('e')),
1164 Outcome::Commit(EngineCmd::ApplyOpG {
1165 op: OperatorKind::Delete,
1166 ch: 'e',
1167 total_count: 1,
1168 })
1169 );
1170 }
1171
1172 #[test]
1173 fn op_d_then_g_then_j_commits_for_gj() {
1174 let state = op_g(OperatorKind::Delete, 1);
1176 assert_eq!(
1177 step(state, Key::Char('j')),
1178 Outcome::Commit(EngineCmd::ApplyOpG {
1179 op: OperatorKind::Delete,
1180 ch: 'j',
1181 total_count: 1,
1182 })
1183 );
1184 }
1185
1186 #[test]
1187 fn op_2d_3g_g_total_count_6() {
1188 let state = PendingState::AfterOp {
1191 op: OperatorKind::Delete,
1192 count1: 2,
1193 inner_count: 3,
1194 };
1195 let Outcome::Wait(op_g_state) = step(state, Key::Char('g')) else {
1196 panic!("expected Wait(OpG)");
1197 };
1198 assert_eq!(
1199 op_g_state,
1200 PendingState::OpG {
1201 op: OperatorKind::Delete,
1202 total_count: 6,
1203 }
1204 );
1205 assert_eq!(
1206 step(op_g_state, Key::Char('g')),
1207 Outcome::Commit(EngineCmd::ApplyOpG {
1208 op: OperatorKind::Delete,
1209 ch: 'g',
1210 total_count: 6,
1211 })
1212 );
1213 }
1214
1215 #[test]
1216 fn op_d_g_then_esc_cancels() {
1217 let state = op_g(OperatorKind::Delete, 1);
1219 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
1220 }
1221
1222 #[test]
1223 fn op_d_g_then_enter_cancels() {
1224 let state = op_g(OperatorKind::Delete, 1);
1226 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
1227 }
1228
1229 #[test]
1230 fn op_c_then_g_then_g_commits_change_op_g() {
1231 let state = op_g(OperatorKind::Change, 1);
1233 assert_eq!(
1234 step(state, Key::Char('g')),
1235 Outcome::Commit(EngineCmd::ApplyOpG {
1236 op: OperatorKind::Change,
1237 ch: 'g',
1238 total_count: 1,
1239 })
1240 );
1241 }
1242
1243 #[test]
1246 fn select_register_a_commits() {
1247 let state = PendingState::SelectRegister;
1249 assert_eq!(
1250 step(state, Key::Char('a')),
1251 Outcome::Commit(EngineCmd::SetPendingRegister { reg: 'a' })
1252 );
1253 }
1254
1255 #[test]
1256 fn select_register_plus_commits() {
1257 let state = PendingState::SelectRegister;
1259 assert_eq!(
1260 step(state, Key::Char('+')),
1261 Outcome::Commit(EngineCmd::SetPendingRegister { reg: '+' })
1262 );
1263 }
1264
1265 #[test]
1266 fn select_register_underscore_commits() {
1267 let state = PendingState::SelectRegister;
1269 assert_eq!(
1270 step(state, Key::Char('_')),
1271 Outcome::Commit(EngineCmd::SetPendingRegister { reg: '_' })
1272 );
1273 }
1274
1275 #[test]
1276 fn select_register_esc_cancels() {
1277 let state = PendingState::SelectRegister;
1278 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
1279 }
1280
1281 #[test]
1282 fn select_register_enter_cancels() {
1283 let state = PendingState::SelectRegister;
1285 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
1286 }
1287
1288 #[test]
1291 fn set_mark_a_commits() {
1292 let state = PendingState::SetMark;
1294 assert_eq!(
1295 step(state, Key::Char('a')),
1296 Outcome::Commit(EngineCmd::SetMark { ch: 'a' })
1297 );
1298 }
1299
1300 #[test]
1301 fn set_mark_esc_cancels() {
1302 let state = PendingState::SetMark;
1303 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
1304 }
1305
1306 #[test]
1307 fn set_mark_enter_cancels() {
1308 let state = PendingState::SetMark;
1310 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
1311 }
1312
1313 #[test]
1316 fn goto_mark_line_a_commits() {
1317 let state = PendingState::GotoMarkLine;
1319 assert_eq!(
1320 step(state, Key::Char('a')),
1321 Outcome::Commit(EngineCmd::GotoMarkLine { ch: 'a' })
1322 );
1323 }
1324
1325 #[test]
1326 fn goto_mark_line_esc_cancels() {
1327 let state = PendingState::GotoMarkLine;
1328 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
1329 }
1330
1331 #[test]
1332 fn goto_mark_line_enter_cancels() {
1333 let state = PendingState::GotoMarkLine;
1335 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
1336 }
1337
1338 #[test]
1341 fn goto_mark_char_a_commits() {
1342 let state = PendingState::GotoMarkChar;
1344 assert_eq!(
1345 step(state, Key::Char('a')),
1346 Outcome::Commit(EngineCmd::GotoMarkChar { ch: 'a' })
1347 );
1348 }
1349
1350 #[test]
1351 fn goto_mark_char_esc_cancels() {
1352 let state = PendingState::GotoMarkChar;
1353 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
1354 }
1355
1356 #[test]
1357 fn goto_mark_char_enter_cancels() {
1358 let state = PendingState::GotoMarkChar;
1360 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
1361 }
1362}