Skip to main content

SequenceKeymap

Struct SequenceKeymap 

Source
#[non_exhaustive]
pub struct SequenceKeymap<A> { /* private fields */ }
Expand description

A table mapping sequences of normalized KeyInputs to a caller-defined action A.

This is the sequence-level analogue of keymap_core::Keymap: state-free, with the action type A brought by the caller. It is prefix-free by construction — see SequenceKeymap::bind and the crate-level docs — so lookup never needs a timeout to disambiguate.

Implementations§

Source§

impl<A> SequenceKeymap<A>

Source

pub fn new() -> Self

Creates an empty sequence map.

Examples found in repository?
examples/leader_sequence.rs (line 40)
38fn untimed() {
39    println!("== untimed ==");
40    let mut map = SequenceKeymap::new();
41    map.bind([ctrl('x'), ctrl('s')], Action::Save).unwrap();
42    map.bind([ctrl('x'), ctrl('c')], Action::Quit).unwrap();
43    map.bind([plain('g'), plain('g')], Action::GotoTop).unwrap();
44
45    // A stream a terminal might deliver: a completed save, an abandoned prefix
46    // (`ctrl+x` then an unrelated key), then `g g`.
47    let stream = [
48        ctrl('x'),
49        ctrl('s'),
50        ctrl('x'),
51        plain('z'),
52        plain('g'),
53        plain('g'),
54    ];
55
56    let mut pending: Vec<KeyInput> = Vec::new();
57    for key in stream {
58        pending.push(key);
59        match map.lookup(&pending) {
60            Match::Exact(action) => {
61                println!("{} -> fire {action:?}", render(&pending));
62                pending.clear();
63            }
64            Match::Prefix => {
65                println!("{} -> prefix, waiting", render(&pending));
66            }
67            Match::NoMatch => {
68                println!("{} -> no binding, passing through", render(&pending));
69                pending.clear();
70            }
71        }
72    }
73}
74
75/// `jj`-style time window using [`TimedPending`].
76///
77/// [`TimedPending`] bundles the expiry check, flush, and new-key processing
78/// into a single `feed` call. The library still holds no clock — `now` is
79/// caller-supplied data (a `base + offset` pair here, so the demo is
80/// deterministic and never flaky).
81///
82/// Note what this demo does *not* do: real vim `jj` also binds `j` on its own
83/// (a literal cursor move). That needs both `j` *and* `[j, j]` bound — which
84/// the prefix-free invariant forbids (`bind` rejects it with `PrefixShadow`).
85/// Here `j` alone is unbound, so the timeout is the *only* caller policy needed.
86fn timed() {
87    const WINDOW: Duration = Duration::from_millis(500);
88
89    println!("== timed (jj, via TimedPending) ==");
90    let mut map = SequenceKeymap::new();
91    map.bind([plain('j'), plain('j')], Action::NormalMode)
92        .unwrap();
93
94    // Deterministic timestamps: base + offset (no real sleep / Instant::now).
95    let base = Instant::now();
96    let stream = [
97        (plain('j'), 0u64),
98        (plain('j'), 120), // 120ms gap: quick, completes `jj`
99        (plain('j'), 900),
100        (plain('j'), 1700), // 800ms gap: too slow, expiry fires
101    ];
102
103    let mut pending = TimedPending::new();
104    for (key, ms) in stream {
105        let now = base + Duration::from_millis(ms);
106        let result = pending.feed(&map, key, now, WINDOW);
107
108        // If a previous prefix expired, report its keys as literals first.
109        if let Some(expired) = result.expired {
110            println!(
111                "{} @ +{ms}ms -> idle timeout, flushed as literals",
112                render(&expired),
113            );
114        }
115
116        match result.step {
117            Step::Fired(action) => println!("{key} @ +{ms}ms -> fire {action:?}"),
118            Step::Pending => println!("{key} @ +{ms}ms -> prefix, waiting (window {WINDOW:?})"),
119            Step::PassThrough(keys) => {
120                println!("{} @ +{ms}ms -> no binding, passing through", render(&keys));
121            }
122        }
123    }
124
125    // The stream ended mid-prefix. Drain as literals (what an idle timer does).
126    let dangling = pending.flush();
127    if !dangling.is_empty() {
128        println!(
129            "{} -> pending at end; flushed as literals",
130            render(&dangling)
131        );
132    }
133}
Source

pub fn is_empty(&self) -> bool

Returns true if no sequences are bound.

Source

pub fn bind( &mut self, sequence: impl IntoIterator<Item = KeyInput>, action: A, ) -> Result<Option<A>, SeqBindError>

