hjkl-vim 0.2.0

Vim modal state types and grammar primitives for the hjkl editor stack. Pre-1.0 churn.
Documentation
/// Pending-state machine for second-key chords. The umbrella stores
/// `Option<PendingState>`; when `Some`, it routes keys through `step`
/// instead of the keymap trie.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PendingState {
    Replace { count: usize },
    // 2b–2e variants land later.
}

/// One step of the reducer.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Outcome {
    /// Need more keys — keep accumulating with new state.
    Wait(PendingState),
    /// Run this engine command, then clear pending.
    Commit(crate::cmd::EngineCmd),
    /// Cancel pending (Esc, invalid char, etc.). No engine call.
    Cancel,
    /// Pending state didn't consume this key — host should route it
    /// normally (e.g. modifier-only key). Pending state stays alive.
    Forward,
}

/// `Key` is intentionally minimal — hjkl-vim should not depend on
/// crossterm. Hosts translate their native keys into this shape.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Key {
    Char(char),
    Esc,
    Enter,
    Backspace,
    Tab,
    // Add more variants only as later chunks require them.
}

pub fn step(state: PendingState, key: Key) -> Outcome {
    match state {
        PendingState::Replace { count } => match key {
            Key::Esc => Outcome::Cancel,
            Key::Char(ch) => Outcome::Commit(crate::cmd::EngineCmd::ReplaceChar { ch, count }),
            Key::Enter => Outcome::Commit(crate::cmd::EngineCmd::ReplaceChar { ch: '\n', count }),
            _ => Outcome::Cancel,
        },
    }
}