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