Skip to main content

Crate keymap_seq

Crate keymap_seq 

Source
Expand description

§keymap-seq

Multi-key sequence resolution layered on top of keymap-core’s single-chord vocabulary: leader trees (space w), emacs-style prefix chords (ctrl+x ctrl+s), and the like.

Like keymap-core, this crate is state-free. A SequenceKeymap answers “given the keys pressed so far, is this an exact binding, a prefix of a longer one, or neither?” via SequenceKeymap::lookup, and “what keys may follow this prefix?” via SequenceKeymap::continuations (for which-key-style menus). The pending key buffer — the keys typed since the last resolution — lives in the caller, exactly as keymap_core::resolve_layered keeps the active-layer choice caller-side. The library holds no clock and no mutable session state.

§The prefix-free invariant

A binding may not be a proper prefix of another binding. With timeouts out of scope (see below), j bound and j j bound would make the press of j undecidable — fire j now, or wait for the second j? Only a clock or a lookahead could break that tie, and this crate has neither. So SequenceKeymap::bind rejects such a pair with SeqBindError::PrefixShadow, which keeps lookup a total, deterministic, time-free function: a prefix is always Match::Prefix, a leaf is always Match::Exact, anything off the tree is Match::NoMatch.

§Timeouts and the caller loop

jj-style “press twice quickly” bindings need a time window, which is a property of the caller’s event loop (when to read the clock, blocking vs poll-with-deadline), not of this table. The intended pattern keeps the library time-free. PendingSequence folds the buffer bookkeeping below into a small caller-owned helper (feed to push a key, flush for the idle timeout); the raw lookup loop it wraps is:

use keymap_core::{Key, KeyInput, Modifiers};
use keymap_seq::{Match, SequenceKeymap};

enum Action { Save }

let cx = KeyInput::new(Key::Char('x'), Modifiers::CTRL);
let cs = KeyInput::new(Key::Char('s'), Modifiers::CTRL);
let mut map = SequenceKeymap::new();
map.bind([cx, cs], Action::Save).unwrap();

// The caller owns the pending buffer.
let mut pending: Vec<KeyInput> = Vec::new();
for key in [cx, cs] {
    pending.push(key);
    match map.lookup(&pending) {
        Match::Exact(action) => {
            assert_eq!(action, &Action::Save);
            pending.clear(); // fired — reset for the next sequence
        }
        Match::Prefix => { /* keep waiting (and, for `jj`, start a timer) */ }
        Match::NoMatch => { pending.clear(); /* replay/pass through */ }
    }
}

§Runnable example

cargo run -p keymap-seq --example leader_sequence runs the buffer-and-flush loop above, including a timed jj-style window demo using TimedPending (source).

Structs§

PendingSequence
A caller-owned pending buffer for resolving multi-key sequences, with the clear/retain bookkeeping folded in.
SequenceKeymap
A table mapping sequences of normalized KeyInputs to a caller-defined action A.
TimedPending
A PendingSequence with a time-window expiry gate.
TimedStep
The result of a single TimedPending::feed call.

Enums§

Continuation
What pressing one more key after a prefix does, as reported by SequenceKeymap::continuations.
Match
The outcome of resolving a pending key buffer with SequenceKeymap::lookup.
SeqBindError
Why a SequenceKeymap::bind call was rejected.
Step
What a PendingSequence::feed resolved to — the caller’s next action.