pub struct VimNormalResolver {
pub pending_register: RwLock<Option<char>>,
/* private fields */
}Expand description
Vim normal mode key resolver.
In normal mode:
- Digits 1-9 (and 0 after other digits) accumulate as count prefix
"followed by a character selects a register- Other keys are looked up in the keymap
- Operators (d, y, c) trigger transition to operator-pending mode
qstarts/stops macro recording (Epic #465 Phase 8D)@plays macro from register (Epic #465 Phase 8D)
§State Management
The resolver owns its state (counts, pending register) rather than storing it externally. This enables:
- Unit testing without full runner
- Different editing styles with different state needs
- Clean hot-reload (replace resolver, state resets)
§Example
let resolver = VimNormalResolver::new();
// Process '3' - accumulates count
let result = resolver.resolve(&key_3, &mut state);
assert!(matches!(result, ResolveResult::Pending));
// Process 'j' - executes cursor-down with count=3
let result = resolver.resolve(&key_j, &mut state);
assert!(matches!(result, ResolveResult::Execute(..)));Fields§
§pending_register: RwLock<Option<char>>Pending register selection.
None: No register prefixSome('"'): Waiting for register character (sentinel)Some('a'..'z'): Register selected
Reset after command execution or escape.
Implementations§
Source§impl VimNormalResolver
impl VimNormalResolver
Sourcepub fn pending_count(&self) -> Option<usize>
pub fn pending_count(&self) -> Option<usize>
Sourcepub fn pending_register(&self) -> Option<char>
pub fn pending_register(&self) -> Option<char>
Sourcepub fn is_waiting_for_register(&self) -> bool
pub fn is_waiting_for_register(&self) -> bool
Check if waiting for register character.
Sourcepub fn is_count_digit(&self, key: &KeyEvent) -> bool
pub fn is_count_digit(&self, key: &KeyEvent) -> bool
Check if a key is a count digit.
- First digit must be 1-9 (not 0, since 0 is a motion)
- Subsequent digits can be 0-9
Sourcepub fn accumulate_count(&self, key: &KeyEvent)
pub fn accumulate_count(&self, key: &KeyEvent)
Accumulate a count digit.
Sourcepub fn is_register_prefix(key: &KeyEvent) -> bool
pub fn is_register_prefix(key: &KeyEvent) -> bool
Check if a key is the register prefix (").
Sourcepub fn handle_register_char(&self, key: &KeyEvent) -> ResolveResult
pub fn handle_register_char(&self, key: &KeyEvent) -> ResolveResult
Handle register character after " prefix.
Sourcepub fn take_count(&self) -> Option<usize>
pub fn take_count(&self) -> Option<usize>
Take the accumulated count, clearing it.
Sourcepub fn take_register(&self) -> Option<char>
pub fn take_register(&self) -> Option<char>
Take the pending register, clearing it.
Sourcepub fn push_pending_key(&self, key: KeyEvent)
pub fn push_pending_key(&self, key: KeyEvent)
Add a key to pending sequence.
Sourcepub fn get_pending_keys(&self) -> KeySequence
pub fn get_pending_keys(&self) -> KeySequence
Get a clone of pending keys for lookup.
Sourcepub fn clear_state(&self)
pub fn clear_state(&self)
Clear all internal state (for use from &self via interior mutability).
Sourcepub fn pending_macro_op(&self) -> Option<PendingMacroOp>
pub fn pending_macro_op(&self) -> Option<PendingMacroOp>
Check if we’re waiting for a macro operation register.
Sourcepub fn set_pending_macro(&self, op: PendingMacroOp)
pub fn set_pending_macro(&self, op: PendingMacroOp)
Set pending macro operation.
Sourcepub fn clear_pending_macro(&self)
pub fn clear_pending_macro(&self)
Clear pending macro operation.
Sourcepub fn is_macro_record_key(key: &KeyEvent) -> bool
pub fn is_macro_record_key(key: &KeyEvent) -> bool
Check if a key is the macro record key (q without modifiers).
Sourcepub fn is_macro_play_key(key: &KeyEvent) -> bool
pub fn is_macro_play_key(key: &KeyEvent) -> bool
Check if a key is the macro play key (@ without modifiers).
Sourcepub fn build_context(&self, keys: KeySequence) -> ResolveContext
pub fn build_context(&self, keys: KeySequence) -> ResolveContext
Build resolve context with count and register.
Sourcepub fn is_count_digit_ext(&self, key: &KeyEvent, vim: &VimSessionState) -> bool
pub fn is_count_digit_ext(&self, key: &KeyEvent, vim: &VimSessionState) -> bool
Check if a key is a count digit (extension-based version).
Sourcepub fn accumulate_count_ext(&self, key: &KeyEvent, vim: &mut VimSessionState)
pub fn accumulate_count_ext(&self, key: &KeyEvent, vim: &mut VimSessionState)
Accumulate a count digit (extension-based version).
Sourcepub fn handle_register_char_ext(
&self,
key: &KeyEvent,
vim: &mut VimSessionState,
) -> ResolveResult
pub fn handle_register_char_ext( &self, key: &KeyEvent, vim: &mut VimSessionState, ) -> ResolveResult
Handle register character after " prefix (extension-based version).
Sourcepub fn build_context_ext(
&self,
keys: KeySequence,
vim: &mut VimSessionState,
) -> ResolveContext
pub fn build_context_ext( &self, keys: KeySequence, vim: &mut VimSessionState, ) -> ResolveContext
Build resolve context with count and register (extension-based version).
Sourcepub fn classify_find_char_command(cmd: &CommandId) -> Option<PendingCharOp>
pub fn classify_find_char_command(cmd: &CommandId) -> Option<PendingCharOp>
Classify a command as a find-char operation, if applicable.
Returns the corresponding PendingCharOp if the command is one of the
find-char commands (f, F, t, T), otherwise returns None.
This enables the resolver to intercept these commands and handle the character wait internally, rather than relying on the runner.
Sourcepub fn classify_mark_command(cmd: &CommandId) -> Option<PendingCharOp>
pub fn classify_mark_command(cmd: &CommandId) -> Option<PendingCharOp>
Classify a command as a mark operation, if applicable (#654).
Returns the corresponding PendingCharOp if the command is one of the
mark commands (m, ’, `), otherwise returns None.
Sourcepub fn is_insert_entry_command(cmd: &CommandId) -> bool
pub fn is_insert_entry_command(cmd: &CommandId) -> bool
Check if a command is an insert entry command (#577).
These commands start a change that should be recorded for dot repeat.
The recording starts here and continues through insert mode until
ExitToNormal finishes it.
Sourcepub fn classify_operator_mode(cmd: &CommandId) -> Option<ModeId>
pub fn classify_operator_mode(cmd: &CommandId) -> Option<ModeId>
Classify an operator entry command and return the target mode.
Returns the ModeId for the dedicated operator mode (DELETE, YANK, CHANGE)
if the command is an operator entry command, otherwise returns None.
§Epic #415 - Dedicated Operator Modes
Instead of routing all operators to a generic operator-pending mode,
we now push to dedicated modes where:
- The MODE itself carries operator semantics (no runtime lookup needed)
- Each resolver is focused (~300 lines) and easier to debug
- Statusline shows “DELETE” instead of “OP-PENDING”
The enter-*-operator commands in the editor module are “shell” commands (they do nothing). The real work is done by:
- This resolver: intercepts and pushes to the specific operator mode
- Dedicated operator resolver (delete/yank/change): captures motion, returns range
- Runner: executes the operator on the range
§Compile-time Safety
This function uses hard-typed comparisons against the editor module’s
CommandId constants. If those constants are renamed or removed, this
code will fail to compile rather than silently break at runtime.
Trait Implementations§
Source§impl Default for VimNormalResolver
impl Default for VimNormalResolver
Source§impl ModeKeyResolver for VimNormalResolver
impl ModeKeyResolver for VimNormalResolver
Source§fn resolve_with_keymap(
&self,
key: &KeyEvent,
_state: &mut ModeState,
input: &ResolveInput<'_>,
) -> ResolveResult
fn resolve_with_keymap( &self, key: &KeyEvent, _state: &mut ModeState, input: &ResolveInput<'_>, ) -> ResolveResult
Vim-style key resolution with keymap access.
This method queries the keymap and applies Vim policy to determine whether to execute immediately or wait for more keys.
§Vim Policy
| Lookup State | Vim Behavior |
|---|---|
ExactWithLonger | Pending - wait for more keys (d might become dd) |
ExactOnly | Execute - run the command immediately |
PrefixOnly | Pending - wait for more keys |
NotFound | NotHandled - delegate to fallback handler |
Source§fn resolve_with_extensions(
&self,
key: &KeyEvent,
_state: &mut ModeState,
input: &ResolveInput<'_>,
_shared_extensions: &mut ExtensionMap,
client_extensions: &mut ExtensionMap,
) -> ResolveResult
fn resolve_with_extensions( &self, key: &KeyEvent, _state: &mut ModeState, input: &ResolveInput<'_>, _shared_extensions: &mut ExtensionMap, client_extensions: &mut ExtensionMap, ) -> ResolveResult
Vim-style key resolution with keymap AND session extensions access.
This is the new architecture (Epic #385) that uses VimSessionState from
extensions instead of the runner’s AppState. The resolver owns the vim
policy, the runner is pure mechanism.
§State Management
| State | Source | Notes |
|---|---|---|
pending_count | VimSessionState | From extensions |
pending_register | VimSessionState | From extensions |
pending_keys | Internal RwLock | Multi-key sequence |
Note: For backward compatibility, we still use internal pending_keys.
Once all resolvers are migrated, these can move to VimSessionState too.
Source§fn inherits_from(&self) -> Option<&ModeId>
fn inherits_from(&self) -> Option<&ModeId>
NotHandled. Read moreSource§fn pending_keys(&self) -> KeySequence
fn pending_keys(&self) -> KeySequence
Source§fn resolve(&self, _key: &KeyEvent, _state: &mut ModeState) -> ResolveResult
fn resolve(&self, _key: &KeyEvent, _state: &mut ModeState) -> ResolveResult
Override resolve_with_keymap() instead