Binds a key sequence to action.

Re-binding the exact same sequence overwrites and returns the previous action, mirroring keymap_core::Keymap::bind.

§Errors
  • SeqBindError::Empty if sequence yields no keys.
  • SeqBindError::PrefixShadow if the new sequence would be a proper prefix of an existing binding, or an existing binding would be a proper prefix of it. Either case is unresolvable without a timeout (see the crate docs), so it is rejected rather than silently shadowed; the map is left unchanged.
Examples found in repository?
examples/leader_sequence.rs (line 41)
38fn untimed() {
39    println!("== untimed ==");
40    let mut map = SequenceKeymap::new();
41    map.bind([ctrl('x'), ctrl('s')], Action::Save).unwrap();
42    map.bind([ctrl('x'), ctrl('c')], Action::Quit).unwrap();
43    map.bind([plain('g'), plain('g')], Action::GotoTop).unwrap();
44
45    // A stream a terminal might deliver: a completed save, an abandoned prefix
46    // (`ctrl+x` then an unrelated key), then `g g`.
47    let stream = [
48        ctrl('x'),
49        ctrl('s'),
50        ctrl('x'),
51        plain('z'),
52        plain('g'),
53        plain('g'),
54    ];
55
56    let mut pending: Vec<KeyInput> = Vec::new();
57    for key in stream {
58        pending.push(key);
59        match map.lookup(&pending) {
60            Match::Exact(action) => {
61                println!("{} -> fire {action:?}", render(&pending));
62                pending.clear();
63            }
64            Match::Prefix => {
65                println!("{} -> prefix, waiting", render(&pending));
66            }
67            Match::NoMatch => {
68                println!("{} -> no binding, passing through", render(&pending));
69                pending.clear();
70            }
71        }
72    }
73}
74
75/// `jj`-style time window using [`TimedPending`].
76///
77/// [`TimedPending`] bundles the expiry check, flush, and new-key processing
78/// into a single `feed` call. The library still holds no clock — `now` is
79/// caller-supplied data (a `base + offset` pair here, so the demo is
80/// deterministic and never flaky).
81///
82/// Note what this demo does *not* do: real vim `jj` also binds `j` on its own
83/// (a literal cursor move). That needs both `j` *and* `[j, j]` bound — which
84/// the prefix-free invariant forbids (`bind` rejects it with `PrefixShadow`).
85/// Here `j` alone is unbound, so the timeout is the *only* caller policy needed.
86fn timed() {
87    const WINDOW: Duration = Duration::from_millis(500);
88
89    println!("== timed (jj, via TimedPending) ==");
90    let mut map = SequenceKeymap::new();
91    map.bind([plain('j'), plain('j')], Action::NormalMode)
92        .unwrap();
93
94    // Deterministic timestamps: base + offset (no real sleep / Instant::now).
95    let base = Instant::now();
96    let stream = [
97        (plain('j'), 0u64),
98        (plain('j'), 120), // 120ms gap: quick, completes `jj`
99        (plain('j'), 900),
100        (plain('j'), 1700), // 800ms gap: too slow, expiry fires
101    ];
102
103    let mut pending = TimedPending::new();
104    for (key, ms) in stream {
105        let now = base + Duration::from_millis(ms);
106        let result = pending.feed(&map, key, now, WINDOW);
107
108        // If a previous prefix expired, report its keys as literals first.
109        if let Some(expired) = result.expired {
110            println!(
111                "{} @ +{ms}ms -> idle timeout, flushed as literals",
112                render(&expired),
113            );
114        }
115
116        match result.step {
117            Step::Fired(action) => println!("{key} @ +{ms}ms -> fire {action:?}"),
118            Step::Pending => println!("{key} @ +{ms}ms -> prefix, waiting (window {WINDOW:?})"),
119            Step::PassThrough(keys) => {
120                println!("{} @ +{ms}ms -> no binding, passing through", render(&keys));
121            }
122        }
123    }
124
125    // The stream ended mid-prefix. Drain as literals (what an idle timer does).
126    let dangling = pending.flush();
127    if !dangling.is_empty() {
128        println!(
129            "{} -> pending at end; flushed as literals",
130            render(&dangling)
131        );
132    }
133}
Source

pub fn lookup(&self, keys: &[KeyInput]) -> Match<'_, A>

Resolves the keys pressed so far against the table.

keys is the caller’s pending buffer. The result is one of:

  • Match::Exactkeys is a complete binding; the caller should fire it and clear the buffer.
  • Match::Prefixkeys is a proper prefix of one or more bindings; the caller should keep buffering (and, for timed bindings, run its timer).
  • Match::NoMatchkeys is not on any binding path; the caller should clear the buffer and handle the keys itself (e.g. pass through).
