1use crate::vim::{op_is_change, parse_motion};
10use hjkl_engine::{
11 FsmMode, Host, Input, Key, LastChange, Motion, Operator, Pending, ScrollDir, VimMode,
12};
13
14use hjkl_engine::Pending::{OpSneakFirst, OpSneakSecond, SneakFirst, SneakSecond};
16
17use crate::VimEditorExt;
18
19pub fn step_normal<H: Host>(
26 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
27 input: Input,
28) -> bool {
29 if let Key::Char(d @ '0'..='9') = input.key
31 && !input.ctrl
32 && !input.alt
33 && !matches!(
34 ed.pending(),
35 Pending::Replace
36 | Pending::Find { .. }
37 | Pending::OpFind { .. }
38 | Pending::VisualTextObj { .. }
39 | Pending::SelectRegister
43 | Pending::SetMark
44 | Pending::GotoMarkLine
45 | Pending::GotoMarkChar
46 | Pending::RecordMacroTarget
47 | Pending::PlayMacroTarget { .. }
48 | SneakFirst { .. }
49 | SneakSecond { .. }
50 | OpSneakFirst { .. }
51 | OpSneakSecond { .. }
52 )
53 && (d != '0' || ed.count() > 0)
54 {
55 ed.accumulate_count_digit(d as usize - '0' as usize);
56 return true;
57 }
58
59 match ed.take_pending() {
61 Pending::Replace => return handle_replace(ed, input),
62 Pending::Find { forward, till } => return handle_find_target(ed, input, forward, till),
63 Pending::OpFind {
64 op,
65 count1,
66 forward,
67 till,
68 } => return handle_op_find_target(ed, input, op, count1, forward, till),
69 Pending::G => return handle_after_g(ed, input),
70 Pending::OpG { op, count1 } => return handle_op_after_g(ed, input, op, count1),
71 Pending::Op { op, count1 } => return handle_after_op(ed, input, op, count1),
72 Pending::OpTextObj { op, count1, inner } => {
73 return handle_text_object(ed, input, op, count1, inner);
74 }
75 Pending::VisualTextObj { inner } => {
76 return handle_visual_text_obj(ed, input, inner);
77 }
78 Pending::Z => return handle_after_z(ed, input),
79 Pending::SetMark => return handle_set_mark(ed, input),
80 Pending::GotoMarkLine => return handle_goto_mark(ed, input, true),
81 Pending::GotoMarkChar => return handle_goto_mark(ed, input, false),
82 Pending::SelectRegister => return handle_select_register(ed, input),
83 Pending::RecordMacroTarget => return handle_record_macro_target(ed, input),
84 Pending::PlayMacroTarget { count } => return handle_play_macro_target(ed, input, count),
85 Pending::SquareBracketOpen => {
86 let cnt = ed.take_count();
87 return handle_after_square_bracket_open(ed, input, cnt);
88 }
89 Pending::SquareBracketClose => {
90 let cnt = ed.take_count();
91 return handle_after_square_bracket_close(ed, input, cnt);
92 }
93 Pending::OpSquareBracketOpen { op, count1 } => {
94 return handle_op_after_square_bracket_open(ed, input, op, count1);
95 }
96 Pending::OpSquareBracketClose { op, count1 } => {
97 return handle_op_after_square_bracket_close(ed, input, op, count1);
98 }
99 SneakFirst { forward, count } => {
100 return handle_sneak_first(ed, input, forward, count);
101 }
102 SneakSecond { c1, forward, count } => {
103 return handle_sneak_second(ed, input, c1, forward, count);
104 }
105 OpSneakFirst {
106 op,
107 count1,
108 forward,
109 } => {
110 return handle_op_sneak_first(ed, input, op, count1, forward);
111 }
112 OpSneakSecond {
113 op,
114 count1,
115 c1,
116 forward,
117 } => {
118 return handle_op_sneak_second(ed, input, op, count1, c1, forward);
119 }
120 Pending::None => {}
121 }
122
123 let had_explicit_count = ed.count() > 0;
126 let count = ed.take_count();
127
128 match input.key {
130 Key::Esc => {
131 ed.exit_blame();
134 ed.force_normal();
135 return true;
136 }
137 Key::Char('v') if !input.ctrl && ed.fsm_mode() == FsmMode::Normal => {
138 ed.set_visual_anchor(ed.cursor());
139 ed.set_mode(VimMode::Visual);
140 return true;
141 }
142 Key::Char('V') if !input.ctrl && ed.fsm_mode() == FsmMode::Normal => {
143 let (row, _) = ed.cursor();
144 ed.set_visual_line_anchor(row);
145 ed.set_mode(VimMode::VisualLine);
146 return true;
147 }
148 Key::Char('v') if !input.ctrl && ed.fsm_mode() == FsmMode::VisualLine => {
149 ed.set_visual_anchor(ed.cursor());
150 ed.set_mode(VimMode::Visual);
151 return true;
152 }
153 Key::Char('V') if !input.ctrl && ed.fsm_mode() == FsmMode::Visual => {
154 let (row, _) = ed.cursor();
155 ed.set_visual_line_anchor(row);
156 ed.set_mode(VimMode::VisualLine);
157 return true;
158 }
159 Key::Char('v') if input.ctrl && ed.fsm_mode() == FsmMode::Normal => {
160 let cur = ed.cursor();
161 ed.set_block_anchor(cur);
162 ed.set_block_vcol(cur.1);
163 ed.set_block_to_eol(false);
164 ed.set_mode(VimMode::VisualBlock);
165 return true;
166 }
167 Key::Char('v') if input.ctrl && ed.fsm_mode() == FsmMode::VisualBlock => {
168 ed.set_mode(VimMode::Normal);
170 return true;
171 }
172 Key::Char('o') if !input.ctrl => match ed.fsm_mode() {
175 FsmMode::Visual => {
176 let cur = ed.cursor();
177 let anchor = ed.visual_anchor();
178 ed.set_visual_anchor(cur);
179 ed.jump_cursor(anchor.0, anchor.1);
180 return true;
181 }
182 FsmMode::VisualLine => {
183 let cur_row = ed.cursor().0;
184 let anchor_row = ed.visual_line_anchor();
185 ed.set_visual_line_anchor(cur_row);
186 ed.jump_cursor(anchor_row, 0);
187 return true;
188 }
189 FsmMode::VisualBlock => {
190 let cur = ed.cursor();
191 let anchor = ed.block_anchor();
192 ed.set_block_anchor(cur);
193 ed.set_block_vcol(anchor.1);
194 ed.jump_cursor(anchor.0, anchor.1);
195 return true;
196 }
197 _ => {}
198 },
199 _ => {}
200 }
201
202 if ed.is_visual() && !input.ctrl && matches!(input.key, Key::Char('p') | Key::Char('P')) {
204 ed.visual_paste(matches!(input.key, Key::Char('P')));
205 return true;
206 }
207
208 if ed.is_visual() && !input.ctrl && input.key == Key::Char('J') {
210 ed.visual_join(true);
211 return true;
212 }
213
214 if ed.is_visual()
218 && let Some(op) = visual_operator(&input)
219 {
220 ed.apply_visual_operator(op, count.max(1));
221 return true;
222 }
223
224 if ed.fsm_mode() == FsmMode::VisualBlock && !input.ctrl {
228 match input.key {
229 Key::Char('r') => {
230 ed.set_pending(Pending::Replace);
231 return true;
232 }
233 Key::Char('I') => {
234 let (top, bot, left, _right) = ed.visual_block_bounds();
235 ed.visual_block_insert_at_left(top, bot, left);
236 return true;
237 }
238 Key::Char('A') => {
239 let (top, bot, _left, right) = ed.visual_block_bounds();
252 let col = if ed.block_to_eol() {
253 ed.line_char_count(top)
254 } else {
255 right + 1
256 };
257 ed.visual_block_append_at_right(top, bot, col);
258 return true;
259 }
260 _ => {}
261 }
262 }
263
264 if matches!(ed.fsm_mode(), FsmMode::Visual | FsmMode::VisualLine)
266 && !input.ctrl
267 && matches!(input.key, Key::Char('i') | Key::Char('a'))
268 {
269 let inner = matches!(input.key, Key::Char('i'));
270 ed.set_pending(Pending::VisualTextObj { inner });
271 return true;
272 }
273
274 if input.ctrl
279 && let Key::Char(c) = input.key
280 {
281 match c {
282 'd' => {
283 ed.scroll_half_page(ScrollDir::Down, count);
284 return true;
285 }
286 'u' => {
287 ed.scroll_half_page(ScrollDir::Up, count);
288 return true;
289 }
290 'f' => {
291 ed.scroll_full_page(ScrollDir::Down, count);
292 return true;
293 }
294 'b' => {
295 ed.scroll_full_page(ScrollDir::Up, count);
296 return true;
297 }
298 'e' if ed.fsm_mode() == FsmMode::Normal => {
299 ed.scroll_line(ScrollDir::Down, count);
300 return true;
301 }
302 'y' if ed.fsm_mode() == FsmMode::Normal => {
303 ed.scroll_line(ScrollDir::Up, count);
304 return true;
305 }
306 'r' => {
307 ed.later_by_steps(count.max(1));
308 return true;
309 }
310 'a' if ed.fsm_mode() == FsmMode::Normal => {
311 ed.adjust_number(count.max(1) as i64);
312 return true;
313 }
314 'a' if ed.is_visual() => {
317 ed.adjust_number_visual(count.max(1) as i64, false);
318 return true;
319 }
320 'x' if ed.is_visual() => {
321 ed.adjust_number_visual(-(count.max(1) as i64), false);
322 return true;
323 }
324 'x' if ed.fsm_mode() == FsmMode::Normal => {
325 ed.adjust_number(-(count.max(1) as i64));
326 return true;
327 }
328 'o' if ed.fsm_mode() == FsmMode::Normal => {
329 ed.jump_back(count);
330 return true;
331 }
332 'i' if ed.fsm_mode() == FsmMode::Normal => {
333 ed.jump_forward(count);
334 return true;
335 }
336 _ => {}
337 }
338 }
339
340 if !input.ctrl && input.key == Key::Tab && ed.fsm_mode() == FsmMode::Normal {
342 ed.jump_forward(count);
343 return true;
344 }
345
346 if !input.ctrl && input.key == Key::Char('%') && had_explicit_count {
349 ed.goto_percent(count);
350 return true;
351 }
352
353 if let Some(motion) = parse_motion(&input) {
355 ed.execute_motion(motion.clone(), count);
356 if ed.fsm_mode() == FsmMode::VisualBlock {
358 ed.update_block_vcol(&motion);
359 }
360 if let Motion::Find { ch, forward, till } = motion {
361 ed.set_last_find(Some((ch, forward, till)));
362 }
363 return true;
364 }
365
366 if ed.fsm_mode() == FsmMode::Normal && !input.ctrl && input.key == Key::Char('.') {
372 ed.replay_last_change(if had_explicit_count { count } else { 0 });
373 return true;
374 }
375
376 if ed.fsm_mode() == FsmMode::Normal && handle_normal_only(ed, &input, count) {
378 return true;
379 }
380
381 if ed.fsm_mode() == FsmMode::Normal
383 && let Key::Char(op_ch) = input.key
384 && !input.ctrl
385 && let Some(op) = char_to_operator(op_ch)
386 {
387 ed.set_pending(Pending::Op { op, count1: count });
388 return true;
389 }
390
391 if ed.fsm_mode() == FsmMode::Normal
393 && let Some((forward, till)) = find_entry(&input)
394 {
395 ed.set_count(count);
396 ed.set_pending(Pending::Find { forward, till });
397 return true;
398 }
399
400 if !input.ctrl
403 && input.key == Key::Char('g')
404 && matches!(
405 ed.fsm_mode(),
406 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
407 )
408 {
409 ed.set_count(count);
410 ed.set_pending(Pending::G);
411 return true;
412 }
413
414 if !input.ctrl
416 && input.key == Key::Char('z')
417 && matches!(
418 ed.fsm_mode(),
419 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
420 )
421 {
422 ed.set_pending(Pending::Z);
423 return true;
424 }
425
426 if !input.ctrl
428 && input.key == Key::Char('[')
429 && matches!(
430 ed.fsm_mode(),
431 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
432 )
433 {
434 ed.set_count(count);
435 ed.set_pending(Pending::SquareBracketOpen);
436 return true;
437 }
438
439 if !input.ctrl
441 && input.key == Key::Char(']')
442 && matches!(
443 ed.fsm_mode(),
444 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
445 )
446 {
447 ed.set_count(count);
448 ed.set_pending(Pending::SquareBracketClose);
449 return true;
450 }
451
452 if !input.ctrl
458 && matches!(
459 ed.fsm_mode(),
460 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
461 )
462 && input.key == Key::Char('`')
463 {
464 ed.set_pending(Pending::GotoMarkChar);
465 return true;
466 }
467 if !input.ctrl && ed.fsm_mode() == FsmMode::Normal {
468 match input.key {
469 Key::Char('m') => {
470 ed.set_pending(Pending::SetMark);
471 return true;
472 }
473 Key::Char('\'') => {
474 ed.set_pending(Pending::GotoMarkLine);
475 return true;
476 }
477 Key::Char('`') => {
478 ed.set_pending(Pending::GotoMarkChar);
480 return true;
481 }
482 Key::Char('"') => {
483 ed.set_pending(Pending::SelectRegister);
486 return true;
487 }
488 Key::Char('@') => {
489 ed.set_pending(Pending::PlayMacroTarget { count });
493 return true;
494 }
495 Key::Char('q') if ed.recording_macro().is_none() => {
496 ed.set_pending(Pending::RecordMacroTarget);
501 return true;
502 }
503 _ => {}
504 }
505 }
506
507 true
509}
510
511fn handle_normal_only<H: Host>(
515 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
516 input: &Input,
517 count: usize,
518) -> bool {
519 if input.ctrl {
520 return false;
521 }
522 match input.key {
523 Key::Char('i') => {
524 ed.enter_insert_i(count);
525 true
526 }
527 Key::Char('I') => {
528 ed.enter_insert_shift_i(count);
529 true
530 }
531 Key::Char('a') => {
532 ed.enter_insert_a(count);
533 true
534 }
535 Key::Char('A') => {
536 ed.enter_insert_shift_a(count);
537 true
538 }
539 Key::Char('R') => {
540 ed.enter_replace_mode(count);
541 true
542 }
543 Key::Char('o') => {
544 ed.open_line_below(count);
545 true
546 }
547 Key::Char('O') => {
548 ed.open_line_above(count);
549 true
550 }
551 Key::Char('x') => {
552 ed.delete_char_forward(count);
553 true
554 }
555 Key::Char('X') => {
556 ed.delete_char_backward(count);
557 true
558 }
559 Key::Char('~') => {
560 ed.toggle_case_at_cursor(count);
561 true
562 }
563 Key::Char('J') => {
564 ed.join_line(count);
565 true
566 }
567 Key::Char('D') => {
568 ed.delete_to_eol();
569 true
570 }
571 Key::Char('Y') => {
572 ed.yank_to_eol(count);
573 true
574 }
575 Key::Char('C') => {
576 ed.change_to_eol();
577 true
578 }
579 Key::Char('s') => {
580 if ed.settings().motion_sneak {
581 ed.set_pending(SneakFirst {
586 forward: true,
587 count,
588 });
589 } else {
590 ed.substitute_char(count);
591 }
592 true
593 }
594 Key::Char('S') => {
595 if ed.settings().motion_sneak {
596 ed.set_pending(SneakFirst {
599 forward: false,
600 count,
601 });
602 } else {
603 ed.substitute_line(count);
604 }
605 true
606 }
607 Key::Char('p') => {
608 ed.paste_after(count);
609 true
610 }
611 Key::Char('P') => {
612 ed.paste_before(count);
613 true
614 }
615 Key::Char('&') => {
616 ed.ampersand_repeat();
618 true
619 }
620 Key::Char('u') => {
621 ed.earlier_by_steps(count.max(1));
622 true
623 }
624 Key::Char('r') => {
625 ed.set_count(count);
626 ed.set_pending(Pending::Replace);
627 true
628 }
629 Key::Char('/') => {
630 ed.enter_search(true);
631 true
632 }
633 Key::Char('?') => {
634 ed.enter_search(false);
635 true
636 }
637 _ => false,
638 }
639}
640
641fn handle_set_mark<H: Host>(
644 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
645 input: Input,
646) -> bool {
647 if let Key::Char(c) = input.key {
648 ed.set_mark_at_cursor(c);
649 }
650 true
651}
652
653fn handle_select_register<H: Host>(
654 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
655 input: Input,
656) -> bool {
657 if let Key::Char(c) = input.key {
658 ed.set_pending_register(c);
659 }
660 true
661}
662
663fn handle_record_macro_target<H: Host>(
664 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
665 input: Input,
666) -> bool {
667 if let Key::Char(c) = input.key
668 && (c.is_ascii_alphabetic() || c.is_ascii_digit())
669 {
670 ed.set_recording_macro(Some(c));
671 if c.is_ascii_uppercase() {
674 let lower = c.to_ascii_lowercase();
675 let text = ed
679 .registers()
680 .read(lower)
681 .map(|s| s.text.clone())
682 .unwrap_or_default();
683 ed.set_recording_keys(hjkl_engine::decode_macro(&text));
684 } else {
685 ed.set_recording_keys(vec![]);
686 }
687 }
688 true
689}
690
691fn handle_play_macro_target<H: Host>(
692 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
693 input: Input,
694 count: usize,
695) -> bool {
696 let reg = match input.key {
697 Key::Char('@') => ed.last_macro(),
698 Key::Char(c) if c.is_ascii_alphabetic() || c.is_ascii_digit() => {
699 Some(c.to_ascii_lowercase())
700 }
701 _ => None,
702 };
703 let Some(reg) = reg else {
704 return true;
705 };
706 let text = match ed.registers().read(reg) {
709 Some(slot) if !slot.text.is_empty() => slot.text.clone(),
710 _ => return true,
711 };
712 let keys = hjkl_engine::decode_macro(&text);
713 ed.set_last_macro(Some(reg));
714 const MAX_REPLAY_DEPTH: usize = 100;
720 thread_local! {
721 static REPLAY_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
722 }
723 let depth = REPLAY_DEPTH.with(std::cell::Cell::get);
724 if depth >= MAX_REPLAY_DEPTH {
725 return true;
726 }
727 REPLAY_DEPTH.with(|d| d.set(depth + 1));
728 let times = count.max(1);
729 let was_replaying = ed.is_replaying_macro_raw();
730 ed.set_replaying_macro_raw(true);
731 for _ in 0..times {
732 for k in keys.iter().copied() {
733 crate::dispatch_input(ed, k);
734 }
735 }
736 ed.set_replaying_macro_raw(was_replaying);
737 REPLAY_DEPTH.with(|d| d.set(depth));
738 true
739}
740
741fn handle_goto_mark<H: Host>(
742 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
743 input: Input,
744 linewise: bool,
745) -> bool {
746 let Key::Char(c) = input.key else {
747 return true;
748 };
749 if linewise {
755 let _ = ed.try_goto_mark_line(c);
756 } else {
757 let _ = ed.try_goto_mark_char(c);
758 }
759 true
760}
761
762fn handle_after_op<H: Host>(
763 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
764 input: Input,
765 op: Operator,
766 count1: usize,
767) -> bool {
768 if let Key::Char(d @ '0'..='9') = input.key
770 && !input.ctrl
771 && (d != '0' || ed.count() > 0)
772 {
773 ed.accumulate_count_digit(d as usize - '0' as usize);
774 ed.set_pending(Pending::Op { op, count1 });
775 return true;
776 }
777
778 if input.key == Key::Esc {
780 ed.reset_count();
781 return true;
782 }
783
784 let double_ch = match op {
788 Operator::Delete => Some('d'),
789 Operator::Change => Some('c'),
790 Operator::Yank => Some('y'),
791 Operator::Indent => Some('>'),
792 Operator::Outdent => Some('<'),
793 Operator::Uppercase => Some('U'),
794 Operator::Lowercase => Some('u'),
795 Operator::ToggleCase => Some('~'),
796 Operator::Fold => None,
797 Operator::Reflow => Some('q'),
800 Operator::ReflowKeepCursor => Some('w'),
802 Operator::AutoIndent => Some('='),
804 Operator::Filter => Some('!'),
806 Operator::Comment => Some('c'),
808 Operator::Rot13 => Some('?'),
810 };
811 if let Key::Char(c) = input.key
812 && !input.ctrl
813 && Some(c) == double_ch
814 {
815 let count2 = ed.take_count();
816 let total = count1.max(1).saturating_mul(count2.max(1));
817 ed.apply_op_double(op, total);
818 return true;
819 }
820
821 if let Key::Char('i') | Key::Char('a') = input.key
823 && !input.ctrl
824 {
825 let inner = matches!(input.key, Key::Char('i'));
826 ed.set_pending(Pending::OpTextObj { op, count1, inner });
827 return true;
828 }
829
830 if input.key == Key::Char('g') && !input.ctrl {
832 ed.set_pending(Pending::OpG { op, count1 });
833 return true;
834 }
835
836 if !input.ctrl && input.key == Key::Char('[') {
838 ed.set_pending(Pending::OpSquareBracketOpen { op, count1 });
839 return true;
840 }
841 if !input.ctrl && input.key == Key::Char(']') {
842 ed.set_pending(Pending::OpSquareBracketClose { op, count1 });
843 return true;
844 }
845
846 if let Some((forward, till)) = find_entry(&input) {
848 ed.set_pending(Pending::OpFind {
849 op,
850 count1,
851 forward,
852 till,
853 });
854 return true;
855 }
856
857 if ed.settings().motion_sneak
859 && let Key::Char(sc) = input.key
860 && !input.ctrl
861 && matches!(sc, 's' | 'S')
862 {
863 let forward = sc == 's';
864 ed.set_pending(OpSneakFirst {
865 op,
866 count1,
867 forward,
868 });
869 return true;
870 }
871
872 if !input.ctrl && matches!(input.key, Key::Char('/') | Key::Char('?')) {
876 let forward = input.key == Key::Char('/');
877 ed.enter_search_op(forward, op, count1);
878 return true;
879 }
880
881 let count2 = ed.take_count();
883 let total = count1.max(1).saturating_mul(count2.max(1));
884 if let Some(motion) = parse_motion(&input) {
885 let motion = match motion {
886 Motion::FindRepeat { reverse } => match ed.last_find() {
887 Some((ch, forward, till)) => Motion::Find {
888 ch,
889 forward: if reverse { !forward } else { forward },
890 till,
891 },
892 None => return true,
893 },
894 Motion::WordFwd
899 if op == Operator::Change
900 && ed.char_at_cursor().is_some_and(|c| !c.is_whitespace()) =>
901 {
902 Motion::WordEnd
903 }
904 Motion::BigWordFwd
905 if op == Operator::Change
906 && ed.char_at_cursor().is_some_and(|c| !c.is_whitespace()) =>
907 {
908 Motion::BigWordEnd
909 }
910 m => m,
911 };
912 ed.apply_op_with_motion_direct(op, &motion, total);
913 if let Motion::Find { ch, forward, till } = &motion {
914 ed.set_last_find(Some((*ch, *forward, *till)));
915 }
916 if !ed.is_replaying()
919 && (op_is_change(op) || matches!(op, Operator::Indent | Operator::Outdent))
920 {
921 ed.set_last_change(Some(LastChange::OpMotion {
922 op,
923 motion,
924 count: total,
925 inserted: None,
926 }));
927 }
928 return true;
929 }
930
931 true
933}
934
935fn handle_op_after_g<H: Host>(
936 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
937 input: Input,
938 op: Operator,
939 count1: usize,
940) -> bool {
941 let count2 = ed.take_count();
944 if input.ctrl {
945 return true;
946 }
947 let total = count1.max(1).saturating_mul(count2.max(1));
948 if let Key::Char(ch) = input.key {
949 ed.apply_op_g(op, ch, total);
950 }
951 true
952}
953
954fn handle_after_g<H: Host>(
955 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
956 input: Input,
957) -> bool {
958 let count = ed.take_count();
959 if ed.is_visual() {
962 if input.ctrl {
963 if let Key::Char(c) = input.key {
965 match c {
966 'a' => ed.adjust_number_visual(count.max(1) as i64, true),
967 'x' => ed.adjust_number_visual(-(count.max(1) as i64), true),
968 _ => {}
969 }
970 }
971 return true;
972 }
973 if let Key::Char(c) = input.key {
974 match c {
975 'u' => ed.apply_visual_operator(Operator::Lowercase, count.max(1)),
976 'U' => ed.apply_visual_operator(Operator::Uppercase, count.max(1)),
977 '~' => ed.apply_visual_operator(Operator::ToggleCase, count.max(1)),
978 '?' => ed.apply_visual_operator(Operator::Rot13, count.max(1)),
979 'q' => ed.apply_visual_operator(Operator::Reflow, count.max(1)),
980 'w' => ed.apply_visual_operator(Operator::ReflowKeepCursor, count.max(1)),
981 'J' => ed.visual_join(false),
983 'g' | 'e' | 'E' | '_' | 'j' | 'k' | 'M' | 'm' | '*' | '#' => ed.after_g(c, count),
985 _ => {}
987 }
988 }
989 return true;
990 }
991 if let Key::Char(ch) = input.key {
994 ed.after_g(ch, count);
995 }
996 true
997}
998
999fn handle_after_z<H: Host>(
1000 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1001 input: Input,
1002) -> bool {
1003 let count = ed.take_count();
1004 if let Key::Char(ch) = input.key {
1007 ed.after_z(ch, count);
1008 }
1009 true
1010}
1011
1012fn handle_replace<H: Host>(
1013 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1014 input: Input,
1015) -> bool {
1016 let count = ed.take_count();
1019 if let Key::Char(ch) = input.key {
1020 if ed.fsm_mode() == FsmMode::VisualBlock {
1021 ed.replace_block_char(ch);
1022 return true;
1023 }
1024 ed.replace_char_at(ch, count.max(1));
1025 if !ed.is_replaying() {
1026 ed.set_last_change(Some(LastChange::ReplaceChar {
1027 ch,
1028 count: count.max(1),
1029 }));
1030 }
1031 }
1032 true
1033}
1034
1035fn handle_find_target<H: Host>(
1036 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1037 input: Input,
1038 forward: bool,
1039 till: bool,
1040) -> bool {
1041 let count = ed.take_count();
1044 let Key::Char(ch) = input.key else {
1045 return true;
1046 };
1047 ed.find_char(ch, forward, till, count.max(1));
1048 true
1049}
1050
1051fn handle_op_find_target<H: Host>(
1052 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1053 input: Input,
1054 op: Operator,
1055 count1: usize,
1056 forward: bool,
1057 till: bool,
1058) -> bool {
1059 let count2 = ed.take_count();
1061 let Key::Char(ch) = input.key else {
1062 return true;
1063 };
1064 let total = count1.max(1).saturating_mul(count2.max(1));
1065 ed.apply_op_find(op, ch, forward, till, total);
1066 true
1067}
1068
1069fn handle_text_object<H: Host>(
1070 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1071 input: Input,
1072 op: Operator,
1073 count1: usize,
1074 inner: bool,
1075) -> bool {
1076 let count2 = ed.take_count();
1081 let Key::Char(ch) = input.key else {
1082 return true;
1083 };
1084 let total = count1.max(1).saturating_mul(count2.max(1));
1085 ed.apply_op_text_obj(op, ch, inner, total);
1088 true
1089}
1090
1091fn handle_visual_text_obj<H: Host>(
1092 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1093 input: Input,
1094 inner: bool,
1095) -> bool {
1096 let Key::Char(ch) = input.key else {
1097 return true;
1098 };
1099 ed.visual_text_obj_extend(ch, inner);
1100 true
1101}
1102
1103fn handle_after_square_bracket_open<H: Host>(
1107 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1108 input: Input,
1109 count: usize,
1110) -> bool {
1111 if let Key::Char('p' | 'P') = input.key {
1113 ed.paste_reindent(true, count.max(1));
1114 return true;
1115 }
1116 let motion = match input.key {
1117 Key::Char('[') => Motion::SectionBackward,
1118 Key::Char(']') => Motion::SectionEndBackward,
1119 Key::Char('(') => Motion::UnmatchedBracket {
1121 forward: false,
1122 open: '(',
1123 },
1124 Key::Char('{') => Motion::UnmatchedBracket {
1125 forward: false,
1126 open: '{',
1127 },
1128 _ => return true, };
1130 ed.execute_motion(motion, count);
1131 true
1132}
1133
1134fn handle_after_square_bracket_close<H: Host>(
1136 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1137 input: Input,
1138 count: usize,
1139) -> bool {
1140 match input.key {
1142 Key::Char('p') => {
1143 ed.paste_reindent(false, count.max(1));
1144 return true;
1145 }
1146 Key::Char('P') => {
1147 ed.paste_reindent(true, count.max(1));
1148 return true;
1149 }
1150 _ => {}
1151 }
1152 let motion = match input.key {
1153 Key::Char(']') => Motion::SectionForward,
1154 Key::Char('[') => Motion::SectionEndForward,
1155 Key::Char(')') => Motion::UnmatchedBracket {
1157 forward: true,
1158 open: '(',
1159 },
1160 Key::Char('}') => Motion::UnmatchedBracket {
1161 forward: true,
1162 open: '{',
1163 },
1164 _ => return true,
1165 };
1166 ed.execute_motion(motion, count);
1167 true
1168}
1169
1170fn handle_op_after_square_bracket_open<H: Host>(
1172 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1173 input: Input,
1174 op: Operator,
1175 count1: usize,
1176) -> bool {
1177 let count2 = ed.take_count();
1180 let motion = match input.key {
1181 Key::Char('[') => Motion::SectionBackward,
1182 Key::Char(']') => Motion::SectionEndBackward,
1183 Key::Char('(') => Motion::UnmatchedBracket {
1184 forward: false,
1185 open: '(',
1186 },
1187 Key::Char('{') => Motion::UnmatchedBracket {
1188 forward: false,
1189 open: '{',
1190 },
1191 _ => return true,
1192 };
1193 let total = count1.max(1).saturating_mul(count2.max(1));
1194 ed.apply_op_with_motion_direct(op, &motion, total);
1195 true
1196}
1197
1198fn handle_op_after_square_bracket_close<H: Host>(
1200 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1201 input: Input,
1202 op: Operator,
1203 count1: usize,
1204) -> bool {
1205 let count2 = ed.take_count();
1207 let motion = match input.key {
1208 Key::Char(']') => Motion::SectionForward,
1209 Key::Char('[') => Motion::SectionEndForward,
1210 Key::Char(')') => Motion::UnmatchedBracket {
1211 forward: true,
1212 open: '(',
1213 },
1214 Key::Char('}') => Motion::UnmatchedBracket {
1215 forward: true,
1216 open: '{',
1217 },
1218 _ => return true,
1219 };
1220 let total = count1.max(1).saturating_mul(count2.max(1));
1221 ed.apply_op_with_motion_direct(op, &motion, total);
1222 true
1223}
1224
1225fn char_to_operator(c: char) -> Option<Operator> {
1228 match c {
1229 'd' => Some(Operator::Delete),
1230 'c' => Some(Operator::Change),
1231 'y' => Some(Operator::Yank),
1232 '>' => Some(Operator::Indent),
1233 '<' => Some(Operator::Outdent),
1234 '=' => Some(Operator::AutoIndent),
1235 _ => None,
1236 }
1237}
1238
1239fn visual_operator(input: &Input) -> Option<Operator> {
1240 if input.ctrl {
1241 return None;
1242 }
1243 match input.key {
1244 Key::Char('y') => Some(Operator::Yank),
1245 Key::Char('d') | Key::Char('x') => Some(Operator::Delete),
1246 Key::Char('c') | Key::Char('s') => Some(Operator::Change),
1247 Key::Char('U') => Some(Operator::Uppercase),
1249 Key::Char('u') => Some(Operator::Lowercase),
1250 Key::Char('~') => Some(Operator::ToggleCase),
1251 Key::Char('>') => Some(Operator::Indent),
1253 Key::Char('<') => Some(Operator::Outdent),
1254 Key::Char('=') => Some(Operator::AutoIndent),
1256 _ => None,
1257 }
1258}
1259
1260fn find_entry(input: &Input) -> Option<(bool, bool)> {
1261 if input.ctrl {
1262 return None;
1263 }
1264 match input.key {
1265 Key::Char('f') => Some((true, false)),
1266 Key::Char('F') => Some((false, false)),
1267 Key::Char('t') => Some((true, true)),
1268 Key::Char('T') => Some((false, true)),
1269 _ => None,
1270 }
1271}
1272
1273fn handle_sneak_first<H: Host>(
1282 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1283 input: Input,
1284 forward: bool,
1285 count: usize,
1286) -> bool {
1287 match input.key {
1288 Key::Esc => {
1289 true
1291 }
1292 Key::Char(c1) => {
1293 ed.set_pending(hjkl_engine::Pending::SneakSecond { c1, forward, count });
1295 true
1296 }
1297 _ => {
1298 true
1300 }
1301 }
1302}
1303
1304fn handle_sneak_second<H: Host>(
1307 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1308 input: Input,
1309 c1: char,
1310 forward: bool,
1311 count: usize,
1312) -> bool {
1313 match input.key {
1314 Key::Esc => true, Key::Char(c2) => {
1316 ed.sneak(c1, c2, forward, count.max(1));
1317 true
1318 }
1319 _ => true, }
1321}
1322
1323fn handle_op_sneak_first<H: Host>(
1325 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1326 input: Input,
1327 op: Operator,
1328 count1: usize,
1329 forward: bool,
1330) -> bool {
1331 match input.key {
1332 Key::Esc => {
1333 ed.reset_count();
1335 true
1336 }
1337 Key::Char(c1) => {
1338 ed.set_pending(hjkl_engine::Pending::OpSneakSecond {
1339 op,
1340 count1,
1341 c1,
1342 forward,
1343 });
1344 true
1345 }
1346 _ => {
1347 ed.reset_count();
1348 true
1349 }
1350 }
1351}
1352
1353fn handle_op_sneak_second<H: Host>(
1355 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1356 input: Input,
1357 op: Operator,
1358 count1: usize,
1359 c1: char,
1360 forward: bool,
1361) -> bool {
1362 let count2 = ed.take_count();
1364 match input.key {
1365 Key::Esc => true,
1366 Key::Char(c2) => {
1367 let total = count1.max(1).saturating_mul(count2.max(1));
1368 ed.apply_op_sneak(op, c1, c2, forward, total);
1369 true
1370 }
1371 _ => true,
1372 }
1373}