1use crate::vim::{op_is_change, parse_motion, search_selected_text, visual_selection_for_search};
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 target_row = (row + count - 1).min(crate::vim::last_content_row(ed));
165 ed.jump_cursor(target_row, 0);
166 }
167 return true;
168 }
169 Key::Char('v') if !input.ctrl && ed.fsm_mode() == FsmMode::VisualLine => {
170 ed.set_visual_anchor(ed.cursor());
171 ed.set_mode(VimMode::Visual);
172 return true;
173 }
174 Key::Char('V') if !input.ctrl && ed.fsm_mode() == FsmMode::Visual => {
175 let (row, _) = ed.cursor();
176 ed.set_visual_line_anchor(row);
177 ed.set_mode(VimMode::VisualLine);
178 return true;
179 }
180 Key::Char('v') if input.ctrl && ed.fsm_mode() == FsmMode::Normal => {
181 let cur = ed.cursor();
182 ed.set_block_anchor(cur);
183 ed.set_block_vcol(cur.1);
184 ed.set_block_to_eol(false);
185 ed.set_mode(VimMode::VisualBlock);
186 return true;
187 }
188 Key::Char('v') if input.ctrl && ed.fsm_mode() == FsmMode::VisualBlock => {
189 ed.set_mode(VimMode::Normal);
191 return true;
192 }
193 Key::Char('o') if !input.ctrl => match ed.fsm_mode() {
196 FsmMode::Visual => {
197 let cur = ed.cursor();
198 let anchor = ed.visual_anchor();
199 ed.set_visual_anchor(cur);
200 ed.jump_cursor(anchor.0, anchor.1);
201 return true;
202 }
203 FsmMode::VisualLine => {
204 let cur_row = ed.cursor().0;
205 let anchor_row = ed.visual_line_anchor();
206 ed.set_visual_line_anchor(cur_row);
207 ed.jump_cursor(anchor_row, 0);
208 return true;
209 }
210 FsmMode::VisualBlock => {
211 let cur = ed.cursor();
212 let anchor = ed.block_anchor();
213 ed.set_block_anchor(cur);
214 ed.set_block_vcol(anchor.1);
215 ed.jump_cursor(anchor.0, anchor.1);
216 return true;
217 }
218 _ => {}
219 },
220 _ => {}
221 }
222
223 if ed.is_visual() && !input.ctrl && matches!(input.key, Key::Char('p') | Key::Char('P')) {
225 ed.visual_paste(matches!(input.key, Key::Char('P')));
226 return true;
227 }
228
229 if ed.is_visual() && !input.ctrl && input.key == Key::Char('J') {
231 ed.visual_join(true);
232 return true;
233 }
234
235 if ed.is_visual()
239 && let Some(op) = visual_operator(&input)
240 {
241 ed.apply_visual_operator(op, count.max(1));
242 return true;
243 }
244
245 if matches!(ed.fsm_mode(), FsmMode::Visual | FsmMode::VisualLine)
254 && !input.ctrl
255 && input.key == Key::Char('r')
256 {
257 ed.set_pending(Pending::Replace);
258 return true;
259 }
260
261 if ed.fsm_mode() == FsmMode::VisualBlock && !input.ctrl {
265 match input.key {
266 Key::Char('r') => {
267 ed.set_pending(Pending::Replace);
268 return true;
269 }
270 Key::Char('I') => {
271 let (top, bot, left, _right) = ed.visual_block_bounds();
272 ed.visual_block_insert_at_left(top, bot, left, count.max(1));
276 return true;
277 }
278 Key::Char('A') => {
279 let (top, bot, left, right) = ed.visual_block_bounds();
292 let col = if ed.block_to_eol() {
293 ed.line_char_count(top)
294 } else {
295 right + 1
296 };
297 ed.visual_block_append_at_right(top, bot, col, left, count.max(1));
301 return true;
302 }
303 Key::Char('D') => {
312 ed.set_block_to_eol(true);
313 ed.apply_visual_operator(Operator::Delete, 1);
314 return true;
315 }
316 Key::Char('C') => {
317 ed.set_block_to_eol(true);
318 ed.apply_visual_operator(Operator::Change, 1);
319 return true;
320 }
321 Key::Char('X') => {
322 ed.apply_visual_operator(Operator::Delete, 1);
323 return true;
324 }
325 Key::Char('Y') => {
326 ed.apply_visual_operator(Operator::Yank, 1);
327 return true;
328 }
329 Key::Char('S') | Key::Char('R') => {
333 let (top, bot, _left, _right) = ed.visual_block_bounds();
334 ed.set_visual_line_anchor(top);
335 ed.jump_cursor(bot, 0);
336 ed.set_mode(VimMode::VisualLine);
337 ed.apply_visual_operator(Operator::Change, 1);
338 return true;
339 }
340 _ => {}
341 }
342 }
343
344 if matches!(
346 ed.fsm_mode(),
347 FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
348 ) && !input.ctrl
349 && matches!(input.key, Key::Char('i') | Key::Char('a'))
350 {
351 let inner = matches!(input.key, Key::Char('i'));
352 if had_explicit_count {
353 ed.set_count(count);
354 }
355 ed.set_pending(Pending::VisualTextObj { inner });
356 return true;
357 }
358
359 if input.ctrl
364 && let Key::Char(c) = input.key
365 {
366 match c {
367 'd' => {
368 ed.scroll_half_page(ScrollDir::Down, count);
369 return true;
370 }
371 'u' => {
372 ed.scroll_half_page(ScrollDir::Up, count);
373 return true;
374 }
375 'f' => {
376 ed.scroll_full_page(ScrollDir::Down, count);
377 return true;
378 }
379 'b' => {
380 ed.scroll_full_page(ScrollDir::Up, count);
381 return true;
382 }
383 'e' if ed.fsm_mode() == FsmMode::Normal => {
384 ed.scroll_line(ScrollDir::Down, count);
385 return true;
386 }
387 'y' if ed.fsm_mode() == FsmMode::Normal => {
388 ed.scroll_line(ScrollDir::Up, count);
389 return true;
390 }
391 'r' => {
392 ed.redo_by_steps(count.max(1));
394 return true;
395 }
396 'a' if ed.fsm_mode() == FsmMode::Normal => {
397 ed.adjust_number(count.max(1) as i64);
398 return true;
399 }
400 'a' if ed.is_visual() => {
403 ed.adjust_number_visual(count.max(1) as i64, false);
404 return true;
405 }
406 'x' if ed.is_visual() => {
407 ed.adjust_number_visual(-(count.max(1) as i64), false);
408 return true;
409 }
410 'x' if ed.fsm_mode() == FsmMode::Normal => {
411 ed.adjust_number(-(count.max(1) as i64));
412 return true;
413 }
414 'o' if ed.fsm_mode() == FsmMode::Normal => {
415 ed.jump_back(count);
416 return true;
417 }
418 'i' if ed.fsm_mode() == FsmMode::Normal => {
419 ed.jump_forward(count);
420 return true;
421 }
422 _ => {}
423 }
424 }
425
426 if !input.ctrl && input.key == Key::Tab && ed.fsm_mode() == FsmMode::Normal {
428 ed.jump_forward(count);
429 return true;
430 }
431
432 if !input.ctrl && input.key == Key::Char('%') && had_explicit_count {
435 ed.goto_percent(count);
436 return true;
437 }
438
439 if ed.is_visual()
442 && !input.ctrl
443 && !input.alt
444 && let Key::Char(ch @ ('*' | '#')) = input.key
445 {
446 let head = ed.cursor();
447 let (start, selected) = visual_selection_for_search(ed);
448 ed.jump_cursor(start.0, start.1);
449 if !search_selected_text(ed, &selected, ch == '*', count) {
450 ed.jump_cursor(head.0, head.1);
451 }
452 ed.set_mode(VimMode::Normal);
453 return true;
454 }
455
456 if let Some(motion) = parse_motion(&input) {
458 ed.execute_motion(motion.clone(), count);
459 if ed.fsm_mode() == FsmMode::VisualBlock {
461 ed.update_block_vcol(&motion);
462 }
463 if let Motion::Find { ch, forward, till } = motion {
464 ed.set_last_find(Some((ch, forward, till)));
465 }
466 return true;
467 }
468
469 if ed.fsm_mode() == FsmMode::Normal
475 && !input.ctrl
476 && !input.alt
477 && !input.shift
478 && input.key == Key::Char('.')
479 {
480 ed.replay_last_change(if had_explicit_count { count } else { 0 });
481 return true;
482 }
483
484 if ed.fsm_mode() == FsmMode::Normal && handle_normal_only(ed, &input, count) {
486 return true;
487 }
488
489 if ed.fsm_mode() == FsmMode::Normal
491 && let Key::Char(op_ch) = input.key
492 && !input.ctrl
493 && let Some(op) = char_to_operator(op_ch)
494 {
495 ed.set_pending(Pending::Op { op, count1: count });
496 return true;
497 }
498
499 if ed.fsm_mode() == FsmMode::Normal
501 && let Some((forward, till)) = find_entry(&input)
502 {
503 ed.set_count(count);
504 ed.set_pending(Pending::Find { forward, till });
505 return true;
506 }
507
508 if !input.ctrl
511 && input.key == Key::Char('g')
512 && matches!(
513 ed.fsm_mode(),
514 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
515 )
516 {
517 ed.set_count(count);
518 ed.set_pending(Pending::G);
519 return true;
520 }
521
522 if !input.ctrl
528 && input.key == Key::Char('z')
529 && matches!(
530 ed.fsm_mode(),
531 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
532 )
533 {
534 ed.set_count(count);
535 ed.set_pending(Pending::Z);
536 return true;
537 }
538
539 if !input.ctrl
541 && input.key == Key::Char('[')
542 && matches!(
543 ed.fsm_mode(),
544 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
545 )
546 {
547 ed.set_count(count);
548 ed.set_pending(Pending::SquareBracketOpen);
549 return true;
550 }
551
552 if !input.ctrl
554 && input.key == Key::Char(']')
555 && matches!(
556 ed.fsm_mode(),
557 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
558 )
559 {
560 ed.set_count(count);
561 ed.set_pending(Pending::SquareBracketClose);
562 return true;
563 }
564
565 if !input.ctrl
571 && matches!(
572 ed.fsm_mode(),
573 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
574 )
575 && input.key == Key::Char('`')
576 {
577 ed.set_pending(Pending::GotoMarkChar);
578 return true;
579 }
580
581 if !input.ctrl
589 && matches!(
590 ed.fsm_mode(),
591 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
592 )
593 && input.key == Key::Char('"')
594 {
595 ed.set_pending(Pending::SelectRegister);
596 return true;
597 }
598
599 if !input.ctrl && ed.fsm_mode() == FsmMode::Normal {
600 match input.key {
601 Key::Char('m') => {
602 ed.set_pending(Pending::SetMark);
603 return true;
604 }
605 Key::Char('\'') => {
606 ed.set_pending(Pending::GotoMarkLine);
607 return true;
608 }
609 Key::Char('`') => {
610 ed.set_pending(Pending::GotoMarkChar);
612 return true;
613 }
614 Key::Char('@') => {
615 ed.set_pending(Pending::PlayMacroTarget { count });
619 return true;
620 }
621 Key::Char('q') if ed.recording_macro().is_none() => {
622 ed.set_pending(Pending::RecordMacroTarget);
627 return true;
628 }
629 _ => {}
630 }
631 }
632
633 true
635}
636
637fn handle_normal_only<H: Host>(
641 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
642 input: &Input,
643 count: usize,
644) -> bool {
645 if input.ctrl {
646 return false;
647 }
648 match input.key {
649 Key::Char('i') => {
650 ed.enter_insert_i(count);
651 true
652 }
653 Key::Char('I') => {
654 ed.enter_insert_shift_i(count);
655 true
656 }
657 Key::Char('a') => {
658 ed.enter_insert_a(count);
659 true
660 }
661 Key::Char('A') => {
662 ed.enter_insert_shift_a(count);
663 true
664 }
665 Key::Char('R') => {
666 ed.enter_replace_mode(count);
667 true
668 }
669 Key::Char('o') => {
670 ed.open_line_below(count);
671 true
672 }
673 Key::Char('O') => {
674 ed.open_line_above(count);
675 true
676 }
677 Key::Char('x') => {
678 ed.delete_char_forward(count);
679 true
680 }
681 Key::Char('X') => {
682 ed.delete_char_backward(count);
683 true
684 }
685 Key::Char('~') => {
686 ed.toggle_case_at_cursor(count);
687 true
688 }
689 Key::Char('J') => {
690 ed.join_line(count);
691 true
692 }
693 Key::Char('D') => {
694 ed.delete_to_eol(count);
695 true
696 }
697 Key::Char('Y') => {
698 ed.yank_to_eol(count);
699 true
700 }
701 Key::Char('C') => {
702 ed.change_to_eol(count);
703 true
704 }
705 Key::Char('s') => {
706 if ed.settings().motion_sneak {
707 ed.set_pending(SneakFirst {
712 forward: true,
713 count,
714 });
715 } else {
716 ed.substitute_char(count);
717 }
718 true
719 }
720 Key::Char('S') => {
721 if ed.settings().motion_sneak {
722 ed.set_pending(SneakFirst {
725 forward: false,
726 count,
727 });
728 } else {
729 ed.substitute_line(count);
730 }
731 true
732 }
733 Key::Char('p') => {
734 ed.paste_after(count);
735 true
736 }
737 Key::Char('P') => {
738 ed.paste_before(count);
739 true
740 }
741 Key::Char('&') => {
742 ed.ampersand_repeat();
744 true
745 }
746 Key::Char('u') => {
747 ed.undo_by_steps(count.max(1));
750 true
751 }
752 Key::Char('U') => {
753 ed.undo_line();
757 true
758 }
759 Key::Char('r') => {
760 ed.set_count(count);
761 ed.set_pending(Pending::Replace);
762 true
763 }
764 Key::Char('/') => {
765 ed.enter_search(true);
766 true
767 }
768 Key::Char('?') => {
769 ed.enter_search(false);
770 true
771 }
772 _ => false,
773 }
774}
775
776fn handle_set_mark<H: Host>(
779 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
780 input: Input,
781) -> bool {
782 if let Key::Char(c) = input.key {
783 ed.set_mark_at_cursor(c);
784 }
785 true
786}
787
788fn handle_select_register<H: Host>(
789 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
790 input: Input,
791) -> bool {
792 if let Key::Char(c) = input.key {
793 ed.set_pending_register(c);
794 }
795 true
796}
797
798fn handle_record_macro_target<H: Host>(
799 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
800 input: Input,
801) -> bool {
802 if let Key::Char(c) = input.key
803 && (c.is_ascii_alphabetic() || c.is_ascii_digit())
804 {
805 ed.set_recording_macro(Some(c));
806 if c.is_ascii_uppercase() {
809 let lower = c.to_ascii_lowercase();
810 let text = ed
814 .with_registers(|r| r.read(lower).map(|s| s.text.clone()))
815 .unwrap_or_default();
816 ed.set_recording_keys(hjkl_engine::decode_macro(&text));
817 } else {
818 ed.set_recording_keys(vec![]);
819 }
820 }
821 true
822}
823
824fn handle_play_macro_target<H: Host>(
825 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
826 input: Input,
827 count: usize,
828) -> bool {
829 let reg = match input.key {
830 Key::Char('@') => ed.last_macro(),
831 Key::Char(c) if c.is_ascii_alphabetic() || c.is_ascii_digit() => {
832 Some(c.to_ascii_lowercase())
833 }
834 _ => None,
835 };
836 let Some(reg) = reg else {
837 return true;
838 };
839 let text = match ed.with_registers(|r| r.read(reg).cloned()) {
842 Some(slot) if !slot.text.is_empty() => slot.text,
843 _ => return true,
844 };
845 let keys = hjkl_engine::decode_macro(&text);
846 ed.set_last_macro(Some(reg));
847 const MAX_REPLAY_DEPTH: usize = 100;
853 thread_local! {
854 static REPLAY_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
855 }
856 let depth = REPLAY_DEPTH.with(std::cell::Cell::get);
857 if depth >= MAX_REPLAY_DEPTH {
858 return true;
859 }
860 REPLAY_DEPTH.with(|d| d.set(depth + 1));
861 let times = count.max(1);
862 let was_replaying = ed.is_replaying_macro_raw();
863 ed.set_replaying_macro_raw(true);
864 {
869 let _undo_group = ed.undo_group();
870 for _ in 0..times {
871 for k in keys.iter().copied() {
872 crate::dispatch_input(ed, k);
873 }
874 }
875 }
876 ed.set_replaying_macro_raw(was_replaying);
877 REPLAY_DEPTH.with(|d| d.set(depth));
878 true
879}
880
881fn handle_goto_mark<H: Host>(
882 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
883 input: Input,
884 linewise: bool,
885) -> bool {
886 let Key::Char(c) = input.key else {
887 return true;
888 };
889 if linewise {
895 let _ = ed.try_goto_mark_line(c);
896 } else {
897 let _ = ed.try_goto_mark_char(c);
898 }
899 true
900}
901
902fn handle_after_op<H: Host>(
903 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
904 input: Input,
905 op: Operator,
906 count1: usize,
907) -> bool {
908 if let Key::Char(d @ '0'..='9') = input.key
910 && !input.ctrl
911 && (d != '0' || ed.count() > 0)
912 {
913 ed.accumulate_count_digit(d as usize - '0' as usize);
914 ed.set_pending(Pending::Op { op, count1 });
915 return true;
916 }
917
918 if input.key == Key::Esc {
920 ed.reset_count();
921 return true;
922 }
923
924 let double_ch = match op {
928 Operator::Delete => Some('d'),
929 Operator::Change => Some('c'),
930 Operator::Yank => Some('y'),
931 Operator::Indent => Some('>'),
932 Operator::Outdent => Some('<'),
933 Operator::Uppercase => Some('U'),
934 Operator::Lowercase => Some('u'),
935 Operator::ToggleCase => Some('~'),
936 Operator::Fold => None,
937 Operator::Reflow => Some('q'),
940 Operator::ReflowKeepCursor => Some('w'),
942 Operator::AutoIndent => Some('='),
944 Operator::Filter => Some('!'),
946 Operator::Comment => Some('c'),
948 Operator::Rot13 => Some('?'),
950 };
951 if let Key::Char(c) = input.key
952 && !input.ctrl
953 && Some(c) == double_ch
954 {
955 let count2 = ed.take_count();
956 let total = count1.max(1).saturating_mul(count2.max(1));
957 ed.apply_op_double(op, total);
958 return true;
959 }
960
961 if let Key::Char('i') | Key::Char('a') = input.key
963 && !input.ctrl
964 {
965 let inner = matches!(input.key, Key::Char('i'));
966 ed.set_pending(Pending::OpTextObj { op, count1, inner });
967 return true;
968 }
969
970 if input.key == Key::Char('g') && !input.ctrl {
972 ed.set_pending(Pending::OpG { op, count1 });
973 return true;
974 }
975
976 if !input.ctrl && input.key == Key::Char('[') {
978 ed.set_pending(Pending::OpSquareBracketOpen { op, count1 });
979 return true;
980 }
981 if !input.ctrl && input.key == Key::Char(']') {
982 ed.set_pending(Pending::OpSquareBracketClose { op, count1 });
983 return true;
984 }
985
986 if let Some((forward, till)) = find_entry(&input) {
988 ed.set_pending(Pending::OpFind {
989 op,
990 count1,
991 forward,
992 till,
993 });
994 return true;
995 }
996
997 if ed.settings().motion_sneak
999 && let Key::Char(sc) = input.key
1000 && !input.ctrl
1001 && matches!(sc, 's' | 'S')
1002 {
1003 let forward = sc == 's';
1004 ed.set_pending(OpSneakFirst {
1005 op,
1006 count1,
1007 forward,
1008 });
1009 return true;
1010 }
1011
1012 if !input.ctrl && matches!(input.key, Key::Char('/') | Key::Char('?')) {
1016 let forward = input.key == Key::Char('/');
1017 ed.enter_search_op(forward, op, count1);
1018 return true;
1019 }
1020
1021 let count2 = ed.take_count();
1023 let total = count1.max(1).saturating_mul(count2.max(1));
1024 if let Some(motion) = parse_motion(&input) {
1025 let motion = match motion {
1026 Motion::FindRepeat { reverse } => match ed.last_find() {
1027 Some((ch, forward, till)) => Motion::Find {
1028 ch,
1029 forward: if reverse { !forward } else { forward },
1030 till,
1031 },
1032 None => return true,
1033 },
1034 m => crate::vim::operator::change_word_motion(
1035 m,
1036 op,
1037 ed.char_at_cursor().is_some_and(|c| !c.is_whitespace()),
1038 ),
1039 };
1040 let register = ed.pending_register();
1043 ed.apply_op_with_motion_direct(op, &motion, total);
1044 if let Motion::Find { ch, forward, till } = &motion {
1045 ed.set_last_find(Some((*ch, *forward, *till)));
1046 }
1047 if !ed.is_replaying()
1050 && (op_is_change(op) || matches!(op, Operator::Indent | Operator::Outdent))
1051 {
1052 ed.set_last_change(Some(LastChange::OpMotion {
1053 op,
1054 motion,
1055 count: total,
1056 inserted: None,
1057 register,
1058 }));
1059 }
1060 return true;
1061 }
1062
1063 true
1065}
1066
1067fn handle_op_after_g<H: Host>(
1068 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1069 input: Input,
1070 op: Operator,
1071 count1: usize,
1072) -> bool {
1073 let count2 = ed.take_count();
1076 if input.ctrl {
1077 return true;
1078 }
1079 let total = count1.max(1).saturating_mul(count2.max(1));
1080 if let Key::Char(ch) = input.key {
1081 ed.apply_op_g(op, ch, total);
1082 }
1083 true
1084}
1085
1086fn handle_after_g<H: Host>(
1087 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1088 input: Input,
1089) -> bool {
1090 let count = ed.take_count();
1091 if ed.is_visual() {
1094 if input.ctrl {
1095 if let Key::Char(c) = input.key {
1097 match c {
1098 'a' => ed.adjust_number_visual(count.max(1) as i64, true),
1099 'x' => ed.adjust_number_visual(-(count.max(1) as i64), true),
1100 _ => {}
1101 }
1102 }
1103 return true;
1104 }
1105 if let Key::Char(c) = input.key {
1106 match c {
1107 'u' => ed.apply_visual_operator(Operator::Lowercase, count.max(1)),
1108 'U' => ed.apply_visual_operator(Operator::Uppercase, count.max(1)),
1109 '~' => ed.apply_visual_operator(Operator::ToggleCase, count.max(1)),
1110 '?' => ed.apply_visual_operator(Operator::Rot13, count.max(1)),
1111 'q' => ed.apply_visual_operator(Operator::Reflow, count.max(1)),
1112 'w' => ed.apply_visual_operator(Operator::ReflowKeepCursor, count.max(1)),
1113 'J' => ed.visual_join(false),
1115 'g' | 'e' | 'E' | '_' | 'j' | 'k' | 'M' | 'm' | '*' | '#' => ed.after_g(c, count),
1117 _ => {}
1119 }
1120 }
1121 return true;
1122 }
1123 if let Key::Char(ch) = input.key {
1126 ed.after_g(ch, count);
1127 }
1128 true
1129}
1130
1131fn handle_after_z<H: Host>(
1132 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1133 input: Input,
1134) -> bool {
1135 let count = ed.take_count();
1136 if let Key::Char(ch) = input.key {
1139 ed.after_z(ch, count);
1140 }
1141 true
1142}
1143
1144fn handle_replace<H: Host>(
1145 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1146 input: Input,
1147) -> bool {
1148 let count = ed.take_count();
1151 if let Key::Char(ch) = input.key {
1152 if ed.fsm_mode() == FsmMode::VisualBlock {
1153 ed.replace_block_char(ch);
1154 return true;
1155 }
1156 if matches!(ed.fsm_mode(), FsmMode::Visual | FsmMode::VisualLine) {
1160 ed.visual_replace_char(ch);
1161 return true;
1162 }
1163 ed.replace_char_at(ch, count.max(1));
1164 if !ed.is_replaying() {
1165 ed.set_last_change(Some(LastChange::ReplaceChar {
1166 ch,
1167 count: count.max(1),
1168 }));
1169 }
1170 }
1171 true
1172}
1173
1174fn handle_find_target<H: Host>(
1175 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1176 input: Input,
1177 forward: bool,
1178 till: bool,
1179) -> bool {
1180 let count = ed.take_count();
1183 let Key::Char(ch) = input.key else {
1184 return true;
1185 };
1186 ed.find_char(ch, forward, till, count.max(1));
1187 true
1188}
1189
1190fn handle_op_find_target<H: Host>(
1191 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1192 input: Input,
1193 op: Operator,
1194 count1: usize,
1195 forward: bool,
1196 till: bool,
1197) -> bool {
1198 let count2 = ed.take_count();
1200 let Key::Char(ch) = input.key else {
1201 return true;
1202 };
1203 let total = count1.max(1).saturating_mul(count2.max(1));
1204 ed.apply_op_find(op, ch, forward, till, total);
1205 true
1206}
1207
1208fn handle_text_object<H: Host>(
1209 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1210 input: Input,
1211 op: Operator,
1212 count1: usize,
1213 inner: bool,
1214) -> bool {
1215 let count2 = ed.take_count();
1220 let Key::Char(ch) = input.key else {
1221 return true;
1222 };
1223 let total = count1.max(1).saturating_mul(count2.max(1));
1224 ed.apply_op_text_obj(op, ch, inner, total);
1227 true
1228}
1229
1230fn handle_visual_text_obj<H: Host>(
1231 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1232 input: Input,
1233 inner: bool,
1234) -> bool {
1235 let count = ed.take_count();
1236 let Key::Char(ch) = input.key else {
1237 return true;
1238 };
1239 ed.visual_text_obj_extend_counted(ch, inner, count);
1240 true
1241}
1242
1243fn handle_after_square_bracket_open<H: Host>(
1247 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1248 input: Input,
1249 count: usize,
1250) -> bool {
1251 if let Key::Char('p' | 'P') = input.key {
1253 ed.paste_reindent(true, count.max(1));
1254 return true;
1255 }
1256 let motion = match input.key {
1257 Key::Char('[') => Motion::SectionBackward,
1258 Key::Char(']') => Motion::SectionEndBackward,
1259 Key::Char('(') => Motion::UnmatchedBracket {
1261 forward: false,
1262 open: '(',
1263 },
1264 Key::Char('{') => Motion::UnmatchedBracket {
1265 forward: false,
1266 open: '{',
1267 },
1268 _ => return true, };
1270 ed.execute_motion(motion, count);
1271 true
1272}
1273
1274fn handle_after_square_bracket_close<H: Host>(
1276 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1277 input: Input,
1278 count: usize,
1279) -> bool {
1280 match input.key {
1282 Key::Char('p') => {
1283 ed.paste_reindent(false, count.max(1));
1284 return true;
1285 }
1286 Key::Char('P') => {
1287 ed.paste_reindent(true, count.max(1));
1288 return true;
1289 }
1290 _ => {}
1291 }
1292 let motion = match input.key {
1293 Key::Char(']') => Motion::SectionForward,
1294 Key::Char('[') => Motion::SectionEndForward,
1295 Key::Char(')') => Motion::UnmatchedBracket {
1297 forward: true,
1298 open: '(',
1299 },
1300 Key::Char('}') => Motion::UnmatchedBracket {
1301 forward: true,
1302 open: '{',
1303 },
1304 _ => return true,
1305 };
1306 ed.execute_motion(motion, count);
1307 true
1308}
1309
1310fn handle_op_after_square_bracket_open<H: Host>(
1312 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1313 input: Input,
1314 op: Operator,
1315 count1: usize,
1316) -> bool {
1317 let count2 = ed.take_count();
1320 let motion = match input.key {
1321 Key::Char('[') => Motion::SectionBackward,
1322 Key::Char(']') => Motion::SectionEndBackward,
1323 Key::Char('(') => Motion::UnmatchedBracket {
1324 forward: false,
1325 open: '(',
1326 },
1327 Key::Char('{') => Motion::UnmatchedBracket {
1328 forward: false,
1329 open: '{',
1330 },
1331 _ => return true,
1332 };
1333 let total = count1.max(1).saturating_mul(count2.max(1));
1334 ed.apply_op_with_motion_direct(op, &motion, total);
1335 true
1336}
1337
1338fn handle_op_after_square_bracket_close<H: Host>(
1340 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1341 input: Input,
1342 op: Operator,
1343 count1: usize,
1344) -> bool {
1345 let count2 = ed.take_count();
1347 let motion = match input.key {
1348 Key::Char(']') => Motion::SectionForward,
1349 Key::Char('[') => Motion::SectionEndForward,
1350 Key::Char(')') => Motion::UnmatchedBracket {
1351 forward: true,
1352 open: '(',
1353 },
1354 Key::Char('}') => Motion::UnmatchedBracket {
1355 forward: true,
1356 open: '{',
1357 },
1358 _ => return true,
1359 };
1360 let total = count1.max(1).saturating_mul(count2.max(1));
1361 ed.apply_op_with_motion_direct(op, &motion, total);
1362 true
1363}
1364
1365fn char_to_operator(c: char) -> Option<Operator> {
1368 match c {
1369 'd' => Some(Operator::Delete),
1370 'c' => Some(Operator::Change),
1371 'y' => Some(Operator::Yank),
1372 '>' => Some(Operator::Indent),
1373 '<' => Some(Operator::Outdent),
1374 '=' => Some(Operator::AutoIndent),
1375 _ => None,
1376 }
1377}
1378
1379fn visual_operator(input: &Input) -> Option<Operator> {
1380 if input.ctrl {
1381 return None;
1382 }
1383 match input.key {
1384 Key::Char('y') => Some(Operator::Yank),
1385 Key::Char('d') | Key::Char('x') => Some(Operator::Delete),
1386 Key::Char('c') | Key::Char('s') => Some(Operator::Change),
1387 Key::Char('U') => Some(Operator::Uppercase),
1389 Key::Char('u') => Some(Operator::Lowercase),
1390 Key::Char('~') => Some(Operator::ToggleCase),
1391 Key::Char('>') => Some(Operator::Indent),
1393 Key::Char('<') => Some(Operator::Outdent),
1394 Key::Char('=') => Some(Operator::AutoIndent),
1396 _ => None,
1397 }
1398}
1399
1400fn find_entry(input: &Input) -> Option<(bool, bool)> {
1401 if input.ctrl {
1402 return None;
1403 }
1404 match input.key {
1405 Key::Char('f') => Some((true, false)),
1406 Key::Char('F') => Some((false, false)),
1407 Key::Char('t') => Some((true, true)),
1408 Key::Char('T') => Some((false, true)),
1409 _ => None,
1410 }
1411}
1412
1413fn handle_sneak_first<H: Host>(
1422 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1423 input: Input,
1424 forward: bool,
1425 count: usize,
1426) -> bool {
1427 match input.key {
1428 Key::Esc => {
1429 true
1431 }
1432 Key::Char(c1) => {
1433 ed.set_pending(hjkl_engine::Pending::SneakSecond { c1, forward, count });
1435 true
1436 }
1437 _ => {
1438 true
1440 }
1441 }
1442}
1443
1444fn handle_sneak_second<H: Host>(
1447 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1448 input: Input,
1449 c1: char,
1450 forward: bool,
1451 count: usize,
1452) -> bool {
1453 match input.key {
1454 Key::Esc => true, Key::Char(c2) => {
1456 ed.sneak(c1, c2, forward, count.max(1));
1457 true
1458 }
1459 _ => true, }
1461}
1462
1463fn handle_op_sneak_first<H: Host>(
1465 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1466 input: Input,
1467 op: Operator,
1468 count1: usize,
1469 forward: bool,
1470) -> bool {
1471 match input.key {
1472 Key::Esc => {
1473 ed.reset_count();
1475 true
1476 }
1477 Key::Char(c1) => {
1478 ed.set_pending(hjkl_engine::Pending::OpSneakSecond {
1479 op,
1480 count1,
1481 c1,
1482 forward,
1483 });
1484 true
1485 }
1486 _ => {
1487 ed.reset_count();
1488 true
1489 }
1490 }
1491}
1492
1493fn handle_op_sneak_second<H: Host>(
1495 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1496 input: Input,
1497 op: Operator,
1498 count1: usize,
1499 c1: char,
1500 forward: bool,
1501) -> bool {
1502 let count2 = ed.take_count();
1504 match input.key {
1505 Key::Esc => true,
1506 Key::Char(c2) => {
1507 let total = count1.max(1).saturating_mul(count2.max(1));
1508 ed.apply_op_sneak(op, c1, c2, forward, total);
1509 true
1510 }
1511 _ => true,
1512 }
1513}