Examples found in repository?
examples/leader_sequence.rs (line 59)
38fn untimed() {
39    println!("== untimed ==");
40    let mut map = SequenceKeymap::new();
41    map.bind([ctrl('x'), ctrl('s')], Action::Save).unwrap();
42    map.bind([ctrl('x'), ctrl('c')], Action::Quit).unwrap();
43    map.bind([plain('g'), plain('g')], Action::GotoTop).unwrap();
44
45    // A stream a terminal might deliver: a completed save, an abandoned prefix
46    // (`ctrl+x` then an unrelated key), then `g g`.
47    let stream = [
48        ctrl('x'),
49        ctrl('s'),
50        ctrl('x'),
51        plain('z'),
52        plain('g'),
53        plain('g'),
54    ];
55
56    let mut pending: Vec<KeyInput> = Vec::new();
57    for key in stream {
58        pending.push(key);
59        match map.lookup(&pending) {
60            Match::Exact(action) => {
61                println!("{} -> fire {action:?}", render(&pending));
62                pending.clear();
63            }
64            Match::Prefix => {
65                println!("{} -> prefix, waiting", render(&pending));
66            }
67            Match::NoMatch => {
68                println!("{} -> no binding, passing through", render(&pending));
69                pending.clear();
70            }
71        }
72    }
73}
Source

pub fn bindings(&self) -> impl Iterator<Item = (Vec<KeyInput>, &A)>

Enumerates every bound sequence as a (path, action) pair — the sequence-level dual of keymap_core::Keymap::iter.

Each yielded Vec<KeyInput> is a complete bound sequence (a leaf path); by the prefix-free invariant it is never a partial prefix. Unlike Keymap::iter, which can borrow its stored key, a trie path is reconstructed during traversal, so each item owns its Vec and borrows only the action.

Order is unspecified (the trie’s children live in a HashMap); collect and sort caller-side for stable output. This is the data source for listing, search, and serialization on top of keymap-seq.

Source

pub fn continuations( &self, prefix: &[KeyInput], ) -> impl Iterator<Item = (KeyInput, Continuation<'_, A>)>

Enumerates the keys that may immediately follow prefix, for rendering a which-key-style menu of what can be pressed next.

Each item is a KeyInput that extends prefix by one, paired with what pressing it does: complete a binding (Continuation::Action, carrying the action) or open a deeper prefix (Continuation::Prefix).

The iterator is empty when prefix does not name an internal node — whether it is off the tree, already completes a binding (a leaf has no children), or the map is empty. continuations deliberately does not re-encode that distinction; call lookup to tell those apart.

Order is unspecified (the children live in a HashMap); collect and sort caller-side for a stable menu.

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

#[derive(Debug, PartialEq)]
enum Action { Save, Quit }

let k = |c| KeyInput::new(Key::Char(c), Modifiers::NONE);
let mut map = SequenceKeymap::new();
map.bind([k('a'), k('b')], Action::Save).unwrap();
map.bind([k('c')], Action::Quit).unwrap();

// From the root: `a` opens a deeper prefix, `c` completes a binding.
let mut next: Vec<_> = map.continuations(&[]).collect();
next.sort_by_key(|(key, _)| key.to_string());
assert_eq!(
    next,
    vec![
        (k('a'), Continuation::Prefix),
        (k('c'), Continuation::Action(&Action::Quit)),
    ],
);

// A completed binding has no continuations.
assert_eq!(map.continuations(&[k('c')]).count(), 0);

Trait Implementations§

Source§

impl<A: Clone> Clone for SequenceKeymap<A>

Source§

fn clone(&self) -> SequenceKeymap<A>

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<A: Debug> Debug for SequenceKeymap<A>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<A> Default for SequenceKeymap<A>

Source§

fn default() -> Self

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

Auto Trait Implementations§

§

impl<A> Freeze for SequenceKeymap<A>
where A: Freeze,

§

impl<A> RefUnwindSafe for SequenceKeymap<A>
where A: RefUnwindSafe,

§

impl<A> Send for SequenceKeymap<A>
where A: Send,

§

impl<A> Sync for SequenceKeymap<A>
where A: Sync,

§

impl<A> Unpin for SequenceKeymap<A>
where A: Unpin,

§

impl<A> UnsafeUnpin for SequenceKeymap<A>
where A: UnsafeUnpin,

§

impl<A> UnwindSafe for SequenceKeymap<A>
where A: UnwindSafe,

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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.