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 Motion::WordFwd
1039 if op == Operator::Change
1040 && ed.char_at_cursor().is_some_and(|c| !c.is_whitespace()) =>
1041 {
1042 Motion::WordEnd
1043 }
1044 Motion::BigWordFwd
1045 if op == Operator::Change
1046 && ed.char_at_cursor().is_some_and(|c| !c.is_whitespace()) =>
1047 {
1048 Motion::BigWordEnd
1049 }
1050 m => m,
1051 };
1052 let register = ed.pending_register();
1055 ed.apply_op_with_motion_direct(op, &motion, total);
1056 if let Motion::Find { ch, forward, till } = &motion {
1057 ed.set_last_find(Some((*ch, *forward, *till)));
1058 }
1059 if !ed.is_replaying()
1062 && (op_is_change(op) || matches!(op, Operator::Indent | Operator::Outdent))
1063 {
1064 ed.set_last_change(Some(LastChange::OpMotion {
1065 op,
1066 motion,
1067 count: total,
1068 inserted: None,
1069 register,
1070 }));
1071 }
1072 return true;
1073 }
1074
1075 true
1077}
1078
1079fn handle_op_after_g<H: Host>(
1080 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1081 input: Input,
1082 op: Operator,
1083 count1: usize,
1084) -> bool {
1085 let count2 = ed.take_count();
1088 if input.ctrl {
1089 return true;
1090 }
1091 let total = count1.max(1).saturating_mul(count2.max(1));
1092 if let Key::Char(ch) = input.key {
1093 ed.apply_op_g(op, ch, total);
1094 }
1095 true
1096}
1097
1098fn handle_after_g<H: Host>(
1099 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1100 input: Input,
1101) -> bool {
1102 let count = ed.take_count();
1103 if ed.is_visual() {
1106 if input.ctrl {
1107 if let Key::Char(c) = input.key {
1109 match c {
1110 'a' => ed.adjust_number_visual(count.max(1) as i64, true),
1111 'x' => ed.adjust_number_visual(-(count.max(1) as i64), true),
1112 _ => {}
1113 }
1114 }
1115 return true;
1116 }
1117 if let Key::Char(c) = input.key {
1118 match c {
1119 'u' => ed.apply_visual_operator(Operator::Lowercase, count.max(1)),
1120 'U' => ed.apply_visual_operator(Operator::Uppercase, count.max(1)),
1121 '~' => ed.apply_visual_operator(Operator::ToggleCase, count.max(1)),
1122 '?' => ed.apply_visual_operator(Operator::Rot13, count.max(1)),
1123 'q' => ed.apply_visual_operator(Operator::Reflow, count.max(1)),
1124 'w' => ed.apply_visual_operator(Operator::ReflowKeepCursor, count.max(1)),
1125 'J' => ed.visual_join(false),
1127 'g' | 'e' | 'E' | '_' | 'j' | 'k' | 'M' | 'm' | '*' | '#' => ed.after_g(c, count),
1129 _ => {}
1131 }
1132 }
1133 return true;
1134 }
1135 if let Key::Char(ch) = input.key {
1138 ed.after_g(ch, count);
1139 }
1140 true
1141}
1142
1143fn handle_after_z<H: Host>(
1144 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1145 input: Input,
1146) -> bool {
1147 let count = ed.take_count();
1148 if let Key::Char(ch) = input.key {
1151 ed.after_z(ch, count);
1152 }
1153 true
1154}
1155
1156fn handle_replace<H: Host>(
1157 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1158 input: Input,
1159) -> bool {
1160 let count = ed.take_count();
1163 if let Key::Char(ch) = input.key {
1164 if ed.fsm_mode() == FsmMode::VisualBlock {
1165 ed.replace_block_char(ch);
1166 return true;
1167 }
1168 if matches!(ed.fsm_mode(), FsmMode::Visual | FsmMode::VisualLine) {
1172 ed.visual_replace_char(ch);
1173 return true;
1174 }
1175 ed.replace_char_at(ch, count.max(1));
1176 if !ed.is_replaying() {
1177 ed.set_last_change(Some(LastChange::ReplaceChar {
1178 ch,
1179 count: count.max(1),
1180 }));
1181 }
1182 }
1183 true
1184}
1185
1186fn handle_find_target<H: Host>(
1187 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1188 input: Input,
1189 forward: bool,
1190 till: bool,
1191) -> bool {
1192 let count = ed.take_count();
1195 let Key::Char(ch) = input.key else {
1196 return true;
1197 };
1198 ed.find_char(ch, forward, till, count.max(1));
1199 true
1200}
1201
1202fn handle_op_find_target<H: Host>(
1203 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1204 input: Input,
1205 op: Operator,
1206 count1: usize,
1207 forward: bool,
1208 till: bool,
1209) -> bool {
1210 let count2 = ed.take_count();
1212 let Key::Char(ch) = input.key else {
1213 return true;
1214 };
1215 let total = count1.max(1).saturating_mul(count2.max(1));
1216 ed.apply_op_find(op, ch, forward, till, total);
1217 true
1218}
1219
1220fn handle_text_object<H: Host>(
1221 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1222 input: Input,
1223 op: Operator,
1224 count1: usize,
1225 inner: bool,
1226) -> bool {
1227 let count2 = ed.take_count();
1232 let Key::Char(ch) = input.key else {
1233 return true;
1234 };
1235 let total = count1.max(1).saturating_mul(count2.max(1));
1236 ed.apply_op_text_obj(op, ch, inner, total);
1239 true
1240}
1241
1242fn handle_visual_text_obj<H: Host>(
1243 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1244 input: Input,
1245 inner: bool,
1246) -> bool {
1247 let count = ed.take_count();
1248 let Key::Char(ch) = input.key else {
1249 return true;
1250 };
1251 ed.visual_text_obj_extend_counted(ch, inner, count);
1252 true
1253}
1254
1255fn handle_after_square_bracket_open<H: Host>(
1259 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1260 input: Input,
1261 count: usize,
1262) -> bool {
1263 if let Key::Char('p' | 'P') = input.key {
1265 ed.paste_reindent(true, count.max(1));
1266 return true;
1267 }
1268 let motion = match input.key {
1269 Key::Char('[') => Motion::SectionBackward,
1270 Key::Char(']') => Motion::SectionEndBackward,
1271 Key::Char('(') => Motion::UnmatchedBracket {
1273 forward: false,
1274 open: '(',
1275 },
1276 Key::Char('{') => Motion::UnmatchedBracket {
1277 forward: false,
1278 open: '{',
1279 },
1280 _ => return true, };
1282 ed.execute_motion(motion, count);
1283 true
1284}
1285
1286fn handle_after_square_bracket_close<H: Host>(
1288 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1289 input: Input,
1290 count: usize,
1291) -> bool {
1292 match input.key {
1294 Key::Char('p') => {
1295 ed.paste_reindent(false, count.max(1));
1296 return true;
1297 }
1298 Key::Char('P') => {
1299 ed.paste_reindent(true, count.max(1));
1300 return true;
1301 }
1302 _ => {}
1303 }
1304 let motion = match input.key {
1305 Key::Char(']') => Motion::SectionForward,
1306 Key::Char('[') => Motion::SectionEndForward,
1307 Key::Char(')') => Motion::UnmatchedBracket {
1309 forward: true,
1310 open: '(',
1311 },
1312 Key::Char('}') => Motion::UnmatchedBracket {
1313 forward: true,
1314 open: '{',
1315 },
1316 _ => return true,
1317 };
1318 ed.execute_motion(motion, count);
1319 true
1320}
1321
1322fn handle_op_after_square_bracket_open<H: Host>(
1324 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1325 input: Input,
1326 op: Operator,
1327 count1: usize,
1328) -> bool {
1329 let count2 = ed.take_count();
1332 let motion = match input.key {
1333 Key::Char('[') => Motion::SectionBackward,
1334 Key::Char(']') => Motion::SectionEndBackward,
1335 Key::Char('(') => Motion::UnmatchedBracket {
1336 forward: false,
1337 open: '(',
1338 },
1339 Key::Char('{') => Motion::UnmatchedBracket {
1340 forward: false,
1341 open: '{',
1342 },
1343 _ => return true,
1344 };
1345 let total = count1.max(1).saturating_mul(count2.max(1));
1346 ed.apply_op_with_motion_direct(op, &motion, total);
1347 true
1348}
1349
1350fn handle_op_after_square_bracket_close<H: Host>(
1352 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1353 input: Input,
1354 op: Operator,
1355 count1: usize,
1356) -> bool {
1357 let count2 = ed.take_count();
1359 let motion = match input.key {
1360 Key::Char(']') => Motion::SectionForward,
1361 Key::Char('[') => Motion::SectionEndForward,
1362 Key::Char(')') => Motion::UnmatchedBracket {
1363 forward: true,
1364 open: '(',
1365 },
1366 Key::Char('}') => Motion::UnmatchedBracket {
1367 forward: true,
1368 open: '{',
1369 },
1370 _ => return true,
1371 };
1372 let total = count1.max(1).saturating_mul(count2.max(1));
1373 ed.apply_op_with_motion_direct(op, &motion, total);
1374 true
1375}
1376
1377fn char_to_operator(c: char) -> Option<Operator> {
1380 match c {
1381 'd' => Some(Operator::Delete),
1382 'c' => Some(Operator::Change),
1383 'y' => Some(Operator::Yank),
1384 '>' => Some(Operator::Indent),
1385 '<' => Some(Operator::Outdent),
1386 '=' => Some(Operator::AutoIndent),
1387 _ => None,
1388 }
1389}
1390
1391fn visual_operator(input: &Input) -> Option<Operator> {
1392 if input.ctrl {
1393 return None;
1394 }
1395 match input.key {
1396 Key::Char('y') => Some(Operator::Yank),
1397 Key::Char('d') | Key::Char('x') => Some(Operator::Delete),
1398 Key::Char('c') | Key::Char('s') => Some(Operator::Change),
1399 Key::Char('U') => Some(Operator::Uppercase),
1401 Key::Char('u') => Some(Operator::Lowercase),
1402 Key::Char('~') => Some(Operator::ToggleCase),
1403 Key::Char('>') => Some(Operator::Indent),
1405 Key::Char('<') => Some(Operator::Outdent),
1406 Key::Char('=') => Some(Operator::AutoIndent),
1408 _ => None,
1409 }
1410}
1411
1412fn find_entry(input: &Input) -> Option<(bool, bool)> {
1413 if input.ctrl {
1414 return None;
1415 }
1416 match input.key {
1417 Key::Char('f') => Some((true, false)),
1418 Key::Char('F') => Some((false, false)),
1419 Key::Char('t') => Some((true, true)),
1420 Key::Char('T') => Some((false, true)),
1421 _ => None,
1422 }
1423}
1424
1425fn handle_sneak_first<H: Host>(
1434 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1435 input: Input,
1436 forward: bool,
1437 count: usize,
1438) -> bool {
1439 match input.key {
1440 Key::Esc => {
1441 true
1443 }
1444 Key::Char(c1) => {
1445 ed.set_pending(hjkl_engine::Pending::SneakSecond { c1, forward, count });
1447 true
1448 }
1449 _ => {
1450 true
1452 }
1453 }
1454}
1455
1456fn handle_sneak_second<H: Host>(
1459 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1460 input: Input,
1461 c1: char,
1462 forward: bool,
1463 count: usize,
1464) -> bool {
1465 match input.key {
1466 Key::Esc => true, Key::Char(c2) => {
1468 ed.sneak(c1, c2, forward, count.max(1));
1469 true
1470 }
1471 _ => true, }
1473}
1474
1475fn handle_op_sneak_first<H: Host>(
1477 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1478 input: Input,
1479 op: Operator,
1480 count1: usize,
1481 forward: bool,
1482) -> bool {
1483 match input.key {
1484 Key::Esc => {
1485 ed.reset_count();
1487 true
1488 }
1489 Key::Char(c1) => {
1490 ed.set_pending(hjkl_engine::Pending::OpSneakSecond {
1491 op,
1492 count1,
1493 c1,
1494 forward,
1495 });
1496 true
1497 }
1498 _ => {
1499 ed.reset_count();
1500 true
1501 }
1502 }
1503}
1504
1505fn handle_op_sneak_second<H: Host>(
1507 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1508 input: Input,
1509 op: Operator,
1510 count1: usize,
1511 c1: char,
1512 forward: bool,
1513) -> bool {
1514 let count2 = ed.take_count();
1516 match input.key {
1517 Key::Esc => true,
1518 Key::Char(c2) => {
1519 let total = count1.max(1).saturating_mul(count2.max(1));
1520 ed.apply_op_sneak(op, c1, c2, forward, total);
1521 true
1522 }
1523 _ => true,
1524 }
1525}