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 }
74
75#[derive(Debug, Clone, PartialEq, Eq)]
77pub enum Outcome {
78 Wait(PendingState),
80 Commit(crate::cmd::EngineCmd),
82 Cancel,
84 Forward,
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub enum Key {
93 Char(char),
94 Esc,
95 Enter,
96 Backspace,
97 Tab,
98 }
100
101pub fn step(state: PendingState, key: Key) -> Outcome {
102 match state {
103 PendingState::Replace { count } => match key {
104 Key::Esc => Outcome::Cancel,
105 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::ReplaceChar { ch, count }),
106 Key::Enter => Outcome::Commit(crate::cmd::EngineCmd::ReplaceChar { ch: '\n', count }),
107 _ => Outcome::Cancel,
108 },
109 PendingState::Find {
110 count,
111 forward,
112 till,
113 } => match key {
114 Key::Esc => Outcome::Cancel,
115 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::FindChar {
116 ch,
117 forward,
118 till,
119 count,
120 }),
121 _ => Outcome::Cancel,
123 },
124 PendingState::AfterG { count } => match key {
125 Key::Esc => Outcome::Cancel,
126 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::AfterGChord { ch, count }),
127 _ => Outcome::Cancel,
129 },
130 PendingState::AfterZ { count } => match key {
131 Key::Esc => Outcome::Cancel,
132 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::AfterZChord { ch, count }),
133 _ => Outcome::Cancel,
135 },
136 PendingState::AfterOp {
137 op,
138 count1,
139 inner_count,
140 } => match key {
141 Key::Esc => Outcome::Cancel,
142 Key::Char(d @ '0'..='9') => {
143 if d == '0' && inner_count == 0 {
145 let total = count1.max(1);
147 Outcome::Commit(crate::cmd::EngineCmd::ApplyOpMotion {
148 op,
149 motion_key: '0',
150 total_count: total,
151 })
152 } else {
153 let new_inner = inner_count
154 .saturating_mul(10)
155 .saturating_add(d as usize - '0' as usize);
156 Outcome::Wait(PendingState::AfterOp {
157 op,
158 count1,
159 inner_count: new_inner,
160 })
161 }
162 }
163 Key::Char(ch) => {
164 let total = count1.max(1) * inner_count.max(1);
165 if ch == op.double_char() {
167 Outcome::Commit(crate::cmd::EngineCmd::ApplyOpDouble {
168 op,
169 total_count: total,
170 })
171 } else if ch == 'i' {
175 Outcome::Wait(PendingState::OpTextObj {
176 op,
177 total_count: count1.max(1) * inner_count.max(1),
178 inner: true,
179 })
180 } else if ch == 'a' {
181 Outcome::Wait(PendingState::OpTextObj {
182 op,
183 total_count: count1.max(1) * inner_count.max(1),
184 inner: false,
185 })
186 } else if ch == 'g' {
188 Outcome::Commit(crate::cmd::EngineCmd::EnterOpG { op, count1 })
189 } else if ch == 'f' {
193 Outcome::Wait(PendingState::OpFind {
194 op,
195 total_count: count1.max(1) * inner_count.max(1),
196 forward: true,
197 till: false,
198 })
199 } else if ch == 'F' {
200 Outcome::Wait(PendingState::OpFind {
201 op,
202 total_count: count1.max(1) * inner_count.max(1),
203 forward: false,
204 till: false,
205 })
206 } else if ch == 't' {
207 Outcome::Wait(PendingState::OpFind {
208 op,
209 total_count: count1.max(1) * inner_count.max(1),
210 forward: true,
211 till: true,
212 })
213 } else if ch == 'T' {
214 Outcome::Wait(PendingState::OpFind {
215 op,
216 total_count: count1.max(1) * inner_count.max(1),
217 forward: false,
218 till: true,
219 })
220 } else {
221 Outcome::Commit(crate::cmd::EngineCmd::ApplyOpMotion {
224 op,
225 motion_key: ch,
226 total_count: total,
227 })
228 }
229 }
230 _ => Outcome::Cancel,
232 },
233 PendingState::OpFind {
234 op,
235 total_count,
236 forward,
237 till,
238 } => match key {
239 Key::Esc => Outcome::Cancel,
240 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::ApplyOpFind {
241 op,
242 ch,
243 forward,
244 till,
245 total_count,
246 }),
247 _ => Outcome::Cancel,
249 },
250 PendingState::OpTextObj {
251 op,
252 total_count,
253 inner,
254 } => match key {
255 Key::Esc => Outcome::Cancel,
256 Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::ApplyOpTextObj {
257 op,
258 ch,
259 inner,
260 total_count,
261 }),
262 _ => Outcome::Cancel,
264 },
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271 use crate::cmd::EngineCmd;
272 use crate::operator::OperatorKind;
273
274 #[test]
277 fn after_g_gg_commits() {
278 let state = PendingState::AfterG { count: 1 };
279 assert_eq!(
280 step(state, Key::Char('g')),
281 Outcome::Commit(EngineCmd::AfterGChord { ch: 'g', count: 1 })
282 );
283 }
284
285 #[test]
286 fn after_g_gv_commits() {
287 let state = PendingState::AfterG { count: 1 };
288 assert_eq!(
289 step(state, Key::Char('v')),
290 Outcome::Commit(EngineCmd::AfterGChord { ch: 'v', count: 1 })
291 );
292 }
293
294 #[test]
295 fn after_g_gu_operator_commits() {
296 let state = PendingState::AfterG { count: 1 };
298 assert_eq!(
299 step(state, Key::Char('U')),
300 Outcome::Commit(EngineCmd::AfterGChord { ch: 'U', count: 1 })
301 );
302 }
303
304 #[test]
305 fn after_g_gi_commits() {
306 let state = PendingState::AfterG { count: 1 };
307 assert_eq!(
308 step(state, Key::Char('i')),
309 Outcome::Commit(EngineCmd::AfterGChord { ch: 'i', count: 1 })
310 );
311 }
312
313 #[test]
314 fn after_g_esc_cancels() {
315 let state = PendingState::AfterG { count: 1 };
316 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
317 }
318
319 #[test]
320 fn after_g_count_carry_through() {
321 let state = PendingState::AfterG { count: 5 };
323 assert_eq!(
324 step(state, Key::Char('g')),
325 Outcome::Commit(EngineCmd::AfterGChord { ch: 'g', count: 5 })
326 );
327 }
328
329 #[test]
330 fn after_g_non_char_cancels() {
331 let state = PendingState::AfterG { count: 1 };
333 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
334 }
335
336 #[test]
339 fn after_z_zz_commits() {
340 let state = PendingState::AfterZ { count: 1 };
341 assert_eq!(
342 step(state, Key::Char('z')),
343 Outcome::Commit(EngineCmd::AfterZChord { ch: 'z', count: 1 })
344 );
345 }
346
347 #[test]
348 fn after_z_zf_commits() {
349 let state = PendingState::AfterZ { count: 1 };
350 assert_eq!(
351 step(state, Key::Char('f')),
352 Outcome::Commit(EngineCmd::AfterZChord { ch: 'f', count: 1 })
353 );
354 }
355
356 #[test]
357 fn after_z_esc_cancels() {
358 let state = PendingState::AfterZ { count: 1 };
359 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
360 }
361
362 #[test]
363 fn after_z_count_carry_through() {
364 let state = PendingState::AfterZ { count: 3 };
366 assert_eq!(
367 step(state, Key::Char('z')),
368 Outcome::Commit(EngineCmd::AfterZChord { ch: 'z', count: 3 })
369 );
370 }
371
372 #[test]
373 fn after_z_non_char_cancels() {
374 let state = PendingState::AfterZ { count: 1 };
376 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
377 }
378
379 fn after_op(op: OperatorKind, count1: usize) -> PendingState {
382 PendingState::AfterOp {
383 op,
384 count1,
385 inner_count: 0,
386 }
387 }
388
389 #[test]
390 fn op_d_then_w_commits_motion() {
391 let state = after_op(OperatorKind::Delete, 1);
392 assert_eq!(
393 step(state, Key::Char('w')),
394 Outcome::Commit(EngineCmd::ApplyOpMotion {
395 op: OperatorKind::Delete,
396 motion_key: 'w',
397 total_count: 1,
398 })
399 );
400 }
401
402 #[test]
403 fn op_d_then_d_commits_double() {
404 let state = after_op(OperatorKind::Delete, 1);
405 assert_eq!(
406 step(state, Key::Char('d')),
407 Outcome::Commit(EngineCmd::ApplyOpDouble {
408 op: OperatorKind::Delete,
409 total_count: 1,
410 })
411 );
412 }
413
414 #[test]
415 fn op_d_inner_count_d3w_commits_motion_with_count_3() {
416 let state = after_op(OperatorKind::Delete, 1);
418 let Outcome::Wait(state2) = step(state, Key::Char('3')) else {
420 panic!("expected Wait");
421 };
422 assert_eq!(
423 state2,
424 PendingState::AfterOp {
425 op: OperatorKind::Delete,
426 count1: 1,
427 inner_count: 3
428 }
429 );
430 assert_eq!(
432 step(state2, Key::Char('w')),
433 Outcome::Commit(EngineCmd::ApplyOpMotion {
434 op: OperatorKind::Delete,
435 motion_key: 'w',
436 total_count: 3,
437 })
438 );
439 }
440
441 #[test]
442 fn op_2d_d_commits_double_with_count_2() {
443 let state = after_op(OperatorKind::Delete, 2);
445 assert_eq!(
446 step(state, Key::Char('d')),
447 Outcome::Commit(EngineCmd::ApplyOpDouble {
448 op: OperatorKind::Delete,
449 total_count: 2,
450 })
451 );
452 }
453
454 #[test]
455 fn op_2d_3w_commits_motion_with_total_6() {
456 let state = after_op(OperatorKind::Delete, 2);
458 let Outcome::Wait(state2) = step(state, Key::Char('3')) else {
459 panic!("expected Wait");
460 };
461 assert_eq!(
462 step(state2, Key::Char('w')),
463 Outcome::Commit(EngineCmd::ApplyOpMotion {
464 op: OperatorKind::Delete,
465 motion_key: 'w',
466 total_count: 6,
467 })
468 );
469 }
470
471 #[test]
472 fn op_d_then_i_transitions_to_op_text_obj_inner() {
473 let state = after_op(OperatorKind::Delete, 1);
475 assert_eq!(
476 step(state, Key::Char('i')),
477 Outcome::Wait(PendingState::OpTextObj {
478 op: OperatorKind::Delete,
479 total_count: 1,
480 inner: true,
481 })
482 );
483 }
484
485 #[test]
486 fn op_d_then_a_transitions_to_op_text_obj_around() {
487 let state = after_op(OperatorKind::Delete, 1);
489 assert_eq!(
490 step(state, Key::Char('a')),
491 Outcome::Wait(PendingState::OpTextObj {
492 op: OperatorKind::Delete,
493 total_count: 1,
494 inner: false,
495 })
496 );
497 }
498
499 #[test]
500 fn op_d_then_g_emits_enter_op_g() {
501 let state = after_op(OperatorKind::Delete, 1);
502 assert_eq!(
503 step(state, Key::Char('g')),
504 Outcome::Commit(EngineCmd::EnterOpG {
505 op: OperatorKind::Delete,
506 count1: 1,
507 })
508 );
509 }
510
511 #[test]
512 fn op_d_then_f_transitions_to_op_find_forward_not_till() {
513 let state = after_op(OperatorKind::Delete, 1);
515 assert_eq!(
516 step(state, Key::Char('f')),
517 Outcome::Wait(PendingState::OpFind {
518 op: OperatorKind::Delete,
519 total_count: 1,
520 forward: true,
521 till: false,
522 })
523 );
524 }
525
526 #[test]
527 fn op_d_then_cap_f_transitions_to_op_find_backward_not_till() {
528 let state = after_op(OperatorKind::Delete, 1);
530 assert_eq!(
531 step(state, Key::Char('F')),
532 Outcome::Wait(PendingState::OpFind {
533 op: OperatorKind::Delete,
534 total_count: 1,
535 forward: false,
536 till: false,
537 })
538 );
539 }
540
541 #[test]
542 fn op_d_then_t_transitions_to_op_find_forward_till() {
543 let state = after_op(OperatorKind::Delete, 1);
545 assert_eq!(
546 step(state, Key::Char('t')),
547 Outcome::Wait(PendingState::OpFind {
548 op: OperatorKind::Delete,
549 total_count: 1,
550 forward: true,
551 till: true,
552 })
553 );
554 }
555
556 #[test]
557 fn op_d_then_cap_t_transitions_to_op_find_backward_till() {
558 let state = after_op(OperatorKind::Delete, 1);
560 assert_eq!(
561 step(state, Key::Char('T')),
562 Outcome::Wait(PendingState::OpFind {
563 op: OperatorKind::Delete,
564 total_count: 1,
565 forward: false,
566 till: true,
567 })
568 );
569 }
570
571 fn op_find(op: OperatorKind, total_count: usize, forward: bool, till: bool) -> PendingState {
574 PendingState::OpFind {
575 op,
576 total_count,
577 forward,
578 till,
579 }
580 }
581
582 #[test]
583 fn op_d_then_f_then_x_commits_apply_op_find() {
584 let state = op_find(OperatorKind::Delete, 1, true, false);
586 assert_eq!(
587 step(state, Key::Char('x')),
588 Outcome::Commit(EngineCmd::ApplyOpFind {
589 op: OperatorKind::Delete,
590 ch: 'x',
591 forward: true,
592 till: false,
593 total_count: 1,
594 })
595 );
596 }
597
598 #[test]
599 fn op_d_then_cap_f_then_x_commits_apply_op_find_backward() {
600 let state = op_find(OperatorKind::Delete, 1, false, false);
602 assert_eq!(
603 step(state, Key::Char('x')),
604 Outcome::Commit(EngineCmd::ApplyOpFind {
605 op: OperatorKind::Delete,
606 ch: 'x',
607 forward: false,
608 till: false,
609 total_count: 1,
610 })
611 );
612 }
613
614 #[test]
615 fn op_d_then_t_then_x_commits_apply_op_find_till() {
616 let state = op_find(OperatorKind::Delete, 1, true, true);
618 assert_eq!(
619 step(state, Key::Char('x')),
620 Outcome::Commit(EngineCmd::ApplyOpFind {
621 op: OperatorKind::Delete,
622 ch: 'x',
623 forward: true,
624 till: true,
625 total_count: 1,
626 })
627 );
628 }
629
630 #[test]
631 fn op_d_then_cap_t_then_x_commits_apply_op_find_backward_till() {
632 let state = op_find(OperatorKind::Delete, 1, false, true);
634 assert_eq!(
635 step(state, Key::Char('x')),
636 Outcome::Commit(EngineCmd::ApplyOpFind {
637 op: OperatorKind::Delete,
638 ch: 'x',
639 forward: false,
640 till: true,
641 total_count: 1,
642 })
643 );
644 }
645
646 #[test]
647 fn op_2d_3f_x_commits_total_count_6() {
648 let state = PendingState::AfterOp {
651 op: OperatorKind::Delete,
652 count1: 2,
653 inner_count: 3,
654 };
655 let Outcome::Wait(op_find_state) = step(state, Key::Char('f')) else {
656 panic!("expected Wait(OpFind)");
657 };
658 assert_eq!(
659 op_find_state,
660 PendingState::OpFind {
661 op: OperatorKind::Delete,
662 total_count: 6,
663 forward: true,
664 till: false,
665 }
666 );
667 assert_eq!(
668 step(op_find_state, Key::Char('x')),
669 Outcome::Commit(EngineCmd::ApplyOpFind {
670 op: OperatorKind::Delete,
671 ch: 'x',
672 forward: true,
673 till: false,
674 total_count: 6,
675 })
676 );
677 }
678
679 #[test]
680 fn op_d_f_then_esc_cancels() {
681 let state = op_find(OperatorKind::Delete, 1, true, false);
683 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
684 }
685
686 #[test]
687 fn op_d_f_then_enter_cancels() {
688 let state = op_find(OperatorKind::Delete, 1, true, false);
690 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
691 }
692
693 #[test]
694 fn op_d_then_esc_cancels() {
695 let state = after_op(OperatorKind::Delete, 1);
696 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
697 }
698
699 #[test]
700 fn op_d_non_char_cancels() {
701 let state = after_op(OperatorKind::Delete, 1);
702 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
703 }
704
705 fn op_text_obj(op: OperatorKind, total_count: usize, inner: bool) -> PendingState {
708 PendingState::OpTextObj {
709 op,
710 total_count,
711 inner,
712 }
713 }
714
715 #[test]
716 fn op_d_then_i_then_w_commits_apply_op_text_obj_inner() {
717 let state = op_text_obj(OperatorKind::Delete, 1, true);
719 assert_eq!(
720 step(state, Key::Char('w')),
721 Outcome::Commit(EngineCmd::ApplyOpTextObj {
722 op: OperatorKind::Delete,
723 ch: 'w',
724 inner: true,
725 total_count: 1,
726 })
727 );
728 }
729
730 #[test]
731 fn op_d_then_a_then_w_commits_apply_op_text_obj_around() {
732 let state = op_text_obj(OperatorKind::Delete, 1, false);
734 assert_eq!(
735 step(state, Key::Char('w')),
736 Outcome::Commit(EngineCmd::ApplyOpTextObj {
737 op: OperatorKind::Delete,
738 ch: 'w',
739 inner: false,
740 total_count: 1,
741 })
742 );
743 }
744
745 #[test]
746 fn op_d_then_i_then_quote_commits_with_quote_char() {
747 let state = op_text_obj(OperatorKind::Delete, 1, true);
749 assert_eq!(
750 step(state, Key::Char('"')),
751 Outcome::Commit(EngineCmd::ApplyOpTextObj {
752 op: OperatorKind::Delete,
753 ch: '"',
754 inner: true,
755 total_count: 1,
756 })
757 );
758 }
759
760 #[test]
761 fn op_d_then_i_then_paren_commits_with_paren() {
762 let state = op_text_obj(OperatorKind::Delete, 1, true);
764 assert_eq!(
765 step(state, Key::Char('(')),
766 Outcome::Commit(EngineCmd::ApplyOpTextObj {
767 op: OperatorKind::Delete,
768 ch: '(',
769 inner: true,
770 total_count: 1,
771 })
772 );
773 }
774
775 #[test]
776 fn op_c_then_i_then_p_commits_change_paragraph_inner() {
777 let state = op_text_obj(OperatorKind::Change, 1, true);
779 assert_eq!(
780 step(state, Key::Char('p')),
781 Outcome::Commit(EngineCmd::ApplyOpTextObj {
782 op: OperatorKind::Change,
783 ch: 'p',
784 inner: true,
785 total_count: 1,
786 })
787 );
788 }
789
790 #[test]
791 fn op_d_i_then_esc_cancels() {
792 let state = op_text_obj(OperatorKind::Delete, 1, true);
794 assert_eq!(step(state, Key::Esc), Outcome::Cancel);
795 }
796
797 #[test]
798 fn op_d_i_then_enter_cancels() {
799 let state = op_text_obj(OperatorKind::Delete, 1, true);
801 assert_eq!(step(state, Key::Enter), Outcome::Cancel);
802 }
803
804 #[test]
805 fn op_2d_i_w_total_count_2_preserved() {
806 let state = PendingState::AfterOp {
809 op: OperatorKind::Delete,
810 count1: 2,
811 inner_count: 0,
812 };
813 let Outcome::Wait(obj_state) = step(state, Key::Char('i')) else {
814 panic!("expected Wait(OpTextObj)");
815 };
816 assert_eq!(
817 obj_state,
818 PendingState::OpTextObj {
819 op: OperatorKind::Delete,
820 total_count: 2,
821 inner: true,
822 }
823 );
824 assert_eq!(
825 step(obj_state, Key::Char('w')),
826 Outcome::Commit(EngineCmd::ApplyOpTextObj {
827 op: OperatorKind::Delete,
828 ch: 'w',
829 inner: true,
830 total_count: 2,
831 })
832 );
833 }
834
835 #[test]
836 fn op_d_bare_zero_is_line_start_motion() {
837 let state = after_op(OperatorKind::Delete, 1);
839 assert_eq!(
840 step(state, Key::Char('0')),
841 Outcome::Commit(EngineCmd::ApplyOpMotion {
842 op: OperatorKind::Delete,
843 motion_key: '0',
844 total_count: 1,
845 })
846 );
847 }
848
849 #[test]
850 fn op_d_zero_accumulates_when_inner_count_nonzero() {
851 let state = after_op(OperatorKind::Delete, 1);
853 let Outcome::Wait(s2) = step(state, Key::Char('1')) else {
854 panic!("expected Wait");
855 };
856 let Outcome::Wait(s3) = step(s2, Key::Char('0')) else {
857 panic!("expected Wait");
858 };
859 assert_eq!(
860 s3,
861 PendingState::AfterOp {
862 op: OperatorKind::Delete,
863 count1: 1,
864 inner_count: 10,
865 }
866 );
867 assert_eq!(
868 step(s3, Key::Char('w')),
869 Outcome::Commit(EngineCmd::ApplyOpMotion {
870 op: OperatorKind::Delete,
871 motion_key: 'w',
872 total_count: 10,
873 })
874 );
875 }
876
877 #[test]
880 fn op_yank_doubled() {
881 let state = after_op(OperatorKind::Yank, 1);
882 assert_eq!(
883 step(state, Key::Char('y')),
884 Outcome::Commit(EngineCmd::ApplyOpDouble {
885 op: OperatorKind::Yank,
886 total_count: 1,
887 })
888 );
889 }
890
891 #[test]
892 fn op_change_doubled() {
893 let state = after_op(OperatorKind::Change, 1);
894 assert_eq!(
895 step(state, Key::Char('c')),
896 Outcome::Commit(EngineCmd::ApplyOpDouble {
897 op: OperatorKind::Change,
898 total_count: 1,
899 })
900 );
901 }
902
903 #[test]
904 fn op_indent_doubled() {
905 let state = after_op(OperatorKind::Indent, 1);
906 assert_eq!(
907 step(state, Key::Char('>')),
908 Outcome::Commit(EngineCmd::ApplyOpDouble {
909 op: OperatorKind::Indent,
910 total_count: 1,
911 })
912 );
913 }
914
915 #[test]
916 fn op_outdent_doubled() {
917 let state = after_op(OperatorKind::Outdent, 1);
918 assert_eq!(
919 step(state, Key::Char('<')),
920 Outcome::Commit(EngineCmd::ApplyOpDouble {
921 op: OperatorKind::Outdent,
922 total_count: 1,
923 })
924 );
925 }
926
927 #[test]
928 fn op_yank_motion() {
929 let state = after_op(OperatorKind::Yank, 1);
930 assert_eq!(
931 step(state, Key::Char('$')),
932 Outcome::Commit(EngineCmd::ApplyOpMotion {
933 op: OperatorKind::Yank,
934 motion_key: '$',
935 total_count: 1,
936 })
937 );
938 }
939
940 #[test]
941 fn op_change_motion() {
942 let state = after_op(OperatorKind::Change, 1);
943 assert_eq!(
944 step(state, Key::Char('w')),
945 Outcome::Commit(EngineCmd::ApplyOpMotion {
946 op: OperatorKind::Change,
947 motion_key: 'w',
948 total_count: 1,
949 })
950 );
951 }
952
953 #[test]
954 fn op_indent_motion() {
955 let state = after_op(OperatorKind::Indent, 1);
956 assert_eq!(
957 step(state, Key::Char('j')),
958 Outcome::Commit(EngineCmd::ApplyOpMotion {
959 op: OperatorKind::Indent,
960 motion_key: 'j',
961 total_count: 1,
962 })
963 );
964 }
965
966 #[test]
967 fn op_outdent_motion() {
968 let state = after_op(OperatorKind::Outdent, 1);
969 assert_eq!(
970 step(state, Key::Char('k')),
971 Outcome::Commit(EngineCmd::ApplyOpMotion {
972 op: OperatorKind::Outdent,
973 motion_key: 'k',
974 total_count: 1,
975 })
976 );
977 }
978}