1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//! Composer vim Normal-mode keybindings.
use crate::tui::app::{App, VimMode};
/// Handle a plain character key press when the composer is in vim Normal mode.
///
/// Implements the core set of normal-mode bindings:
/// - `h` / `l` — left / right by character
/// - `j` / `k` — down / up by logical line (falls back to prev/next history)
/// - `w` / `b` — word forward / backward
/// - `0` / `$` — line start / end
/// - `x` — delete character under cursor
/// - `d` (×2) — delete current line (`dd`)
/// - `i` — enter Insert before cursor
/// - `a` — enter Insert after cursor
/// - `o` — open new line below and enter Insert
/// - `v` — enter Visual mode
/// - `G` — move to end of buffer
pub(super) fn handle_vim_normal_key(app: &mut App, c: char) {
// Handle pending `d` (waiting for second `d` to complete `dd`).
if app.composer.vim_pending_d {
app.composer.vim_pending_d = false;
if c == 'd' {
app.vim_delete_line();
}
// Any other key cancels the pending operator.
return;
}
match c {
'h' => app.move_cursor_left(),
'l' => app.move_cursor_right(),
'j' => app.vim_move_down(),
'k' => app.vim_move_up(),
'w' => app.vim_move_word_forward(),
'b' => app.vim_move_word_backward(),
'0' => app.vim_move_line_start(),
'$' => app.vim_move_line_end(),
'x' => app.vim_delete_char_under_cursor(),
'd' => {
// Start the `dd` operator sequence.
app.composer.vim_pending_d = true;
}
'i' => app.vim_enter_insert(),
'a' => app.vim_enter_append(),
'o' => app.vim_open_line_below(),
'v' => {
app.composer.vim_mode = VimMode::Visual;
app.needs_redraw = true;
}
'G' => app.move_cursor_end(),
_ => {
// Unknown normal-mode key — silently ignored in Normal mode.
}
}
}