Skip to main content

VimNormalResolver

Struct VimNormalResolver 

Source
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
  • q starts/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 prefix
  • Some('"'): Waiting for register character (sentinel)
  • Some('a'..'z'): Register selected

Reset after command execution or escape.

Implementations§

Source§

impl VimNormalResolver

Source

pub const fn new() -> Self

Create a new normal mode resolver.

Source

pub fn pending_count(&self) -> Option<usize>

Get the accumulated count, if any.

§Panics

Panics if the internal lock is poisoned.

Source

pub fn pending_register(&self) -> Option<char>

Get the pending register, if any.

§Panics

Panics if the internal lock is poisoned.

Source

pub fn is_waiting_for_register(&self) -> bool

Check if waiting for register character.

Source

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
Source

pub fn accumulate_count(&self, key: &KeyEvent)

Accumulate a count digit.

Source

pub fn is_register_prefix(key: &KeyEvent) -> bool

Check if a key is the register prefix (").

Source

pub fn handle_register_char(&self, key: &KeyEvent) -> ResolveResult

Handle register character after " prefix.

Source

pub fn take_count(&self) -> Option<usize>

Take the accumulated count, clearing it.

Source

pub fn take_register(&self) -> Option<char>

Take the pending register, clearing it.

Source

pub fn push_pending_key(&self, key: KeyEvent)

Add a key to pending sequence.

Source

pub fn get_pending_keys(&self) -> KeySequence

Get a clone of pending keys for lookup.

Source

pub fn clear_state(&self)

Clear all internal state (for use from &self via interior mutability).

Source

pub fn pending_macro_op(&self) -> Option<PendingMacroOp>

Check if we’re waiting for a macro operation register.

Source

pub fn set_pending_macro(&self, op: PendingMacroOp)

Set pending macro operation.

Source

pub fn clear_pending_macro(&self)

Clear pending macro operation.

Source

pub fn is_macro_record_key(key: &KeyEvent) -> bool

Check if a key is the macro record key (q without modifiers).

Source

pub fn is_macro_play_key(key: &KeyEvent) -> bool

Check if a key is the macro play key (@ without modifiers).

Source

pub fn build_context(&self, keys: KeySequence) -> ResolveContext

Build resolve context with count and register.

Source

pub fn is_count_digit_ext(&self, key: &KeyEvent, vim: &VimSessionState) -> bool

Check if a key is a count digit (extension-based version).

Source

pub fn accumulate_count_ext(&self, key: &KeyEvent, vim: &mut VimSessionState)

Accumulate a count digit (extension-based version).

Source

pub fn handle_register_char_ext( &self, key: &KeyEvent, vim: &mut VimSessionState, ) -> ResolveResult

Handle register character after " prefix (extension-based version).

Source

pub fn build_context_ext( &self, keys: KeySequence, vim: &mut VimSessionState, ) -> ResolveContext

Build resolve context with count and register (extension-based version).

Source

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.

Source

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.

Source

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.

Source

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:

  1. This resolver: intercepts and pushes to the specific operator mode
  2. Dedicated operator resolver (delete/yank/change): captures motion, returns range
  3. 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

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl ModeKeyResolver for VimNormalResolver

Source§

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 StateVim Behavior
ExactWithLongerPending - wait for more keys (d might become dd)
ExactOnlyExecute - run the command immediately
PrefixOnlyPending - wait for more keys
NotFoundNotHandled - 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

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
StateSourceNotes
pending_countVimSessionStateFrom extensions
pending_registerVimSessionStateFrom extensions
pending_keysInternal RwLockMulti-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 mode_id(&self) -> &ModeId

Which mode this resolver handles.
Source§

fn inherits_from(&self) -> Option<&ModeId>

Optional parent mode to try if we return NotHandled. Read more
Source§

fn pending_keys(&self) -> KeySequence

Get the accumulated pending keys for this resolver. Read more
Source§

fn reset(&mut self)

Reset any internal state (counts, pending keys, etc.). Read more
Source§

fn resolve(&self, _key: &KeyEvent, _state: &mut ModeState) -> ResolveResult

👎Deprecated since 0.9.5:

Override resolve_with_keymap() instead

Process a key event in this mode’s context (legacy API). Read more
Source§

fn resolve_with_session( &self, key: &KeyEvent, state: &mut ModeState, input: &ResolveInput<'_>, _session: &mut dyn SessionApiDyn, shared_extensions: &mut ExtensionMap, client_extensions: &mut ExtensionMap, ) -> ResolveResult

Process a key event with full session API access. Read more
Source§

fn on_command_complete( &self, _session: &mut dyn SessionApiDyn, _shared_extensions: &mut ExtensionMap, _client_extensions: &mut ExtensionMap, ) -> Option<ModeTransition>

Hook called after a command executes successfully. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more