reovim_module_vim/resolvers/commandline.rs
1//! Vim command-line mode key resolver.
2//!
3//! In command-line mode (`:`, `/`, `?`), typed characters accumulate in
4//! the command-line buffer. Enter executes, Escape cancels.
5
6use {
7 reovim_driver_input::{
8 KeyCode, KeyEvent, KeyLookupState, KeySequence, ModeKeyResolver, ModeState, Modifiers,
9 ResolveContext, ResolveInput, ResolveResult,
10 },
11 reovim_kernel::api::v1::ModeId,
12 reovim_module_cmdline::CmdlineState,
13};
14
15use crate::modes::VimMode;
16
17/// Vim command-line mode key resolver.
18///
19/// Handles input for `:` (Ex commands), `/` (forward search), and `?` (backward search).
20/// Characters are accumulated in the command-line buffer until Enter or Escape.
21///
22/// # Behavior
23///
24/// - Printable characters → `InsertChar` (goes to cmdline buffer)
25/// - Enter → `NotHandled` (keybinding executes command/search)
26/// - Escape → `NotHandled` (keybinding cancels)
27/// - Backspace → `NotHandled` (keybinding deletes char)
28pub struct VimCommandLineResolver {
29 mode_id: ModeId,
30}
31
32impl VimCommandLineResolver {
33 /// Create a new command-line mode resolver.
34 #[must_use]
35 pub const fn new() -> Self {
36 Self {
37 mode_id: VimMode::COMMANDLINE_ID,
38 }
39 }
40
41 /// Check if a key should insert a character into the command-line buffer.
42 const fn is_insertable(key: &KeyEvent) -> Option<char> {
43 // Only consider keys without control/alt modifiers for insertion
44 if key.modifiers.contains(Modifiers::CTRL) || key.modifiers.contains(Modifiers::ALT) {
45 return None;
46 }
47
48 match key.code {
49 KeyCode::Char(c) => Some(c),
50 // Space is insertable
51 // Tab could be for completion (future)
52 // Enter is NOT insertable - it executes
53 _ => None,
54 }
55 }
56}
57
58impl Default for VimCommandLineResolver {
59 fn default() -> Self {
60 Self::new()
61 }
62}
63
64#[cfg_attr(coverage_nightly, coverage(off))]
65impl ModeKeyResolver for VimCommandLineResolver {
66 fn resolve_with_keymap(
67 &self,
68 key: &KeyEvent,
69 _state: &mut ModeState,
70 input: &ResolveInput<'_>,
71 ) -> ResolveResult {
72 // Check for insertable character first
73 // Route to CmdlineState extension (#482 - Generic Input Target)
74 if let Some(c) = Self::is_insertable(key) {
75 return ResolveResult::insert_char_to::<CmdlineState>(c);
76 }
77
78 // For non-insertable keys (Escape, Enter, Backspace, etc.), look up in keymap
79 let mut keys = KeySequence::new();
80 keys.push(*key);
81 let lookup_state = input.keymap.query(input.mode, &keys);
82
83 match lookup_state {
84 KeyLookupState::ExactWithLonger { exact, .. } | KeyLookupState::ExactOnly(exact) => {
85 // Execute the command
86 ResolveResult::Execute(exact, ResolveContext::default())
87 }
88 KeyLookupState::PrefixOnly => {
89 // Wait for more keys
90 ResolveResult::Pending
91 }
92 KeyLookupState::NotFound => {
93 // No binding found
94 ResolveResult::NotHandled
95 }
96 }
97 }
98
99 fn mode_id(&self) -> &ModeId {
100 &self.mode_id
101 }
102
103 fn inherits_from(&self) -> Option<&ModeId> {
104 // Command-line mode doesn't inherit from other modes
105 None
106 }
107
108 fn reset(&mut self) {
109 // No state to reset
110 }
111}