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