1use hjkl_engine::{
10 FsmMode, Host, Input, Key, LastChange, Motion, Operator, Pending, ScrollDir, VimMode,
11 op_is_change, parse_motion,
12};
13
14use hjkl_engine::Pending::{OpSneakFirst, OpSneakSecond, SneakFirst, SneakSecond};
16
17pub fn step_normal<H: Host>(
24 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
25 input: Input,
26) -> bool {
27 if let Key::Char(d @ '0'..='9') = input.key
29 && !input.ctrl
30 && !input.alt
31 && !matches!(
32 ed.pending(),
33 Pending::Replace
34 | Pending::Find { .. }
35 | Pending::OpFind { .. }
36 | Pending::VisualTextObj { .. }
37 | Pending::SelectRegister
41 | Pending::SetMark
42 | Pending::GotoMarkLine
43 | Pending::GotoMarkChar
44 | Pending::RecordMacroTarget
45 | SneakFirst { .. }
46 | SneakSecond { .. }
47 | OpSneakFirst { .. }
48 | OpSneakSecond { .. }
49 )
50 && (d != '0' || ed.count() > 0)
51 {
52 ed.accumulate_count_digit(d as usize - '0' as usize);
53 return true;
54 }
55
56 match ed.take_pending() {
58 Pending::Replace => return handle_replace(ed, input),
59 Pending::Find { forward, till } => return handle_find_target(ed, input, forward, till),
60 Pending::OpFind {
61 op,
62 count1,
63 forward,
64 till,
65 } => return handle_op_find_target(ed, input, op, count1, forward, till),
66 Pending::G => return handle_after_g(ed, input),
67 Pending::OpG { op, count1 } => return handle_op_after_g(ed, input, op, count1),
68 Pending::Op { op, count1 } => return handle_after_op(ed, input, op, count1),
69 Pending::OpTextObj { op, count1, inner } => {
70 return handle_text_object(ed, input, op, count1, inner);
71 }
72 Pending::VisualTextObj { inner } => {
73 return handle_visual_text_obj(ed, input, inner);
74 }
75 Pending::Z => return handle_after_z(ed, input),
76 Pending::SetMark => return handle_set_mark(ed, input),
77 Pending::GotoMarkLine => return handle_goto_mark(ed, input, true),
78 Pending::GotoMarkChar => return handle_goto_mark(ed, input, false),
79 Pending::SelectRegister => return handle_select_register(ed, input),
80 Pending::RecordMacroTarget => return handle_record_macro_target(ed, input),
81 Pending::PlayMacroTarget { count } => return handle_play_macro_target(ed, input, count),
82 Pending::SquareBracketOpen => {
83 let cnt = ed.take_count();
84 return handle_after_square_bracket_open(ed, input, cnt);
85 }
86 Pending::SquareBracketClose => {
87 let cnt = ed.take_count();
88 return handle_after_square_bracket_close(ed, input, cnt);
89 }
90 Pending::OpSquareBracketOpen { op, count1 } => {
91 return handle_op_after_square_bracket_open(ed, input, op, count1);
92 }
93 Pending::OpSquareBracketClose { op, count1 } => {
94 return handle_op_after_square_bracket_close(ed, input, op, count1);
95 }
96 SneakFirst { forward, count } => {
97 return handle_sneak_first(ed, input, forward, count);
98 }
99 SneakSecond { c1, forward, count } => {
100 return handle_sneak_second(ed, input, c1, forward, count);
101 }
102 OpSneakFirst {
103 op,
104 count1,
105 forward,
106 } => {
107 return handle_op_sneak_first(ed, input, op, count1, forward);
108 }
109 OpSneakSecond {
110 op,
111 count1,
112 c1,
113 forward,
114 } => {
115 return handle_op_sneak_second(ed, input, op, count1, c1, forward);
116 }
117 Pending::None => {}
118 }
119
120 let had_explicit_count = ed.count() > 0;
123 let count = ed.take_count();
124
125 match input.key {
127 Key::Esc => {
128 ed.exit_blame();
131 ed.force_normal();
132 return true;
133 }
134 Key::Char('v') if !input.ctrl && ed.fsm_mode() == FsmMode::Normal => {
135 ed.set_visual_anchor(ed.cursor());
136 ed.set_mode(VimMode::Visual);
137 return true;
138 }
139 Key::Char('V') if !input.ctrl && ed.fsm_mode() == FsmMode::Normal => {
140 let (row, _) = ed.cursor();
141 ed.set_visual_line_anchor(row);
142 ed.set_mode(VimMode::VisualLine);
143 return true;
144 }
145 Key::Char('v') if !input.ctrl && ed.fsm_mode() == FsmMode::VisualLine => {
146 ed.set_visual_anchor(ed.cursor());
147 ed.set_mode(VimMode::Visual);
148 return true;
149 }
150 Key::Char('V') if !input.ctrl && ed.fsm_mode() == FsmMode::Visual => {
151 let (row, _) = ed.cursor();
152 ed.set_visual_line_anchor(row);
153 ed.set_mode(VimMode::VisualLine);
154 return true;
155 }
156 Key::Char('v') if input.ctrl && ed.fsm_mode() == FsmMode::Normal => {
157 let cur = ed.cursor();
158 ed.set_block_anchor(cur);
159 ed.set_block_vcol(cur.1);
160 ed.set_mode(VimMode::VisualBlock);
161 return true;
162 }
163 Key::Char('v') if input.ctrl && ed.fsm_mode() == FsmMode::VisualBlock => {
164 ed.set_mode(VimMode::Normal);
166 return true;
167 }
168 Key::Char('o') if !input.ctrl => match ed.fsm_mode() {
171 FsmMode::Visual => {
172 let cur = ed.cursor();
173 let anchor = ed.visual_anchor();
174 ed.set_visual_anchor(cur);
175 ed.jump_cursor(anchor.0, anchor.1);
176 return true;
177 }
178 FsmMode::VisualLine => {
179 let cur_row = ed.cursor().0;
180 let anchor_row = ed.visual_line_anchor();
181 ed.set_visual_line_anchor(cur_row);
182 ed.jump_cursor(anchor_row, 0);
183 return true;
184 }
185 FsmMode::VisualBlock => {
186 let cur = ed.cursor();
187 let anchor = ed.block_anchor();
188 ed.set_block_anchor(cur);
189 ed.set_block_vcol(anchor.1);
190 ed.jump_cursor(anchor.0, anchor.1);
191 return true;
192 }
193 _ => {}
194 },
195 _ => {}
196 }
197
198 if ed.is_visual() && !input.ctrl && matches!(input.key, Key::Char('p') | Key::Char('P')) {
200 ed.visual_paste(matches!(input.key, Key::Char('P')));
201 return true;
202 }
203
204 if ed.is_visual() && !input.ctrl && input.key == Key::Char('J') {
206 ed.visual_join(true);
207 return true;
208 }
209
210 if ed.is_visual()
214 && let Some(op) = visual_operator(&input)
215 {
216 ed.apply_visual_operator(op, count.max(1));
217 return true;
218 }
219
220 if ed.fsm_mode() == FsmMode::VisualBlock && !input.ctrl {
224 match input.key {
225 Key::Char('r') => {
226 ed.set_pending(Pending::Replace);
227 return true;
228 }
229 Key::Char('I') => {
230 let (top, bot, left, _right) = ed.visual_block_bounds();
231 ed.visual_block_insert_at_left(top, bot, left);
232 return true;
233 }
234 Key::Char('A') => {
235 let (top, bot, _left, right) = ed.visual_block_bounds();
236 let line_len = ed.line_char_count(top);
237 let col = (right + 1).min(line_len);
238 ed.visual_block_append_at_right(top, bot, col);
239 return true;
240 }
241 _ => {}
242 }
243 }
244
245 if matches!(ed.fsm_mode(), FsmMode::Visual | FsmMode::VisualLine)
247 && !input.ctrl
248 && matches!(input.key, Key::Char('i') | Key::Char('a'))
249 {
250 let inner = matches!(input.key, Key::Char('i'));
251 ed.set_pending(Pending::VisualTextObj { inner });
252 return true;
253 }
254
255 if input.ctrl
260 && let Key::Char(c) = input.key
261 {
262 match c {
263 'd' => {
264 ed.scroll_half_page(ScrollDir::Down, count);
265 return true;
266 }
267 'u' => {
268 ed.scroll_half_page(ScrollDir::Up, count);
269 return true;
270 }
271 'f' => {
272 ed.scroll_full_page(ScrollDir::Down, count);
273 return true;
274 }
275 'b' => {
276 ed.scroll_full_page(ScrollDir::Up, count);
277 return true;
278 }
279 'e' if ed.fsm_mode() == FsmMode::Normal => {
280 ed.scroll_line(ScrollDir::Down, count);
281 return true;
282 }
283 'y' if ed.fsm_mode() == FsmMode::Normal => {
284 ed.scroll_line(ScrollDir::Up, count);
285 return true;
286 }
287 'r' => {
288 ed.later_by_steps(count.max(1));
289 return true;
290 }
291 'a' if ed.fsm_mode() == FsmMode::Normal => {
292 ed.adjust_number(count.max(1) as i64);
293 return true;
294 }
295 'a' if ed.is_visual() => {
298 ed.adjust_number_visual(count.max(1) as i64, false);
299 return true;
300 }
301 'x' if ed.is_visual() => {
302 ed.adjust_number_visual(-(count.max(1) as i64), false);
303 return true;
304 }
305 'x' if ed.fsm_mode() == FsmMode::Normal => {
306 ed.adjust_number(-(count.max(1) as i64));
307 return true;
308 }
309 'o' if ed.fsm_mode() == FsmMode::Normal => {
310 ed.jump_back(count);
311 return true;
312 }
313 'i' if ed.fsm_mode() == FsmMode::Normal => {
314 ed.jump_forward(count);
315 return true;
316 }
317 _ => {}
318 }
319 }
320
321 if !input.ctrl && input.key == Key::Tab && ed.fsm_mode() == FsmMode::Normal {
323 ed.jump_forward(count);
324 return true;
325 }
326
327 if !input.ctrl && input.key == Key::Char('%') && had_explicit_count {
330 ed.goto_percent(count);
331 return true;
332 }
333
334 if let Some(motion) = parse_motion(&input) {
336 ed.execute_motion(motion.clone(), count);
337 if ed.fsm_mode() == FsmMode::VisualBlock {
339 ed.update_block_vcol(&motion);
340 }
341 if let Motion::Find { ch, forward, till } = motion {
342 ed.set_last_find(Some((ch, forward, till)));
343 }
344 return true;
345 }
346
347 if ed.fsm_mode() == FsmMode::Normal && handle_normal_only(ed, &input, count) {
349 return true;
350 }
351
352 if ed.fsm_mode() == FsmMode::Normal
354 && let Key::Char(op_ch) = input.key
355 && !input.ctrl
356 && let Some(op) = char_to_operator(op_ch)
357 {
358 ed.set_pending(Pending::Op { op, count1: count });
359 return true;
360 }
361
362 if ed.fsm_mode() == FsmMode::Normal
364 && let Some((forward, till)) = find_entry(&input)
365 {
366 ed.set_count(count);
367 ed.set_pending(Pending::Find { forward, till });
368 return true;
369 }
370
371 if !input.ctrl
374 && input.key == Key::Char('g')
375 && matches!(
376 ed.fsm_mode(),
377 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
378 )
379 {
380 ed.set_count(count);
381 ed.set_pending(Pending::G);
382 return true;
383 }
384
385 if !input.ctrl
387 && input.key == Key::Char('z')
388 && matches!(
389 ed.fsm_mode(),
390 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
391 )
392 {
393 ed.set_pending(Pending::Z);
394 return true;
395 }
396
397 if !input.ctrl
399 && input.key == Key::Char('[')
400 && matches!(
401 ed.fsm_mode(),
402 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
403 )
404 {
405 ed.set_count(count);
406 ed.set_pending(Pending::SquareBracketOpen);
407 return true;
408 }
409
410 if !input.ctrl
412 && input.key == Key::Char(']')
413 && matches!(
414 ed.fsm_mode(),
415 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
416 )
417 {
418 ed.set_count(count);
419 ed.set_pending(Pending::SquareBracketClose);
420 return true;
421 }
422
423 if !input.ctrl
429 && matches!(
430 ed.fsm_mode(),
431 FsmMode::Normal | FsmMode::Visual | FsmMode::VisualLine | FsmMode::VisualBlock
432 )
433 && input.key == Key::Char('`')
434 {
435 ed.set_pending(Pending::GotoMarkChar);
436 return true;
437 }
438 if !input.ctrl && ed.fsm_mode() == FsmMode::Normal {
439 match input.key {
440 Key::Char('m') => {
441 ed.set_pending(Pending::SetMark);
442 return true;
443 }
444 Key::Char('\'') => {
445 ed.set_pending(Pending::GotoMarkLine);
446 return true;
447 }
448 Key::Char('`') => {
449 ed.set_pending(Pending::GotoMarkChar);
451 return true;
452 }
453 Key::Char('"') => {
454 ed.set_pending(Pending::SelectRegister);
457 return true;
458 }
459 Key::Char('@') => {
460 ed.set_pending(Pending::PlayMacroTarget { count });
464 return true;
465 }
466 Key::Char('q') if ed.recording_macro().is_none() => {
467 ed.set_pending(Pending::RecordMacroTarget);
472 return true;
473 }
474 _ => {}
475 }
476 }
477
478 true
480}
481
482fn handle_normal_only<H: Host>(
486 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
487 input: &Input,
488 count: usize,
489) -> bool {
490 if input.ctrl {
491 return false;
492 }
493 match input.key {
494 Key::Char('i') => {
495 ed.enter_insert_i(count);
496 true
497 }
498 Key::Char('I') => {
499 ed.enter_insert_shift_i(count);
500 true
501 }
502 Key::Char('a') => {
503 ed.enter_insert_a(count);
504 true
505 }
506 Key::Char('A') => {
507 ed.enter_insert_shift_a(count);
508 true
509 }
510 Key::Char('R') => {
511 ed.enter_replace_mode(count);
512 true
513 }
514 Key::Char('o') => {
515 ed.open_line_below(count);
516 true
517 }
518 Key::Char('O') => {
519 ed.open_line_above(count);
520 true
521 }
522 Key::Char('x') => {
523 ed.delete_char_forward(count);
524 true
525 }
526 Key::Char('X') => {
527 ed.delete_char_backward(count);
528 true
529 }
530 Key::Char('~') => {
531 ed.toggle_case_at_cursor(count);
532 true
533 }
534 Key::Char('J') => {
535 ed.join_line(count);
536 true
537 }
538 Key::Char('D') => {
539 ed.delete_to_eol();
540 true
541 }
542 Key::Char('Y') => {
543 ed.yank_to_eol(count);
544 true
545 }
546 Key::Char('C') => {
547 ed.change_to_eol();
548 true
549 }
550 Key::Char('s') => {
551 if ed.settings().motion_sneak {
552 ed.set_count(count);
554 ed.set_pending(SneakFirst {
555 forward: true,
556 count,
557 });
558 } else {
559 ed.substitute_char(count);
560 }
561 true
562 }
563 Key::Char('S') => {
564 if ed.settings().motion_sneak {
565 ed.set_count(count);
567 ed.set_pending(SneakFirst {
568 forward: false,
569 count,
570 });
571 } else {
572 ed.substitute_line(count);
573 }
574 true
575 }
576 Key::Char('p') => {
577 ed.paste_after(count);
578 true
579 }
580 Key::Char('P') => {
581 ed.paste_before(count);
582 true
583 }
584 Key::Char('&') => {
585 ed.ampersand_repeat();
587 true
588 }
589 Key::Char('u') => {
590 ed.earlier_by_steps(count.max(1));
591 true
592 }
593 Key::Char('r') => {
594 ed.set_count(count);
595 ed.set_pending(Pending::Replace);
596 true
597 }
598 Key::Char('/') => {
599 ed.enter_search(true);
600 true
601 }
602 Key::Char('?') => {
603 ed.enter_search(false);
604 true
605 }
606 Key::Char('.') => {
607 ed.replay_last_change(count);
608 true
609 }
610 _ => false,
611 }
612}
613
614fn handle_set_mark<H: Host>(
617 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
618 input: Input,
619) -> bool {
620 if let Key::Char(c) = input.key {
621 ed.set_mark_at_cursor(c);
622 }
623 true
624}
625
626fn handle_select_register<H: Host>(
627 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
628 input: Input,
629) -> bool {
630 if let Key::Char(c) = input.key {
631 ed.set_pending_register(c);
632 }
633 true
634}
635
636fn handle_record_macro_target<H: Host>(
637 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
638 input: Input,
639) -> bool {
640 if let Key::Char(c) = input.key
641 && (c.is_ascii_alphabetic() || c.is_ascii_digit())
642 {
643 ed.set_recording_macro(Some(c));
644 if c.is_ascii_uppercase() {
647 let lower = c.to_ascii_lowercase();
648 let text = ed
652 .registers()
653 .read(lower)
654 .map(|s| s.text.clone())
655 .unwrap_or_default();
656 ed.set_recording_keys(hjkl_engine::decode_macro(&text));
657 } else {
658 ed.set_recording_keys(vec![]);
659 }
660 }
661 true
662}
663
664fn handle_play_macro_target<H: Host>(
665 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
666 input: Input,
667 count: usize,
668) -> bool {
669 let reg = match input.key {
670 Key::Char('@') => ed.last_macro(),
671 Key::Char(c) if c.is_ascii_alphabetic() || c.is_ascii_digit() => {
672 Some(c.to_ascii_lowercase())
673 }
674 _ => None,
675 };
676 let Some(reg) = reg else {
677 return true;
678 };
679 let text = match ed.registers().read(reg) {
682 Some(slot) if !slot.text.is_empty() => slot.text.clone(),
683 _ => return true,
684 };
685 let keys = hjkl_engine::decode_macro(&text);
686 ed.set_last_macro(Some(reg));
687 let times = count.max(1);
688 let was_replaying = ed.is_replaying_macro_raw();
689 ed.set_replaying_macro_raw(true);
690 for _ in 0..times {
691 for k in keys.iter().copied() {
692 crate::dispatch_input(ed, k);
693 }
694 }
695 ed.set_replaying_macro_raw(was_replaying);
696 true
697}
698
699fn handle_goto_mark<H: Host>(
700 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
701 input: Input,
702 linewise: bool,
703) -> bool {
704 let Key::Char(c) = input.key else {
705 return true;
706 };
707 if linewise {
713 let _ = ed.try_goto_mark_line(c);
714 } else {
715 let _ = ed.try_goto_mark_char(c);
716 }
717 true
718}
719
720fn handle_after_op<H: Host>(
721 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
722 input: Input,
723 op: Operator,
724 count1: usize,
725) -> bool {
726 if let Key::Char(d @ '0'..='9') = input.key
728 && !input.ctrl
729 && (d != '0' || ed.count() > 0)
730 {
731 ed.accumulate_count_digit(d as usize - '0' as usize);
732 ed.set_pending(Pending::Op { op, count1 });
733 return true;
734 }
735
736 if input.key == Key::Esc {
738 ed.reset_count();
739 return true;
740 }
741
742 let double_ch = match op {
746 Operator::Delete => Some('d'),
747 Operator::Change => Some('c'),
748 Operator::Yank => Some('y'),
749 Operator::Indent => Some('>'),
750 Operator::Outdent => Some('<'),
751 Operator::Uppercase => Some('U'),
752 Operator::Lowercase => Some('u'),
753 Operator::ToggleCase => Some('~'),
754 Operator::Fold => None,
755 Operator::Reflow => Some('q'),
758 Operator::ReflowKeepCursor => Some('w'),
760 Operator::AutoIndent => Some('='),
762 Operator::Filter => Some('!'),
764 Operator::Comment => Some('c'),
766 Operator::Rot13 => Some('?'),
768 };
769 if let Key::Char(c) = input.key
770 && !input.ctrl
771 && Some(c) == double_ch
772 {
773 let count2 = ed.take_count();
774 let total = count1.max(1) * count2.max(1);
775 ed.apply_op_double(op, total);
776 return true;
777 }
778
779 if let Key::Char('i') | Key::Char('a') = input.key
781 && !input.ctrl
782 {
783 let inner = matches!(input.key, Key::Char('i'));
784 ed.set_pending(Pending::OpTextObj { op, count1, inner });
785 return true;
786 }
787
788 if input.key == Key::Char('g') && !input.ctrl {
790 ed.set_pending(Pending::OpG { op, count1 });
791 return true;
792 }
793
794 if !input.ctrl && input.key == Key::Char('[') {
796 ed.set_pending(Pending::OpSquareBracketOpen { op, count1 });
797 return true;
798 }
799 if !input.ctrl && input.key == Key::Char(']') {
800 ed.set_pending(Pending::OpSquareBracketClose { op, count1 });
801 return true;
802 }
803
804 if let Some((forward, till)) = find_entry(&input) {
806 ed.set_pending(Pending::OpFind {
807 op,
808 count1,
809 forward,
810 till,
811 });
812 return true;
813 }
814
815 if ed.settings().motion_sneak
817 && let Key::Char(sc) = input.key
818 && !input.ctrl
819 && matches!(sc, 's' | 'S')
820 {
821 let forward = sc == 's';
822 ed.set_pending(OpSneakFirst {
823 op,
824 count1,
825 forward,
826 });
827 return true;
828 }
829
830 if !input.ctrl && matches!(input.key, Key::Char('/') | Key::Char('?')) {
834 let forward = input.key == Key::Char('/');
835 ed.enter_search_op(forward, op, count1);
836 return true;
837 }
838
839 let count2 = ed.take_count();
841 let total = count1.max(1) * count2.max(1);
842 if let Some(motion) = parse_motion(&input) {
843 let motion = match motion {
844 Motion::FindRepeat { reverse } => match ed.last_find() {
845 Some((ch, forward, till)) => Motion::Find {
846 ch,
847 forward: if reverse { !forward } else { forward },
848 till,
849 },
850 None => return true,
851 },
852 Motion::WordFwd if op == Operator::Change => Motion::WordEnd,
856 Motion::BigWordFwd if op == Operator::Change => Motion::BigWordEnd,
857 m => m,
858 };
859 ed.apply_op_with_motion_direct(op, &motion, total);
860 if let Motion::Find { ch, forward, till } = &motion {
861 ed.set_last_find(Some((*ch, *forward, *till)));
862 }
863 if !ed.is_replaying()
866 && (op_is_change(op) || matches!(op, Operator::Indent | Operator::Outdent))
867 {
868 ed.set_last_change(Some(LastChange::OpMotion {
869 op,
870 motion,
871 count: total,
872 inserted: None,
873 }));
874 }
875 return true;
876 }
877
878 true
880}
881
882fn handle_op_after_g<H: Host>(
883 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
884 input: Input,
885 op: Operator,
886 count1: usize,
887) -> bool {
888 if input.ctrl {
889 return true;
890 }
891 let count2 = ed.take_count();
892 let total = count1.max(1) * count2.max(1);
893 if let Key::Char(ch) = input.key {
894 ed.apply_op_g(op, ch, total);
895 }
896 true
897}
898
899fn handle_after_g<H: Host>(
900 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
901 input: Input,
902) -> bool {
903 let count = ed.take_count();
904 if ed.is_visual() {
907 if input.ctrl {
908 if let Key::Char(c) = input.key {
910 match c {
911 'a' => ed.adjust_number_visual(count.max(1) as i64, true),
912 'x' => ed.adjust_number_visual(-(count.max(1) as i64), true),
913 _ => {}
914 }
915 }
916 return true;
917 }
918 if let Key::Char(c) = input.key {
919 match c {
920 'u' => ed.apply_visual_operator(Operator::Lowercase, count.max(1)),
921 'U' => ed.apply_visual_operator(Operator::Uppercase, count.max(1)),
922 '~' => ed.apply_visual_operator(Operator::ToggleCase, count.max(1)),
923 '?' => ed.apply_visual_operator(Operator::Rot13, count.max(1)),
924 'q' => ed.apply_visual_operator(Operator::Reflow, count.max(1)),
925 'w' => ed.apply_visual_operator(Operator::ReflowKeepCursor, count.max(1)),
926 'J' => ed.visual_join(false),
928 'g' | 'e' | 'E' | '_' | 'j' | 'k' | 'M' | 'm' | '*' | '#' => ed.after_g(c, count),
930 _ => {}
932 }
933 }
934 return true;
935 }
936 if let Key::Char(ch) = input.key {
939 ed.after_g(ch, count);
940 }
941 true
942}
943
944fn handle_after_z<H: Host>(
945 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
946 input: Input,
947) -> bool {
948 let count = ed.take_count();
949 if let Key::Char(ch) = input.key {
952 ed.after_z(ch, count);
953 }
954 true
955}
956
957fn handle_replace<H: Host>(
958 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
959 input: Input,
960) -> bool {
961 if let Key::Char(ch) = input.key {
962 if ed.fsm_mode() == FsmMode::VisualBlock {
963 ed.replace_block_char(ch);
964 return true;
965 }
966 let count = ed.take_count();
967 ed.replace_char_at(ch, count.max(1));
968 if !ed.is_replaying() {
969 ed.set_last_change(Some(LastChange::ReplaceChar {
970 ch,
971 count: count.max(1),
972 }));
973 }
974 }
975 true
976}
977
978fn handle_find_target<H: Host>(
979 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
980 input: Input,
981 forward: bool,
982 till: bool,
983) -> bool {
984 let Key::Char(ch) = input.key else {
985 return true;
986 };
987 let count = ed.take_count();
988 ed.find_char(ch, forward, till, count.max(1));
989 true
990}
991
992fn handle_op_find_target<H: Host>(
993 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
994 input: Input,
995 op: Operator,
996 count1: usize,
997 forward: bool,
998 till: bool,
999) -> bool {
1000 let Key::Char(ch) = input.key else {
1001 return true;
1002 };
1003 let count2 = ed.take_count();
1004 let total = count1.max(1) * count2.max(1);
1005 ed.apply_op_find(op, ch, forward, till, total);
1006 true
1007}
1008
1009fn handle_text_object<H: Host>(
1010 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
1011 input: Input,
1012 op: Operator,
1013 count1: usize,
1014 inner: bool,
1015) -> bool {
1016 let Key::Char(ch) = input.key else {
1017 return true;
1018 };
1019 let count2 = ed.take_count();
1023 let total = count1.max(1) * count2.max(1);
1024 ed.apply_op_text_obj(op, ch, inner, total);
1027 true
1028}
1029
1030fn handle_visual_text_obj<H: Host>(
1031 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
1032 input: Input,
1033 inner: bool,
1034) -> bool {
1035 let Key::Char(ch) = input.key else {
1036 return true;
1037 };
1038 ed.visual_text_obj_extend(ch, inner);
1039 true
1040}
1041
1042fn handle_after_square_bracket_open<H: Host>(
1046 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
1047 input: Input,
1048 count: usize,
1049) -> bool {
1050 if let Key::Char('p' | 'P') = input.key {
1052 ed.paste_reindent(true, count.max(1));
1053 return true;
1054 }
1055 let motion = match input.key {
1056 Key::Char('[') => Motion::SectionBackward,
1057 Key::Char(']') => Motion::SectionEndBackward,
1058 Key::Char('(') => Motion::UnmatchedBracket {
1060 forward: false,
1061 open: '(',
1062 },
1063 Key::Char('{') => Motion::UnmatchedBracket {
1064 forward: false,
1065 open: '{',
1066 },
1067 _ => return true, };
1069 ed.execute_motion(motion, count);
1070 true
1071}
1072
1073fn handle_after_square_bracket_close<H: Host>(
1075 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
1076 input: Input,
1077 count: usize,
1078) -> bool {
1079 match input.key {
1081 Key::Char('p') => {
1082 ed.paste_reindent(false, count.max(1));
1083 return true;
1084 }
1085 Key::Char('P') => {
1086 ed.paste_reindent(true, count.max(1));
1087 return true;
1088 }
1089 _ => {}
1090 }
1091 let motion = match input.key {
1092 Key::Char(']') => Motion::SectionForward,
1093 Key::Char('[') => Motion::SectionEndForward,
1094 Key::Char(')') => Motion::UnmatchedBracket {
1096 forward: true,
1097 open: '(',
1098 },
1099 Key::Char('}') => Motion::UnmatchedBracket {
1100 forward: true,
1101 open: '{',
1102 },
1103 _ => return true,
1104 };
1105 ed.execute_motion(motion, count);
1106 true
1107}
1108
1109fn handle_op_after_square_bracket_open<H: Host>(
1111 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
1112 input: Input,
1113 op: Operator,
1114 count1: usize,
1115) -> bool {
1116 let motion = match input.key {
1117 Key::Char('[') => Motion::SectionBackward,
1118 Key::Char(']') => Motion::SectionEndBackward,
1119 Key::Char('(') => Motion::UnmatchedBracket {
1120 forward: false,
1121 open: '(',
1122 },
1123 Key::Char('{') => Motion::UnmatchedBracket {
1124 forward: false,
1125 open: '{',
1126 },
1127 _ => return true,
1128 };
1129 let count2 = ed.take_count();
1130 let total = count1.max(1) * count2.max(1);
1131 ed.apply_op_with_motion_direct(op, &motion, total);
1132 true
1133}
1134
1135fn handle_op_after_square_bracket_close<H: Host>(
1137 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
1138 input: Input,
1139 op: Operator,
1140 count1: usize,
1141) -> bool {
1142 let motion = match input.key {
1143 Key::Char(']') => Motion::SectionForward,
1144 Key::Char('[') => Motion::SectionEndForward,
1145 Key::Char(')') => Motion::UnmatchedBracket {
1146 forward: true,
1147 open: '(',
1148 },
1149 Key::Char('}') => Motion::UnmatchedBracket {
1150 forward: true,
1151 open: '{',
1152 },
1153 _ => return true,
1154 };
1155 let count2 = ed.take_count();
1156 let total = count1.max(1) * count2.max(1);
1157 ed.apply_op_with_motion_direct(op, &motion, total);
1158 true
1159}
1160
1161fn char_to_operator(c: char) -> Option<Operator> {
1164 match c {
1165 'd' => Some(Operator::Delete),
1166 'c' => Some(Operator::Change),
1167 'y' => Some(Operator::Yank),
1168 '>' => Some(Operator::Indent),
1169 '<' => Some(Operator::Outdent),
1170 '=' => Some(Operator::AutoIndent),
1171 _ => None,
1172 }
1173}
1174
1175fn visual_operator(input: &Input) -> Option<Operator> {
1176 if input.ctrl {
1177 return None;
1178 }
1179 match input.key {
1180 Key::Char('y') => Some(Operator::Yank),
1181 Key::Char('d') | Key::Char('x') => Some(Operator::Delete),
1182 Key::Char('c') | Key::Char('s') => Some(Operator::Change),
1183 Key::Char('U') => Some(Operator::Uppercase),
1185 Key::Char('u') => Some(Operator::Lowercase),
1186 Key::Char('~') => Some(Operator::ToggleCase),
1187 Key::Char('>') => Some(Operator::Indent),
1189 Key::Char('<') => Some(Operator::Outdent),
1190 Key::Char('=') => Some(Operator::AutoIndent),
1192 _ => None,
1193 }
1194}
1195
1196fn find_entry(input: &Input) -> Option<(bool, bool)> {
1197 if input.ctrl {
1198 return None;
1199 }
1200 match input.key {
1201 Key::Char('f') => Some((true, false)),
1202 Key::Char('F') => Some((false, false)),
1203 Key::Char('t') => Some((true, true)),
1204 Key::Char('T') => Some((false, true)),
1205 _ => None,
1206 }
1207}
1208
1209fn handle_sneak_first<H: Host>(
1218 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
1219 input: Input,
1220 forward: bool,
1221 count: usize,
1222) -> bool {
1223 match input.key {
1224 Key::Esc => {
1225 true
1227 }
1228 Key::Char(c1) => {
1229 ed.set_pending(hjkl_engine::Pending::SneakSecond { c1, forward, count });
1231 true
1232 }
1233 _ => {
1234 true
1236 }
1237 }
1238}
1239
1240fn handle_sneak_second<H: Host>(
1243 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
1244 input: Input,
1245 c1: char,
1246 forward: bool,
1247 count: usize,
1248) -> bool {
1249 match input.key {
1250 Key::Esc => true, Key::Char(c2) => {
1252 ed.sneak(c1, c2, forward, count.max(1));
1253 true
1254 }
1255 _ => true, }
1257}
1258
1259fn handle_op_sneak_first<H: Host>(
1261 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
1262 input: Input,
1263 op: Operator,
1264 count1: usize,
1265 forward: bool,
1266) -> bool {
1267 match input.key {
1268 Key::Esc => true,
1269 Key::Char(c1) => {
1270 ed.set_pending(hjkl_engine::Pending::OpSneakSecond {
1271 op,
1272 count1,
1273 c1,
1274 forward,
1275 });
1276 true
1277 }
1278 _ => true,
1279 }
1280}
1281
1282fn handle_op_sneak_second<H: Host>(
1284 ed: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
1285 input: Input,
1286 op: Operator,
1287 count1: usize,
1288 c1: char,
1289 forward: bool,
1290) -> bool {
1291 match input.key {
1292 Key::Esc => true,
1293 Key::Char(c2) => {
1294 let count2 = ed.take_count();
1295 let total = count1.max(1) * count2.max(1);
1296 ed.apply_op_sneak(op, c1, c2, forward, total);
1297 true
1298 }
1299 _ => true,
1300 }
1301}