1use crate::vim::{op_is_change, parse_motion};
6use hjkl_engine::{
7 FsmMode, Host, Input, Key, LastChange, Motion, Operator, Pending, ScrollDir, VimMode,
8};
9
10use hjkl_engine::Pending::{OpSneakFirst, OpSneakSecond, SneakFirst, SneakSecond};
12
13use crate::VimEditorExt;
14
15pub fn step_normal<H: Host>(
22 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
23 input: Input,
24) -> bool {
25 if let Key::Char(d @ '0'..='9') = input.key
27 && !input.ctrl
28 && !input.alt
29 && !matches!(
30 ed.pending(),
31 Pending::Replace
32 | Pending::Find { .. }
33 | Pending::OpFind { .. }
34 | Pending::VisualTextObj { .. }
35 | Pending::SelectRegister
39 | Pending::SetMark
40 | Pending::GotoMarkLine
41 | Pending::GotoMarkChar
42 | Pending::RecordMacroTarget
43 | Pending::PlayMacroTarget { .. }
44 | SneakFirst { .. }
45 | SneakSecond { .. }
46 | OpSneakFirst { .. }
47 | OpSneakSecond { .. }
48 )
49 && (d != '0' || ed.count() > 0)
50 {
51 ed.accumulate_count_digit(d as usize - '0' as usize);
52 return true;
53 }
54
55 match ed.take_pending() {
57 Pending::Replace => return handle_replace(ed, input),
58 Pending::Find { forward, till } => return handle_find_target(ed, input, forward, till),
59 Pending::OpFind {
60 op,
61 count1,
62 forward,
63 till,
64 } => return handle_op_find_target(ed, input, op, count1, forward, till),
65 Pending::G => return handle_after_g(ed, input),
66 Pending::OpG { op, count1 } => return handle_op_after_g(ed, input, op, count1),
67 Pending::Op { op, count1 } => return handle_after_op(ed, input, op, count1),
68 Pending::OpTextObj { op, count1, inner } => {
69 return handle_text_object(ed, input, op, count1, inner);
70 }
71 Pending::VisualTextObj { inner } => {
72 return handle_visual_text_obj(ed, input, inner);
73 }
74 Pending::Z => return handle_after_z(ed, input),
75 Pending::SetMark => return handle_set_mark(ed, input),
76 Pending::GotoMarkLine => return handle_goto_mark(ed, input, true),
77 Pending::GotoMarkChar => return handle_goto_mark(ed, input, false),
78 Pending::SelectRegister => return handle_select_register(ed, input),
79 Pending::RecordMacroTarget => return handle_record_macro_target(ed, input),
80 Pending::PlayMacroTarget { count } => return handle_play_macro_target(ed, input, count),
81 Pending::SquareBracketOpen => {
82 let cnt = ed.take_count();
83 return handle_after_square_bracket_open(ed, input, cnt);
84 }
85 Pending::SquareBracketClose => {
86 let cnt = ed.take_count();
87 return handle_after_square_bracket_close(ed, input, cnt);
88 }
89 Pending::OpSquareBracketOpen { op, count1 } => {
90 return handle_op_after_square_bracket_open(ed, input, op, count1);
91 }
92 Pending::OpSquareBracketClose { op, count1 } => {
93 return handle_op_after_square_bracket_close(ed, input, op, count1);
94 }
95 SneakFirst { forward, count } => {
96 return handle_sneak_first(ed, input, forward, count);
97 }
98 SneakSecond { c1, forward, count } => {
99 return handle_sneak_second(ed, input, c1, forward, count);
100 }
101 OpSneakFirst {
102 op,
103 count1,
104 forward,
105 } => {
106 return handle_op_sneak_first(ed, input, op, count1, forward);
107 }
108 OpSneakSecond {
109 op,
110 count1,
111 c1,
112 forward,
113 } => {
114 return handle_op_sneak_second(ed, input, op, count1, c1, forward);
115 }
116 Pending::None => {}
117 }
118
119 let had_explicit_count = ed.count() > 0;
122 let count = ed.take_count();
123
124 match input.key {
126 Key::Esc => {
127 ed.exit_blame();
130 ed.force_normal();
131 return true;
132 }
133 Key::Char('v') if !input.ctrl && ed.fsm_mode() == FsmMode::Normal => {
134 ed.set_visual_anchor(ed.cursor());
135 ed.set_mode(VimMode::Visual);
136 if had_explicit_count && count > 1 {
145 let (row, col) = ed.cursor();
146 let line_chars = hjkl_engine::buf_helpers::buf_line_chars(ed.buffer(), row);
147 let target_col = (col + count - 1).min(line_chars);
148 ed.jump_cursor(row, target_col);
149 }
150 return true;
151 }
152 Key::Char('V') if !input.ctrl && ed.fsm_mode() == FsmMode::Normal => {
153 let (row, _) = ed.cursor();
154 ed.set_visual_line_anchor(row);
155 ed.set_mode(VimMode::VisualLine);
156 if had_explicit_count && count > 1 {
164 let total = hjkl_engine::buf_helpers::buf_row_count(ed.buffer());
165 let last_content_row = if total >= 2
166 && hjkl_engine::buf_helpers::buf_line(ed.buffer(), total - 1)
167 .is_some_and(|s| s.is_empty())
168 {
169 total - 2
170 } else {
171 total.saturating_sub(1)
172 };
173 let target_row = (row + count - 1).min(last_content_row);
174 ed.jump_cursor(target_row, 0);
175 }
176 return true;
177 }
178 Key::Char('v') if !input.ctrl && ed.fsm_mode() == FsmMode::VisualLine => {
179 ed.set_visual_anchor(ed.cursor());
180 ed.set_mode(VimMode::Visual);
181 return true;
182 }
183 Key::Char('V') if !input.ctrl && ed.fsm_mode() == FsmMode::Visual => {
184 let (row, _) = ed.cursor();
185 ed.set_visual_line_anchor(row);
186 ed.set_mode(VimMode::VisualLine);
187 return true;
188 }
189 Key::Char('v') if input.ctrl && ed.fsm_mode() == FsmMode::Normal => {
190 let cur = ed.cursor();
191 ed.set_block_anchor(cur);
192 ed.set_block_vcol(cur.1);
193 ed.set_block_to_eol(false);
194 ed.set_mode(VimMode::VisualBlock);
195 return true;
196 }
197 Key::Char('v') if input.ctrl && ed.fsm_mode() == FsmMode::VisualBlock => {
198 ed.set_mode(VimMode::Normal);
200 return true;
201 }
202 Key::Char('o') if !input.ctrl => match ed.fsm_mode() {
205 FsmMode::Visual => {
206 let cur = ed.cursor();
207 let anchor = ed.visual_anchor();
208 ed.set_visual_anchor(cur);
209 ed.jump_cursor(anchor.0, anchor.1);
210 return true;
211 }
212 FsmMode::VisualLine => {
213 let cur_row = ed.cursor().0;
214 let anchor_row = ed.visual_line_anchor();
215 ed.set_visual_line_anchor(cur_row);
216 ed.jump_cursor(anchor_row, 0);
217 return true;
218 }
219 FsmMode::VisualBlock => {
220 let cur = ed.cursor();
221 let anchor = ed.block_anchor();
222 ed.set_block_anchor(cur);
223 ed.set_block_vcol(anchor.1);
224 ed.jump_cursor(anchor.0, anchor.1);
225 return true;
226 }
227 _ => {}
228 },
229 _ => {}
230 }
231
232 if ed.is_visual() && !input.ctrl && matches!(input.key, Key::Char('p') | Key::Char('P')) {
234 ed.visual_paste(matches!(input.key, Key::Char('P')));
235 return true;
236 }
237
238 if ed.is_visual() && !input.ctrl && input.key == Key::Char('J') {
240 ed.visual_join(true);
241 return true;
242 }
243
244 if ed.is_visual()
248 && let Some(op) = visual_operator(&input)
249 {
250 ed.apply_visual_operator(op, count.max(1));
251 return true;
252 }
253
254 if matches!(ed.fsm_mode(), FsmMode::Visual | FsmMode::VisualLine)
263 && !input.ctrl
264 && input.key == Key::Char('r')
265 {
266 ed.set_pending(Pending::Replace);
267 return true;
268 }
269
270 if ed.fsm_mode() == FsmMode::VisualBlock && !input.ctrl {
274 match input.key {
275 Key::Char('r') => {
276 ed.set_pending(Pending::Replace);
277 return true;
278 }
279 Key::Char('I') => {
280 let (top, bot, left, _right) = ed.visual_block_bounds();
281 ed.visual_block_insert_at_left(top, bot, left, count.max(1));
285 return true;
286 }
287 Key::Char('A') => {
288 let (top, bot, left, right) = ed.visual_block_bounds();
301 let col = if ed.block_to_eol() {
302 ed.line_char_count(top)
303 } else {
304 right + 1
305 };
306 ed.visual_block_append_at_right(top, bot, col, left, count.max(1));
310 return true;
311 }
312 Key::Char('D') => {
321 ed.set_block_to_eol(true);
322 ed.apply_visual_operator(Operator::Delete, 1);
323 return true;
324 }
325 Key::Char('C') => {
326 ed.set_block_to_eol(true);
327 ed.apply_visual_operator(Operator::Change, 1);
328 return true;
329 }
330 Key::Char('X') => {
331 ed.apply_visual_operator(Operator::Delete, 1);
332 return true;
333 }
334 Key::Char('Y') => {
335 ed.apply_visual_operator(Operator::Yank, 1);
336 return true;
337 }
338 Key::Char('S') | Key::Char('R') => {
342 let (top, bot, _left, _right) = ed.visual_block_bounds();
343 ed.set_visual_line_anchor(top);
344 ed.jump_cursor(bot, 0);
345 ed.set_mode(VimMode::VisualLine);
346 ed.apply_visual_operator(Operator::Change, 1);
347 return true;
348 }
349 _ => {}
350 }
351 }
352
353 if matches!(
355 ed.fsm_mode(),
356 FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
357 ) && !input.ctrl
358 && matches!(input.key, Key::Char('i') | Key::Char('a'))
359 {
360 let inner = matches!(input.key, Key::Char('i'));
361 ed.set_pending(Pending::VisualTextObj { inner });
362 return true;
363 }
364
365 if input.ctrl
370 && let Key::Char(c) = input.key
371 {
372 match c {
373 'd' => {
374 ed.scroll_half_page(ScrollDir::Down, count);
375 return true;
376 }
377 'u' => {
378 ed.scroll_half_page(ScrollDir::Up, count);
379 return true;
380 }
381 'f' => {
382 ed.scroll_full_page(ScrollDir::Down, count);
383 return true;
384 }
385 'b' => {
386 ed.scroll_full_page(ScrollDir::Up, count);
387 return true;
388 }
389 'e' if ed.fsm_mode() == FsmMode::Normal => {
390 ed.scroll_line(ScrollDir::Down, count);
391 return true;
392 }
393 'y' if ed.fsm_mode() == FsmMode::Normal => {
394 ed.scroll_line(ScrollDir::Up, count);
395 return true;
396 }
397 'r' => {
398 ed.redo_by_steps(count.max(1));
400 return true;
401 }
402 'a' if ed.fsm_mode() == FsmMode::Normal => {
403 ed.adjust_number(count.max(1) as i64);
404 return true;
405 }
406 'a' if ed.is_visual() => {
409 ed.adjust_number_visual(count.max(1) as i64, false);
410 return true;
411 }
412 'x' if ed.is_visual() => {
413 ed.adjust_number_visual(-(count.max(1) as i64), false);
414 return true;
415 }
416 'x' if ed.fsm_mode() == FsmMode::Normal => {
417 ed.adjust_number(-(count.max(1) as i64));
418 return true;
419 }
420 'o' if ed.fsm_mode() == FsmMode::Normal => {
421 ed.jump_back(count);
422 return true;
423 }
424 'i' if ed.fsm_mode() == FsmMode::Normal => {
425 ed.jump_forward(count);
426 return true;
427 }
428 _ => {}
429 }
430 }
431
432 if !input.ctrl && input.key == Key::Tab && ed.fsm_mode() == FsmMode::Normal {
434 ed.jump_forward(count);
435 return true;
436 }
437
438 if !input.ctrl && input.key == Key::Char('%') && had_explicit_count {
441 ed.goto_percent(count);
442 return true;
443 }
444
445 if let Some(motion) = parse_motion(&input) {
447 ed.execute_motion(motion.clone(), count);
448 if ed.fsm_mode() == FsmMode::VisualBlock {
450 ed.update_block_vcol(&motion);
451 }
452 if let Motion::Find { ch, forward, till } = motion {
453 ed.set_last_find(Some((ch, forward, till)));
454 }
455 return true;
456 }
457
458 if ed.fsm_mode() == FsmMode::Normal
464 && !input.ctrl
465 && !input.alt
466 && !input.shift
467 && input.key == Key::Char('.')
468 {
469 ed.replay_last_change(if had_explicit_count { count } else { 0 });
470 return true;
471 }
472
473 if ed.fsm_mode() == FsmMode::Normal && handle_normal_only(ed, &input, count) {
475 return true;
476 }
477
478 if ed.fsm_mode() == FsmMode::Normal
480 && let Key::Char(op_ch) = input.key
481 && !input.ctrl
482 && let Some(op) = char_to_operator(op_ch)
483 {
484 ed.set_pending(Pending::Op { op, count1: count });
485 return true;
486 }
487
488 if ed.fsm_mode() == FsmMode::Normal
490 && let Some((forward, till)) = find_entry(&input)
491 {
492 ed.set_count(count);
493 ed.set_pending(Pending::Find { forward, till });
494 return true;
495 }
496
497 if !input.ctrl
500 && input.key == Key::Char('g')
501 && matches!(
502 ed.fsm_mode(),
503 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
504 )
505 {
506 ed.set_count(count);
507 ed.set_pending(Pending::G);
508 return true;
509 }
510
511 if !input.ctrl
517 && input.key == Key::Char('z')
518 && matches!(
519 ed.fsm_mode(),
520 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
521 )
522 {
523 ed.set_count(count);
524 ed.set_pending(Pending::Z);
525 return true;
526 }
527
528 if !input.ctrl
530 && input.key == Key::Char('[')
531 && matches!(
532 ed.fsm_mode(),
533 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
534 )
535 {
536 ed.set_count(count);
537 ed.set_pending(Pending::SquareBracketOpen);
538 return true;
539 }
540
541 if !input.ctrl
543 && input.key == Key::Char(']')
544 && matches!(
545 ed.fsm_mode(),
546 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
547 )
548 {
549 ed.set_count(count);
550 ed.set_pending(Pending::SquareBracketClose);
551 return true;
552 }
553
554 if !input.ctrl
560 && matches!(
561 ed.fsm_mode(),
562 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
563 )
564 && input.key == Key::Char('`')
565 {
566 ed.set_pending(Pending::GotoMarkChar);
567 return true;
568 }
569 if !input.ctrl && ed.fsm_mode() == FsmMode::Normal {
570 match input.key {
571 Key::Char('m') => {
572 ed.set_pending(Pending::SetMark);
573 return true;
574 }
575 Key::Char('\'') => {
576 ed.set_pending(Pending::GotoMarkLine);
577 return true;
578 }
579 Key::Char('`') => {
580 ed.set_pending(Pending::GotoMarkChar);
582 return true;
583 }
584 Key::Char('"') => {
585 ed.set_pending(Pending::SelectRegister);
588 return true;
589 }
590 Key::Char('@') => {
591 ed.set_pending(Pending::PlayMacroTarget { count });
595 return true;
596 }
597 Key::Char('q') if ed.recording_macro().is_none() => {
598 ed.set_pending(Pending::RecordMacroTarget);
603 return true;
604 }
605 _ => {}
606 }
607 }
608
609 true
611}
612
613fn handle_normal_only<H: Host>(
617 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
618 input: &Input,
619 count: usize,
620) -> bool {
621 if input.ctrl {
622 return false;
623 }
624 match input.key {
625 Key::Char('i') => {
626 ed.enter_insert_i(count);
627 true
628 }
629 Key::Char('I') => {
630 ed.enter_insert_shift_i(count);
631 true
632 }
633 Key::Char('a') => {
634 ed.enter_insert_a(count);
635 true
636 }
637 Key::Char('A') => {
638 ed.enter_insert_shift_a(count);
639 true
640 }
641 Key::Char('R') => {
642 ed.enter_replace_mode(count);
643 true
644 }
645 Key::Char('o') => {
646 ed.open_line_below(count);
647 true
648 }
649 Key::Char('O') => {
650 ed.open_line_above(count);
651 true
652 }
653 Key::Char('x') => {
654 ed.delete_char_forward(count);
655 true
656 }
657 Key::Char('X') => {
658 ed.delete_char_backward(count);
659 true
660 }
661 Key::Char('~') => {
662 ed.toggle_case_at_cursor(count);
663 true
664 }
665 Key::Char('J') => {
666 ed.join_line(count);
667 true
668 }
669 Key::Char('D') => {
670 ed.delete_to_eol(count);
671 true
672 }
673 Key::Char('Y') => {
674 ed.yank_to_eol(count);
675 true
676 }
677 Key::Char('C') => {
678 ed.change_to_eol(count);
679 true
680 }
681 Key::Char('s') => {
682 if ed.settings().motion_sneak {
683 ed.set_pending(SneakFirst {
688 forward: true,
689 count,
690 });
691 } else {
692 ed.substitute_char(count);
693 }
694 true
695 }
696 Key::Char('S') => {
697 if ed.settings().motion_sneak {
698 ed.set_pending(SneakFirst {
701 forward: false,
702 count,
703 });
704 } else {
705 ed.substitute_line(count);
706 }
707 true
708 }
709 Key::Char('p') => {
710 ed.paste_after(count);
711 true
712 }
713 Key::Char('P') => {
714 ed.paste_before(count);
715 true
716 }
717 Key::Char('&') => {
718 ed.ampersand_repeat();
720 true
721 }
722 Key::Char('u') => {
723 ed.undo_by_steps(count.max(1));
726 true
727 }
728 Key::Char('U') => {
729 ed.undo_line();
733 true
734 }
735 Key::Char('r') => {
736 ed.set_count(count);
737 ed.set_pending(Pending::Replace);
738 true
739 }
740 Key::Char('/') => {
741 ed.enter_search(true);
742 true
743 }
744 Key::Char('?') => {
745 ed.enter_search(false);
746 true
747 }
748 _ => false,
749 }
750}
751
752fn handle_set_mark<H: Host>(
755 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
756 input: Input,
757) -> bool {
758 if let Key::Char(c) = input.key {
759 ed.set_mark_at_cursor(c);
760 }
761 true
762}
763
764fn handle_select_register<H: Host>(
765 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
766 input: Input,
767) -> bool {
768 if let Key::Char(c) = input.key {
769 ed.set_pending_register(c);
770 }
771 true
772}
773
774fn handle_record_macro_target<H: Host>(
775 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
776 input: Input,
777) -> bool {
778 if let Key::Char(c) = input.key
779 && (c.is_ascii_alphabetic() || c.is_ascii_digit())
780 {
781 ed.set_recording_macro(Some(c));
782 if c.is_ascii_uppercase() {
785 let lower = c.to_ascii_lowercase();
786 let text = ed
790 .with_registers(|r| r.read(lower).map(|s| s.text.clone()))
791 .unwrap_or_default();
792 ed.set_recording_keys(hjkl_engine::decode_macro(&text));
793 } else {
794 ed.set_recording_keys(vec![]);
795 }
796 }
797 true
798}
799
800fn handle_play_macro_target<H: Host>(
801 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
802 input: Input,
803 count: usize,
804) -> bool {
805 let reg = match input.key {
806 Key::Char('@') => ed.last_macro(),
807 Key::Char(c) if c.is_ascii_alphabetic() || c.is_ascii_digit() => {
808 Some(c.to_ascii_lowercase())
809 }
810 _ => None,
811 };
812 let Some(reg) = reg else {
813 return true;
814 };
815 let text = match ed.with_registers(|r| r.read(reg).cloned()) {
818 Some(slot) if !slot.text.is_empty() => slot.text,
819 _ => return true,
820 };
821 let keys = hjkl_engine::decode_macro(&text);
822 ed.set_last_macro(Some(reg));
823 const MAX_REPLAY_DEPTH: usize = 100;
829 thread_local! {
830 static REPLAY_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
831 }
832 let depth = REPLAY_DEPTH.with(std::cell::Cell::get);
833 if depth >= MAX_REPLAY_DEPTH {
834 return true;
835 }
836 REPLAY_DEPTH.with(|d| d.set(depth + 1));
837 let times = count.max(1);
838 let was_replaying = ed.is_replaying_macro_raw();
839 ed.set_replaying_macro_raw(true);
840 {
845 let _undo_group = ed.undo_group();
846 for _ in 0..times {
847 for k in keys.iter().copied() {
848 crate::dispatch_input(ed, k);
849 }
850 }
851 }
852 ed.set_replaying_macro_raw(was_replaying);
853 REPLAY_DEPTH.with(|d| d.set(depth));
854 true
855}
856
857fn handle_goto_mark<H: Host>(
858 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
859 input: Input,
860 linewise: bool,
861) -> bool {
862 let Key::Char(c) = input.key else {
863 return true;
864 };
865 if linewise {
871 let _ = ed.try_goto_mark_line(c);
872 } else {
873 let _ = ed.try_goto_mark_char(c);
874 }
875 true
876}
877
878fn handle_after_op<H: Host>(
879 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
880 input: Input,
881 op: Operator,
882 count1: usize,
883) -> bool {
884 if let Key::Char(d @ '0'..='9') = input.key
886 && !input.ctrl
887 && (d != '0' || ed.count() > 0)
888 {
889 ed.accumulate_count_digit(d as usize - '0' as usize);
890 ed.set_pending(Pending::Op { op, count1 });
891 return true;
892 }
893
894 if input.key == Key::Esc {
896 ed.reset_count();
897 return true;
898 }
899
900 let double_ch = match op {
904 Operator::Delete => Some('d'),
905 Operator::Change => Some('c'),
906 Operator::Yank => Some('y'),
907 Operator::Indent => Some('>'),
908 Operator::Outdent => Some('<'),
909 Operator::Uppercase => Some('U'),
910 Operator::Lowercase => Some('u'),
911 Operator::ToggleCase => Some('~'),
912 Operator::Fold => None,
913 Operator::Reflow => Some('q'),
916 Operator::ReflowKeepCursor => Some('w'),
918 Operator::AutoIndent => Some('='),
920 Operator::Filter => Some('!'),
922 Operator::Comment => Some('c'),
924 Operator::Rot13 => Some('?'),
926 };
927 if let Key::Char(c) = input.key
928 && !input.ctrl
929 && Some(c) == double_ch
930 {
931 let count2 = ed.take_count();
932 let total = count1.max(1).saturating_mul(count2.max(1));
933 ed.apply_op_double(op, total);
934 return true;
935 }
936
937 if let Key::Char('i') | Key::Char('a') = input.key
939 && !input.ctrl
940 {
941 let inner = matches!(input.key, Key::Char('i'));
942 ed.set_pending(Pending::OpTextObj { op, count1, inner });
943 return true;
944 }
945
946 if input.key == Key::Char('g') && !input.ctrl {
948 ed.set_pending(Pending::OpG { op, count1 });
949 return true;
950 }
951
952 if !input.ctrl && input.key == Key::Char('[') {
954 ed.set_pending(Pending::OpSquareBracketOpen { op, count1 });
955 return true;
956 }
957 if !input.ctrl && input.key == Key::Char(']') {
958 ed.set_pending(Pending::OpSquareBracketClose { op, count1 });
959 return true;
960 }
961
962 if let Some((forward, till)) = find_entry(&input) {
964 ed.set_pending(Pending::OpFind {
965 op,
966 count1,
967 forward,
968 till,
969 });
970 return true;
971 }
972
973 if ed.settings().motion_sneak
975 && let Key::Char(sc) = input.key
976 && !input.ctrl
977 && matches!(sc, 's' | 'S')
978 {
979 let forward = sc == 's';
980 ed.set_pending(OpSneakFirst {
981 op,
982 count1,
983 forward,
984 });
985 return true;
986 }
987
988 if !input.ctrl && matches!(input.key, Key::Char('/') | Key::Char('?')) {
992 let forward = input.key == Key::Char('/');
993 ed.enter_search_op(forward, op, count1);
994 return true;
995 }
996
997 let count2 = ed.take_count();
999 let total = count1.max(1).saturating_mul(count2.max(1));
1000 if let Some(motion) = parse_motion(&input) {
1001 let motion = match motion {
1002 Motion::FindRepeat { reverse } => match ed.last_find() {
1003 Some((ch, forward, till)) => Motion::Find {
1004 ch,
1005 forward: if reverse { !forward } else { forward },
1006 till,
1007 },
1008 None => return true,
1009 },
1010 Motion::WordFwd
1015 if op == Operator::Change
1016 && ed.char_at_cursor().is_some_and(|c| !c.is_whitespace()) =>
1017 {
1018 Motion::WordEnd
1019 }
1020 Motion::BigWordFwd
1021 if op == Operator::Change
1022 && ed.char_at_cursor().is_some_and(|c| !c.is_whitespace()) =>
1023 {
1024 Motion::BigWordEnd
1025 }
1026 m => m,
1027 };
1028 ed.apply_op_with_motion_direct(op, &motion, total);
1029 if let Motion::Find { ch, forward, till } = &motion {
1030 ed.set_last_find(Some((*ch, *forward, *till)));
1031 }
1032 if !ed.is_replaying()
1035 && (op_is_change(op) || matches!(op, Operator::Indent | Operator::Outdent))
1036 {
1037 ed.set_last_change(Some(LastChange::OpMotion {
1038 op,
1039 motion,
1040 count: total,
1041 inserted: None,
1042 }));
1043 }
1044 return true;
1045 }
1046
1047 true
1049}
1050
1051fn handle_op_after_g<H: Host>(
1052 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1053 input: Input,
1054 op: Operator,
1055 count1: usize,
1056) -> bool {
1057 let count2 = ed.take_count();
1060 if input.ctrl {
1061 return true;
1062 }
1063 let total = count1.max(1).saturating_mul(count2.max(1));
1064 if let Key::Char(ch) = input.key {
1065 ed.apply_op_g(op, ch, total);
1066 }
1067 true
1068}
1069
1070fn handle_after_g<H: Host>(
1071 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1072 input: Input,
1073) -> bool {
1074 let count = ed.take_count();
1075 if ed.is_visual() {
1078 if input.ctrl {
1079 if let Key::Char(c) = input.key {
1081 match c {
1082 'a' => ed.adjust_number_visual(count.max(1) as i64, true),
1083 'x' => ed.adjust_number_visual(-(count.max(1) as i64), true),
1084 _ => {}
1085 }
1086 }
1087 return true;
1088 }
1089 if let Key::Char(c) = input.key {
1090 match c {
1091 'u' => ed.apply_visual_operator(Operator::Lowercase, count.max(1)),
1092 'U' => ed.apply_visual_operator(Operator::Uppercase, count.max(1)),
1093 '~' => ed.apply_visual_operator(Operator::ToggleCase, count.max(1)),
1094 '?' => ed.apply_visual_operator(Operator::Rot13, count.max(1)),
1095 'q' => ed.apply_visual_operator(Operator::Reflow, count.max(1)),
1096 'w' => ed.apply_visual_operator(Operator::ReflowKeepCursor, count.max(1)),
1097 'J' => ed.visual_join(false),
1099 'g' | 'e' | 'E' | '_' | 'j' | 'k' | 'M' | 'm' | '*' | '#' => ed.after_g(c, count),
1101 _ => {}
1103 }
1104 }
1105 return true;
1106 }
1107 if let Key::Char(ch) = input.key {
1110 ed.after_g(ch, count);
1111 }
1112 true
1113}
1114
1115fn handle_after_z<H: Host>(
1116 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1117 input: Input,
1118) -> bool {
1119 let count = ed.take_count();
1120 if let Key::Char(ch) = input.key {
1123 ed.after_z(ch, count);
1124 }
1125 true
1126}
1127
1128fn handle_replace<H: Host>(
1129 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1130 input: Input,
1131) -> bool {
1132 let count = ed.take_count();
1135 if let Key::Char(ch) = input.key {
1136 if ed.fsm_mode() == FsmMode::VisualBlock {
1137 ed.replace_block_char(ch);
1138 return true;
1139 }
1140 if matches!(ed.fsm_mode(), FsmMode::Visual | FsmMode::VisualLine) {
1144 ed.visual_replace_char(ch);
1145 return true;
1146 }
1147 ed.replace_char_at(ch, count.max(1));
1148 if !ed.is_replaying() {
1149 ed.set_last_change(Some(LastChange::ReplaceChar {
1150 ch,
1151 count: count.max(1),
1152 }));
1153 }
1154 }
1155 true
1156}
1157
1158fn handle_find_target<H: Host>(
1159 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1160 input: Input,
1161 forward: bool,
1162 till: bool,
1163) -> bool {
1164 let count = ed.take_count();
1167 let Key::Char(ch) = input.key else {
1168 return true;
1169 };
1170 ed.find_char(ch, forward, till, count.max(1));
1171 true
1172}
1173
1174fn handle_op_find_target<H: Host>(
1175 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1176 input: Input,
1177 op: Operator,
1178 count1: usize,
1179 forward: bool,
1180 till: bool,
1181) -> bool {
1182 let count2 = ed.take_count();
1184 let Key::Char(ch) = input.key else {
1185 return true;
1186 };
1187 let total = count1.max(1).saturating_mul(count2.max(1));
1188 ed.apply_op_find(op, ch, forward, till, total);
1189 true
1190}
1191
1192fn handle_text_object<H: Host>(
1193 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1194 input: Input,
1195 op: Operator,
1196 count1: usize,
1197 inner: bool,
1198) -> bool {
1199 let count2 = ed.take_count();
1204 let Key::Char(ch) = input.key else {
1205 return true;
1206 };
1207 let total = count1.max(1).saturating_mul(count2.max(1));
1208 ed.apply_op_text_obj(op, ch, inner, total);
1211 true
1212}
1213
1214fn handle_visual_text_obj<H: Host>(
1215 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1216 input: Input,
1217 inner: bool,
1218) -> bool {
1219 let Key::Char(ch) = input.key else {
1220 return true;
1221 };
1222 ed.visual_text_obj_extend(ch, inner);
1223 true
1224}
1225
1226fn handle_after_square_bracket_open<H: Host>(
1230 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1231 input: Input,
1232 count: usize,
1233) -> bool {
1234 if let Key::Char('p' | 'P') = input.key {
1236 ed.paste_reindent(true, count.max(1));
1237 return true;
1238 }
1239 let motion = match input.key {
1240 Key::Char('[') => Motion::SectionBackward,
1241 Key::Char(']') => Motion::SectionEndBackward,
1242 Key::Char('(') => Motion::UnmatchedBracket {
1244 forward: false,
1245 open: '(',
1246 },
1247 Key::Char('{') => Motion::UnmatchedBracket {
1248 forward: false,
1249 open: '{',
1250 },
1251 _ => return true, };
1253 ed.execute_motion(motion, count);
1254 true
1255}
1256
1257fn handle_after_square_bracket_close<H: Host>(
1259 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1260 input: Input,
1261 count: usize,
1262) -> bool {
1263 match input.key {
1265 Key::Char('p') => {
1266 ed.paste_reindent(false, count.max(1));
1267 return true;
1268 }
1269 Key::Char('P') => {
1270 ed.paste_reindent(true, count.max(1));
1271 return true;
1272 }
1273 _ => {}
1274 }
1275 let motion = match input.key {
1276 Key::Char(']') => Motion::SectionForward,
1277 Key::Char('[') => Motion::SectionEndForward,
1278 Key::Char(')') => Motion::UnmatchedBracket {
1280 forward: true,
1281 open: '(',
1282 },
1283 Key::Char('}') => Motion::UnmatchedBracket {
1284 forward: true,
1285 open: '{',
1286 },
1287 _ => return true,
1288 };
1289 ed.execute_motion(motion, count);
1290 true
1291}
1292
1293fn handle_op_after_square_bracket_open<H: Host>(
1295 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1296 input: Input,
1297 op: Operator,
1298 count1: usize,
1299) -> bool {
1300 let count2 = ed.take_count();
1303 let motion = match input.key {
1304 Key::Char('[') => Motion::SectionBackward,
1305 Key::Char(']') => Motion::SectionEndBackward,
1306 Key::Char('(') => Motion::UnmatchedBracket {
1307 forward: false,
1308 open: '(',
1309 },
1310 Key::Char('{') => Motion::UnmatchedBracket {
1311 forward: false,
1312 open: '{',
1313 },
1314 _ => return true,
1315 };
1316 let total = count1.max(1).saturating_mul(count2.max(1));
1317 ed.apply_op_with_motion_direct(op, &motion, total);
1318 true
1319}
1320
1321fn handle_op_after_square_bracket_close<H: Host>(
1323 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1324 input: Input,
1325 op: Operator,
1326 count1: usize,
1327) -> bool {
1328 let count2 = ed.take_count();
1330 let motion = match input.key {
1331 Key::Char(']') => Motion::SectionForward,
1332 Key::Char('[') => Motion::SectionEndForward,
1333 Key::Char(')') => Motion::UnmatchedBracket {
1334 forward: true,
1335 open: '(',
1336 },
1337 Key::Char('}') => Motion::UnmatchedBracket {
1338 forward: true,
1339 open: '{',
1340 },
1341 _ => return true,
1342 };
1343 let total = count1.max(1).saturating_mul(count2.max(1));
1344 ed.apply_op_with_motion_direct(op, &motion, total);
1345 true
1346}
1347
1348fn char_to_operator(c: char) -> Option<Operator> {
1351 match c {
1352 'd' => Some(Operator::Delete),
1353 'c' => Some(Operator::Change),
1354 'y' => Some(Operator::Yank),
1355 '>' => Some(Operator::Indent),
1356 '<' => Some(Operator::Outdent),
1357 '=' => Some(Operator::AutoIndent),
1358 _ => None,
1359 }
1360}
1361
1362fn visual_operator(input: &Input) -> Option<Operator> {
1363 if input.ctrl {
1364 return None;
1365 }
1366 match input.key {
1367 Key::Char('y') => Some(Operator::Yank),
1368 Key::Char('d') | Key::Char('x') => Some(Operator::Delete),
1369 Key::Char('c') | Key::Char('s') => Some(Operator::Change),
1370 Key::Char('U') => Some(Operator::Uppercase),
1372 Key::Char('u') => Some(Operator::Lowercase),
1373 Key::Char('~') => Some(Operator::ToggleCase),
1374 Key::Char('>') => Some(Operator::Indent),
1376 Key::Char('<') => Some(Operator::Outdent),
1377 Key::Char('=') => Some(Operator::AutoIndent),
1379 _ => None,
1380 }
1381}
1382
1383fn find_entry(input: &Input) -> Option<(bool, bool)> {
1384 if input.ctrl {
1385 return None;
1386 }
1387 match input.key {
1388 Key::Char('f') => Some((true, false)),
1389 Key::Char('F') => Some((false, false)),
1390 Key::Char('t') => Some((true, true)),
1391 Key::Char('T') => Some((false, true)),
1392 _ => None,
1393 }
1394}
1395
1396fn handle_sneak_first<H: Host>(
1405 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1406 input: Input,
1407 forward: bool,
1408 count: usize,
1409) -> bool {
1410 match input.key {
1411 Key::Esc => {
1412 true
1414 }
1415 Key::Char(c1) => {
1416 ed.set_pending(hjkl_engine::Pending::SneakSecond { c1, forward, count });
1418 true
1419 }
1420 _ => {
1421 true
1423 }
1424 }
1425}
1426
1427fn handle_sneak_second<H: Host>(
1430 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1431 input: Input,
1432 c1: char,
1433 forward: bool,
1434 count: usize,
1435) -> bool {
1436 match input.key {
1437 Key::Esc => true, Key::Char(c2) => {
1439 ed.sneak(c1, c2, forward, count.max(1));
1440 true
1441 }
1442 _ => true, }
1444}
1445
1446fn handle_op_sneak_first<H: Host>(
1448 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1449 input: Input,
1450 op: Operator,
1451 count1: usize,
1452 forward: bool,
1453) -> bool {
1454 match input.key {
1455 Key::Esc => {
1456 ed.reset_count();
1458 true
1459 }
1460 Key::Char(c1) => {
1461 ed.set_pending(hjkl_engine::Pending::OpSneakSecond {
1462 op,
1463 count1,
1464 c1,
1465 forward,
1466 });
1467 true
1468 }
1469 _ => {
1470 ed.reset_count();
1471 true
1472 }
1473 }
1474}
1475
1476fn handle_op_sneak_second<H: Host>(
1478 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1479 input: Input,
1480 op: Operator,
1481 count1: usize,
1482 c1: char,
1483 forward: bool,
1484) -> bool {
1485 let count2 = ed.take_count();
1487 match input.key {
1488 Key::Esc => true,
1489 Key::Char(c2) => {
1490 let total = count1.max(1).saturating_mul(count2.max(1));
1491 ed.apply_op_sneak(op, c1, c2, forward, total);
1492 true
1493 }
1494 _ => true,
1495 }
1496}