1use crate::vim::{op_is_change, parse_motion};
6use hjkl_engine::{
7 FsmMode, Host, Input, Key, LastChange, Motion, Operator, Pending, ScrollDir, VimMode,
8};
9
10use hjkl_engine::Pending::{OpSneakFirst, OpSneakSecond, SneakFirst, SneakSecond};
12
13use crate::VimEditorExt;
14
15pub fn step_normal<H: Host>(
22 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
23 input: Input,
24) -> bool {
25 if let Key::Char(d @ '0'..='9') = input.key
27 && !input.ctrl
28 && !input.alt
29 && !matches!(
30 ed.pending(),
31 Pending::Replace
32 | Pending::Find { .. }
33 | Pending::OpFind { .. }
34 | Pending::VisualTextObj { .. }
35 | Pending::SelectRegister
39 | Pending::SetMark
40 | Pending::GotoMarkLine
41 | Pending::GotoMarkChar
42 | Pending::RecordMacroTarget
43 | Pending::PlayMacroTarget { .. }
44 | SneakFirst { .. }
45 | SneakSecond { .. }
46 | OpSneakFirst { .. }
47 | OpSneakSecond { .. }
48 )
49 && (d != '0' || ed.count() > 0)
50 {
51 ed.accumulate_count_digit(d as usize - '0' as usize);
52 return true;
53 }
54
55 match ed.take_pending() {
57 Pending::Replace => return handle_replace(ed, input),
58 Pending::Find { forward, till } => return handle_find_target(ed, input, forward, till),
59 Pending::OpFind {
60 op,
61 count1,
62 forward,
63 till,
64 } => return handle_op_find_target(ed, input, op, count1, forward, till),
65 Pending::G => return handle_after_g(ed, input),
66 Pending::OpG { op, count1 } => return handle_op_after_g(ed, input, op, count1),
67 Pending::Op { op, count1 } => return handle_after_op(ed, input, op, count1),
68 Pending::OpTextObj { op, count1, inner } => {
69 return handle_text_object(ed, input, op, count1, inner);
70 }
71 Pending::VisualTextObj { inner } => {
72 return handle_visual_text_obj(ed, input, inner);
73 }
74 Pending::Z => return handle_after_z(ed, input),
75 Pending::SetMark => return handle_set_mark(ed, input),
76 Pending::GotoMarkLine => return handle_goto_mark(ed, input, true),
77 Pending::GotoMarkChar => return handle_goto_mark(ed, input, false),
78 Pending::SelectRegister => return handle_select_register(ed, input),
79 Pending::RecordMacroTarget => return handle_record_macro_target(ed, input),
80 Pending::PlayMacroTarget { count } => return handle_play_macro_target(ed, input, count),
81 Pending::SquareBracketOpen => {
82 let cnt = ed.take_count();
83 return handle_after_square_bracket_open(ed, input, cnt);
84 }
85 Pending::SquareBracketClose => {
86 let cnt = ed.take_count();
87 return handle_after_square_bracket_close(ed, input, cnt);
88 }
89 Pending::OpSquareBracketOpen { op, count1 } => {
90 return handle_op_after_square_bracket_open(ed, input, op, count1);
91 }
92 Pending::OpSquareBracketClose { op, count1 } => {
93 return handle_op_after_square_bracket_close(ed, input, op, count1);
94 }
95 SneakFirst { forward, count } => {
96 return handle_sneak_first(ed, input, forward, count);
97 }
98 SneakSecond { c1, forward, count } => {
99 return handle_sneak_second(ed, input, c1, forward, count);
100 }
101 OpSneakFirst {
102 op,
103 count1,
104 forward,
105 } => {
106 return handle_op_sneak_first(ed, input, op, count1, forward);
107 }
108 OpSneakSecond {
109 op,
110 count1,
111 c1,
112 forward,
113 } => {
114 return handle_op_sneak_second(ed, input, op, count1, c1, forward);
115 }
116 Pending::None => {}
117 }
118
119 let had_explicit_count = ed.count() > 0;
122 let count = ed.take_count();
123
124 match input.key {
126 Key::Esc => {
127 ed.exit_blame();
130 ed.force_normal();
131 return true;
132 }
133 Key::Char('v') if !input.ctrl && ed.fsm_mode() == FsmMode::Normal => {
134 ed.set_visual_anchor(ed.cursor());
135 ed.set_mode(VimMode::Visual);
136 if had_explicit_count && count > 1 {
145 let (row, col) = ed.cursor();
146 let line_chars = hjkl_engine::buf_helpers::buf_line_chars(ed.buffer(), row);
147 let target_col = (col + count - 1).min(line_chars);
148 ed.jump_cursor(row, target_col);
149 }
150 return true;
151 }
152 Key::Char('V') if !input.ctrl && ed.fsm_mode() == FsmMode::Normal => {
153 let (row, _) = ed.cursor();
154 ed.set_visual_line_anchor(row);
155 ed.set_mode(VimMode::VisualLine);
156 if had_explicit_count && count > 1 {
164 let 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 ed.set_pending(Pending::VisualTextObj { inner });
353 return true;
354 }
355
356 if input.ctrl
361 && let Key::Char(c) = input.key
362 {
363 match c {
364 'd' => {
365 ed.scroll_half_page(ScrollDir::Down, count);
366 return true;
367 }
368 'u' => {
369 ed.scroll_half_page(ScrollDir::Up, count);
370 return true;
371 }
372 'f' => {
373 ed.scroll_full_page(ScrollDir::Down, count);
374 return true;
375 }
376 'b' => {
377 ed.scroll_full_page(ScrollDir::Up, count);
378 return true;
379 }
380 'e' if ed.fsm_mode() == FsmMode::Normal => {
381 ed.scroll_line(ScrollDir::Down, count);
382 return true;
383 }
384 'y' if ed.fsm_mode() == FsmMode::Normal => {
385 ed.scroll_line(ScrollDir::Up, count);
386 return true;
387 }
388 'r' => {
389 ed.redo_by_steps(count.max(1));
391 return true;
392 }
393 'a' if ed.fsm_mode() == FsmMode::Normal => {
394 ed.adjust_number(count.max(1) as i64);
395 return true;
396 }
397 'a' if ed.is_visual() => {
400 ed.adjust_number_visual(count.max(1) as i64, false);
401 return true;
402 }
403 'x' if ed.is_visual() => {
404 ed.adjust_number_visual(-(count.max(1) as i64), false);
405 return true;
406 }
407 'x' if ed.fsm_mode() == FsmMode::Normal => {
408 ed.adjust_number(-(count.max(1) as i64));
409 return true;
410 }
411 'o' if ed.fsm_mode() == FsmMode::Normal => {
412 ed.jump_back(count);
413 return true;
414 }
415 'i' if ed.fsm_mode() == FsmMode::Normal => {
416 ed.jump_forward(count);
417 return true;
418 }
419 _ => {}
420 }
421 }
422
423 if !input.ctrl && input.key == Key::Tab && ed.fsm_mode() == FsmMode::Normal {
425 ed.jump_forward(count);
426 return true;
427 }
428
429 if !input.ctrl && input.key == Key::Char('%') && had_explicit_count {
432 ed.goto_percent(count);
433 return true;
434 }
435
436 if let Some(motion) = parse_motion(&input) {
438 ed.execute_motion(motion.clone(), count);
439 if ed.fsm_mode() == FsmMode::VisualBlock {
441 ed.update_block_vcol(&motion);
442 }
443 if let Motion::Find { ch, forward, till } = motion {
444 ed.set_last_find(Some((ch, forward, till)));
445 }
446 return true;
447 }
448
449 if ed.fsm_mode() == FsmMode::Normal
455 && !input.ctrl
456 && !input.alt
457 && !input.shift
458 && input.key == Key::Char('.')
459 {
460 ed.replay_last_change(if had_explicit_count { count } else { 0 });
461 return true;
462 }
463
464 if ed.fsm_mode() == FsmMode::Normal && handle_normal_only(ed, &input, count) {
466 return true;
467 }
468
469 if ed.fsm_mode() == FsmMode::Normal
471 && let Key::Char(op_ch) = input.key
472 && !input.ctrl
473 && let Some(op) = char_to_operator(op_ch)
474 {
475 ed.set_pending(Pending::Op { op, count1: count });
476 return true;
477 }
478
479 if ed.fsm_mode() == FsmMode::Normal
481 && let Some((forward, till)) = find_entry(&input)
482 {
483 ed.set_count(count);
484 ed.set_pending(Pending::Find { forward, till });
485 return true;
486 }
487
488 if !input.ctrl
491 && input.key == Key::Char('g')
492 && matches!(
493 ed.fsm_mode(),
494 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
495 )
496 {
497 ed.set_count(count);
498 ed.set_pending(Pending::G);
499 return true;
500 }
501
502 if !input.ctrl
508 && input.key == Key::Char('z')
509 && matches!(
510 ed.fsm_mode(),
511 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
512 )
513 {
514 ed.set_count(count);
515 ed.set_pending(Pending::Z);
516 return true;
517 }
518
519 if !input.ctrl
521 && input.key == Key::Char('[')
522 && matches!(
523 ed.fsm_mode(),
524 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
525 )
526 {
527 ed.set_count(count);
528 ed.set_pending(Pending::SquareBracketOpen);
529 return true;
530 }
531
532 if !input.ctrl
534 && input.key == Key::Char(']')
535 && matches!(
536 ed.fsm_mode(),
537 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
538 )
539 {
540 ed.set_count(count);
541 ed.set_pending(Pending::SquareBracketClose);
542 return true;
543 }
544
545 if !input.ctrl
551 && matches!(
552 ed.fsm_mode(),
553 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
554 )
555 && input.key == Key::Char('`')
556 {
557 ed.set_pending(Pending::GotoMarkChar);
558 return true;
559 }
560
561 if !input.ctrl
569 && matches!(
570 ed.fsm_mode(),
571 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
572 )
573 && input.key == Key::Char('"')
574 {
575 ed.set_pending(Pending::SelectRegister);
576 return true;
577 }
578
579 if !input.ctrl && ed.fsm_mode() == FsmMode::Normal {
580 match input.key {
581 Key::Char('m') => {
582 ed.set_pending(Pending::SetMark);
583 return true;
584 }
585 Key::Char('\'') => {
586 ed.set_pending(Pending::GotoMarkLine);
587 return true;
588 }
589 Key::Char('`') => {
590 ed.set_pending(Pending::GotoMarkChar);
592 return true;
593 }
594 Key::Char('@') => {
595 ed.set_pending(Pending::PlayMacroTarget { count });
599 return true;
600 }
601 Key::Char('q') if ed.recording_macro().is_none() => {
602 ed.set_pending(Pending::RecordMacroTarget);
607 return true;
608 }
609 _ => {}
610 }
611 }
612
613 true
615}
616
617fn handle_normal_only<H: Host>(
621 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
622 input: &Input,
623 count: usize,
624) -> bool {
625 if input.ctrl {
626 return false;
627 }
628 match input.key {
629 Key::Char('i') => {
630 ed.enter_insert_i(count);
631 true
632 }
633 Key::Char('I') => {
634 ed.enter_insert_shift_i(count);
635 true
636 }
637 Key::Char('a') => {
638 ed.enter_insert_a(count);
639 true
640 }
641 Key::Char('A') => {
642 ed.enter_insert_shift_a(count);
643 true
644 }
645 Key::Char('R') => {
646 ed.enter_replace_mode(count);
647 true
648 }
649 Key::Char('o') => {
650 ed.open_line_below(count);
651 true
652 }
653 Key::Char('O') => {
654 ed.open_line_above(count);
655 true
656 }
657 Key::Char('x') => {
658 ed.delete_char_forward(count);
659 true
660 }
661 Key::Char('X') => {
662 ed.delete_char_backward(count);
663 true
664 }
665 Key::Char('~') => {
666 ed.toggle_case_at_cursor(count);
667 true
668 }
669 Key::Char('J') => {
670 ed.join_line(count);
671 true
672 }
673 Key::Char('D') => {
674 ed.delete_to_eol(count);
675 true
676 }
677 Key::Char('Y') => {
678 ed.yank_to_eol(count);
679 true
680 }
681 Key::Char('C') => {
682 ed.change_to_eol(count);
683 true
684 }
685 Key::Char('s') => {
686 if ed.settings().motion_sneak {
687 ed.set_pending(SneakFirst {
692 forward: true,
693 count,
694 });
695 } else {
696 ed.substitute_char(count);
697 }
698 true
699 }
700 Key::Char('S') => {
701 if ed.settings().motion_sneak {
702 ed.set_pending(SneakFirst {
705 forward: false,
706 count,
707 });
708 } else {
709 ed.substitute_line(count);
710 }
711 true
712 }
713 Key::Char('p') => {
714 ed.paste_after(count);
715 true
716 }
717 Key::Char('P') => {
718 ed.paste_before(count);
719 true
720 }
721 Key::Char('&') => {
722 ed.ampersand_repeat();
724 true
725 }
726 Key::Char('u') => {
727 ed.undo_by_steps(count.max(1));
730 true
731 }
732 Key::Char('U') => {
733 ed.undo_line();
737 true
738 }
739 Key::Char('r') => {
740 ed.set_count(count);
741 ed.set_pending(Pending::Replace);
742 true
743 }
744 Key::Char('/') => {
745 ed.enter_search(true);
746 true
747 }
748 Key::Char('?') => {
749 ed.enter_search(false);
750 true
751 }
752 _ => false,
753 }
754}
755
756fn handle_set_mark<H: Host>(
759 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
760 input: Input,
761) -> bool {
762 if let Key::Char(c) = input.key {
763 ed.set_mark_at_cursor(c);
764 }
765 true
766}
767
768fn handle_select_register<H: Host>(
769 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
770 input: Input,
771) -> bool {
772 if let Key::Char(c) = input.key {
773 ed.set_pending_register(c);
774 }
775 true
776}
777
778fn handle_record_macro_target<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 && (c.is_ascii_alphabetic() || c.is_ascii_digit())
784 {
785 ed.set_recording_macro(Some(c));
786 if c.is_ascii_uppercase() {
789 let lower = c.to_ascii_lowercase();
790 let text = ed
794 .with_registers(|r| r.read(lower).map(|s| s.text.clone()))
795 .unwrap_or_default();
796 ed.set_recording_keys(hjkl_engine::decode_macro(&text));
797 } else {
798 ed.set_recording_keys(vec![]);
799 }
800 }
801 true
802}
803
804fn handle_play_macro_target<H: Host>(
805 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
806 input: Input,
807 count: usize,
808) -> bool {
809 let reg = match input.key {
810 Key::Char('@') => ed.last_macro(),
811 Key::Char(c) if c.is_ascii_alphabetic() || c.is_ascii_digit() => {
812 Some(c.to_ascii_lowercase())
813 }
814 _ => None,
815 };
816 let Some(reg) = reg else {
817 return true;
818 };
819 let text = match ed.with_registers(|r| r.read(reg).cloned()) {
822 Some(slot) if !slot.text.is_empty() => slot.text,
823 _ => return true,
824 };
825 let keys = hjkl_engine::decode_macro(&text);
826 ed.set_last_macro(Some(reg));
827 const MAX_REPLAY_DEPTH: usize = 100;
833 thread_local! {
834 static REPLAY_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
835 }
836 let depth = REPLAY_DEPTH.with(std::cell::Cell::get);
837 if depth >= MAX_REPLAY_DEPTH {
838 return true;
839 }
840 REPLAY_DEPTH.with(|d| d.set(depth + 1));
841 let times = count.max(1);
842 let was_replaying = ed.is_replaying_macro_raw();
843 ed.set_replaying_macro_raw(true);
844 {
849 let _undo_group = ed.undo_group();
850 for _ in 0..times {
851 for k in keys.iter().copied() {
852 crate::dispatch_input(ed, k);
853 }
854 }
855 }
856 ed.set_replaying_macro_raw(was_replaying);
857 REPLAY_DEPTH.with(|d| d.set(depth));
858 true
859}
860
861fn handle_goto_mark<H: Host>(
862 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
863 input: Input,
864 linewise: bool,
865) -> bool {
866 let Key::Char(c) = input.key else {
867 return true;
868 };
869 if linewise {
875 let _ = ed.try_goto_mark_line(c);
876 } else {
877 let _ = ed.try_goto_mark_char(c);
878 }
879 true
880}
881
882fn handle_after_op<H: Host>(
883 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
884 input: Input,
885 op: Operator,
886 count1: usize,
887) -> bool {
888 if let Key::Char(d @ '0'..='9') = input.key
890 && !input.ctrl
891 && (d != '0' || ed.count() > 0)
892 {
893 ed.accumulate_count_digit(d as usize - '0' as usize);
894 ed.set_pending(Pending::Op { op, count1 });
895 return true;
896 }
897
898 if input.key == Key::Esc {
900 ed.reset_count();
901 return true;
902 }
903
904 let double_ch = match op {
908 Operator::Delete => Some('d'),
909 Operator::Change => Some('c'),
910 Operator::Yank => Some('y'),
911 Operator::Indent => Some('>'),
912 Operator::Outdent => Some('<'),
913 Operator::Uppercase => Some('U'),
914 Operator::Lowercase => Some('u'),
915 Operator::ToggleCase => Some('~'),
916 Operator::Fold => None,
917 Operator::Reflow => Some('q'),
920 Operator::ReflowKeepCursor => Some('w'),
922 Operator::AutoIndent => Some('='),
924 Operator::Filter => Some('!'),
926 Operator::Comment => Some('c'),
928 Operator::Rot13 => Some('?'),
930 };
931 if let Key::Char(c) = input.key
932 && !input.ctrl
933 && Some(c) == double_ch
934 {
935 let count2 = ed.take_count();
936 let total = count1.max(1).saturating_mul(count2.max(1));
937 ed.apply_op_double(op, total);
938 return true;
939 }
940
941 if let Key::Char('i') | Key::Char('a') = input.key
943 && !input.ctrl
944 {
945 let inner = matches!(input.key, Key::Char('i'));
946 ed.set_pending(Pending::OpTextObj { op, count1, inner });
947 return true;
948 }
949
950 if input.key == Key::Char('g') && !input.ctrl {
952 ed.set_pending(Pending::OpG { op, count1 });
953 return true;
954 }
955
956 if !input.ctrl && input.key == Key::Char('[') {
958 ed.set_pending(Pending::OpSquareBracketOpen { op, count1 });
959 return true;
960 }
961 if !input.ctrl && input.key == Key::Char(']') {
962 ed.set_pending(Pending::OpSquareBracketClose { op, count1 });
963 return true;
964 }
965
966 if let Some((forward, till)) = find_entry(&input) {
968 ed.set_pending(Pending::OpFind {
969 op,
970 count1,
971 forward,
972 till,
973 });
974 return true;
975 }
976
977 if ed.settings().motion_sneak
979 && let Key::Char(sc) = input.key
980 && !input.ctrl
981 && matches!(sc, 's' | 'S')
982 {
983 let forward = sc == 's';
984 ed.set_pending(OpSneakFirst {
985 op,
986 count1,
987 forward,
988 });
989 return true;
990 }
991
992 if !input.ctrl && matches!(input.key, Key::Char('/') | Key::Char('?')) {
996 let forward = input.key == Key::Char('/');
997 ed.enter_search_op(forward, op, count1);
998 return true;
999 }
1000
1001 let count2 = ed.take_count();
1003 let total = count1.max(1).saturating_mul(count2.max(1));
1004 if let Some(motion) = parse_motion(&input) {
1005 let motion = match motion {
1006 Motion::FindRepeat { reverse } => match ed.last_find() {
1007 Some((ch, forward, till)) => Motion::Find {
1008 ch,
1009 forward: if reverse { !forward } else { forward },
1010 till,
1011 },
1012 None => return true,
1013 },
1014 Motion::WordFwd
1019 if op == Operator::Change
1020 && ed.char_at_cursor().is_some_and(|c| !c.is_whitespace()) =>
1021 {
1022 Motion::WordEnd
1023 }
1024 Motion::BigWordFwd
1025 if op == Operator::Change
1026 && ed.char_at_cursor().is_some_and(|c| !c.is_whitespace()) =>
1027 {
1028 Motion::BigWordEnd
1029 }
1030 m => m,
1031 };
1032 let register = ed.pending_register();
1035 ed.apply_op_with_motion_direct(op, &motion, total);
1036 if let Motion::Find { ch, forward, till } = &motion {
1037 ed.set_last_find(Some((*ch, *forward, *till)));
1038 }
1039 if !ed.is_replaying()
1042 && (op_is_change(op) || matches!(op, Operator::Indent | Operator::Outdent))
1043 {
1044 ed.set_last_change(Some(LastChange::OpMotion {
1045 op,
1046 motion,
1047 count: total,
1048 inserted: None,
1049 register,
1050 }));
1051 }
1052 return true;
1053 }
1054
1055 true
1057}
1058
1059fn handle_op_after_g<H: Host>(
1060 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1061 input: Input,
1062 op: Operator,
1063 count1: usize,
1064) -> bool {
1065 let count2 = ed.take_count();
1068 if input.ctrl {
1069 return true;
1070 }
1071 let total = count1.max(1).saturating_mul(count2.max(1));
1072 if let Key::Char(ch) = input.key {
1073 ed.apply_op_g(op, ch, total);
1074 }
1075 true
1076}
1077
1078fn handle_after_g<H: Host>(
1079 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1080 input: Input,
1081) -> bool {
1082 let count = ed.take_count();
1083 if ed.is_visual() {
1086 if input.ctrl {
1087 if let Key::Char(c) = input.key {
1089 match c {
1090 'a' => ed.adjust_number_visual(count.max(1) as i64, true),
1091 'x' => ed.adjust_number_visual(-(count.max(1) as i64), true),
1092 _ => {}
1093 }
1094 }
1095 return true;
1096 }
1097 if let Key::Char(c) = input.key {
1098 match c {
1099 'u' => ed.apply_visual_operator(Operator::Lowercase, count.max(1)),
1100 'U' => ed.apply_visual_operator(Operator::Uppercase, count.max(1)),
1101 '~' => ed.apply_visual_operator(Operator::ToggleCase, count.max(1)),
1102 '?' => ed.apply_visual_operator(Operator::Rot13, count.max(1)),
1103 'q' => ed.apply_visual_operator(Operator::Reflow, count.max(1)),
1104 'w' => ed.apply_visual_operator(Operator::ReflowKeepCursor, count.max(1)),
1105 'J' => ed.visual_join(false),
1107 'g' | 'e' | 'E' | '_' | 'j' | 'k' | 'M' | 'm' | '*' | '#' => ed.after_g(c, count),
1109 _ => {}
1111 }
1112 }
1113 return true;
1114 }
1115 if let Key::Char(ch) = input.key {
1118 ed.after_g(ch, count);
1119 }
1120 true
1121}
1122
1123fn handle_after_z<H: Host>(
1124 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1125 input: Input,
1126) -> bool {
1127 let count = ed.take_count();
1128 if let Key::Char(ch) = input.key {
1131 ed.after_z(ch, count);
1132 }
1133 true
1134}
1135
1136fn handle_replace<H: Host>(
1137 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1138 input: Input,
1139) -> bool {
1140 let count = ed.take_count();
1143 if let Key::Char(ch) = input.key {
1144 if ed.fsm_mode() == FsmMode::VisualBlock {
1145 ed.replace_block_char(ch);
1146 return true;
1147 }
1148 if matches!(ed.fsm_mode(), FsmMode::Visual | FsmMode::VisualLine) {
1152 ed.visual_replace_char(ch);
1153 return true;
1154 }
1155 ed.replace_char_at(ch, count.max(1));
1156 if !ed.is_replaying() {
1157 ed.set_last_change(Some(LastChange::ReplaceChar {
1158 ch,
1159 count: count.max(1),
1160 }));
1161 }
1162 }
1163 true
1164}
1165
1166fn handle_find_target<H: Host>(
1167 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1168 input: Input,
1169 forward: bool,
1170 till: bool,
1171) -> bool {
1172 let count = ed.take_count();
1175 let Key::Char(ch) = input.key else {
1176 return true;
1177 };
1178 ed.find_char(ch, forward, till, count.max(1));
1179 true
1180}
1181
1182fn handle_op_find_target<H: Host>(
1183 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1184 input: Input,
1185 op: Operator,
1186 count1: usize,
1187 forward: bool,
1188 till: bool,
1189) -> bool {
1190 let count2 = ed.take_count();
1192 let Key::Char(ch) = input.key else {
1193 return true;
1194 };
1195 let total = count1.max(1).saturating_mul(count2.max(1));
1196 ed.apply_op_find(op, ch, forward, till, total);
1197 true
1198}
1199
1200fn handle_text_object<H: Host>(
1201 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1202 input: Input,
1203 op: Operator,
1204 count1: usize,
1205 inner: bool,
1206) -> bool {
1207 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_text_obj(op, ch, inner, total);
1219 true
1220}
1221
1222fn handle_visual_text_obj<H: Host>(
1223 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1224 input: Input,
1225 inner: bool,
1226) -> bool {
1227 let Key::Char(ch) = input.key else {
1228 return true;
1229 };
1230 ed.visual_text_obj_extend(ch, inner);
1231 true
1232}
1233
1234fn handle_after_square_bracket_open<H: Host>(
1238 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1239 input: Input,
1240 count: usize,
1241) -> bool {
1242 if let Key::Char('p' | 'P') = input.key {
1244 ed.paste_reindent(true, count.max(1));
1245 return true;
1246 }
1247 let motion = match input.key {
1248 Key::Char('[') => Motion::SectionBackward,
1249 Key::Char(']') => Motion::SectionEndBackward,
1250 Key::Char('(') => Motion::UnmatchedBracket {
1252 forward: false,
1253 open: '(',
1254 },
1255 Key::Char('{') => Motion::UnmatchedBracket {
1256 forward: false,
1257 open: '{',
1258 },
1259 _ => return true, };
1261 ed.execute_motion(motion, count);
1262 true
1263}
1264
1265fn handle_after_square_bracket_close<H: Host>(
1267 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1268 input: Input,
1269 count: usize,
1270) -> bool {
1271 match input.key {
1273 Key::Char('p') => {
1274 ed.paste_reindent(false, count.max(1));
1275 return true;
1276 }
1277 Key::Char('P') => {
1278 ed.paste_reindent(true, count.max(1));
1279 return true;
1280 }
1281 _ => {}
1282 }
1283 let motion = match input.key {
1284 Key::Char(']') => Motion::SectionForward,
1285 Key::Char('[') => Motion::SectionEndForward,
1286 Key::Char(')') => Motion::UnmatchedBracket {
1288 forward: true,
1289 open: '(',
1290 },
1291 Key::Char('}') => Motion::UnmatchedBracket {
1292 forward: true,
1293 open: '{',
1294 },
1295 _ => return true,
1296 };
1297 ed.execute_motion(motion, count);
1298 true
1299}
1300
1301fn handle_op_after_square_bracket_open<H: Host>(
1303 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1304 input: Input,
1305 op: Operator,
1306 count1: usize,
1307) -> bool {
1308 let count2 = ed.take_count();
1311 let motion = match input.key {
1312 Key::Char('[') => Motion::SectionBackward,
1313 Key::Char(']') => Motion::SectionEndBackward,
1314 Key::Char('(') => Motion::UnmatchedBracket {
1315 forward: false,
1316 open: '(',
1317 },
1318 Key::Char('{') => Motion::UnmatchedBracket {
1319 forward: false,
1320 open: '{',
1321 },
1322 _ => return true,
1323 };
1324 let total = count1.max(1).saturating_mul(count2.max(1));
1325 ed.apply_op_with_motion_direct(op, &motion, total);
1326 true
1327}
1328
1329fn handle_op_after_square_bracket_close<H: Host>(
1331 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1332 input: Input,
1333 op: Operator,
1334 count1: usize,
1335) -> bool {
1336 let count2 = ed.take_count();
1338 let motion = match input.key {
1339 Key::Char(']') => Motion::SectionForward,
1340 Key::Char('[') => Motion::SectionEndForward,
1341 Key::Char(')') => Motion::UnmatchedBracket {
1342 forward: true,
1343 open: '(',
1344 },
1345 Key::Char('}') => Motion::UnmatchedBracket {
1346 forward: true,
1347 open: '{',
1348 },
1349 _ => return true,
1350 };
1351 let total = count1.max(1).saturating_mul(count2.max(1));
1352 ed.apply_op_with_motion_direct(op, &motion, total);
1353 true
1354}
1355
1356fn char_to_operator(c: char) -> Option<Operator> {
1359 match c {
1360 'd' => Some(Operator::Delete),
1361 'c' => Some(Operator::Change),
1362 'y' => Some(Operator::Yank),
1363 '>' => Some(Operator::Indent),
1364 '<' => Some(Operator::Outdent),
1365 '=' => Some(Operator::AutoIndent),
1366 _ => None,
1367 }
1368}
1369
1370fn visual_operator(input: &Input) -> Option<Operator> {
1371 if input.ctrl {
1372 return None;
1373 }
1374 match input.key {
1375 Key::Char('y') => Some(Operator::Yank),
1376 Key::Char('d') | Key::Char('x') => Some(Operator::Delete),
1377 Key::Char('c') | Key::Char('s') => Some(Operator::Change),
1378 Key::Char('U') => Some(Operator::Uppercase),
1380 Key::Char('u') => Some(Operator::Lowercase),
1381 Key::Char('~') => Some(Operator::ToggleCase),
1382 Key::Char('>') => Some(Operator::Indent),
1384 Key::Char('<') => Some(Operator::Outdent),
1385 Key::Char('=') => Some(Operator::AutoIndent),
1387 _ => None,
1388 }
1389}
1390
1391fn find_entry(input: &Input) -> Option<(bool, bool)> {
1392 if input.ctrl {
1393 return None;
1394 }
1395 match input.key {
1396 Key::Char('f') => Some((true, false)),
1397 Key::Char('F') => Some((false, false)),
1398 Key::Char('t') => Some((true, true)),
1399 Key::Char('T') => Some((false, true)),
1400 _ => None,
1401 }
1402}
1403
1404fn handle_sneak_first<H: Host>(
1413 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1414 input: Input,
1415 forward: bool,
1416 count: usize,
1417) -> bool {
1418 match input.key {
1419 Key::Esc => {
1420 true
1422 }
1423 Key::Char(c1) => {
1424 ed.set_pending(hjkl_engine::Pending::SneakSecond { c1, forward, count });
1426 true
1427 }
1428 _ => {
1429 true
1431 }
1432 }
1433}
1434
1435fn handle_sneak_second<H: Host>(
1438 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1439 input: Input,
1440 c1: char,
1441 forward: bool,
1442 count: usize,
1443) -> bool {
1444 match input.key {
1445 Key::Esc => true, Key::Char(c2) => {
1447 ed.sneak(c1, c2, forward, count.max(1));
1448 true
1449 }
1450 _ => true, }
1452}
1453
1454fn handle_op_sneak_first<H: Host>(
1456 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1457 input: Input,
1458 op: Operator,
1459 count1: usize,
1460 forward: bool,
1461) -> bool {
1462 match input.key {
1463 Key::Esc => {
1464 ed.reset_count();
1466 true
1467 }
1468 Key::Char(c1) => {
1469 ed.set_pending(hjkl_engine::Pending::OpSneakSecond {
1470 op,
1471 count1,
1472 c1,
1473 forward,
1474 });
1475 true
1476 }
1477 _ => {
1478 ed.reset_count();
1479 true
1480 }
1481 }
1482}
1483
1484fn handle_op_sneak_second<H: Host>(
1486 ed: &mut hjkl_engine::Editor<hjkl_buffer::View, H>,
1487 input: Input,
1488 op: Operator,
1489 count1: usize,
1490 c1: char,
1491 forward: bool,
1492) -> bool {
1493 let count2 = ed.take_count();
1495 match input.key {
1496 Key::Esc => true,
1497 Key::Char(c2) => {
1498 let total = count1.max(1).saturating_mul(count2.max(1));
1499 ed.apply_op_sneak(op, c1, c2, forward, total);
1500 true
1501 }
1502 _ => true,
1503 }
1504}