#[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>
impl<A> SequenceKeymap<A>
Sourcepub fn new() -> Self
pub fn new() -> Self
Creates an empty sequence map.
Examples found in repository?
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}Sourcepub fn bind(
&mut self,
sequence: impl IntoIterator<Item = KeyInput>,
action: A,
) -> Result<Option<A>, SeqBindError>
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::Emptyifsequenceyields no keys.SeqBindError::PrefixShadowif 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?
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}Sourcepub fn lookup(&self, keys: &[KeyInput]) -> Match<'_, A>
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::Exact—keysis a complete binding; the caller should fire it and clear the buffer.Match::Prefix—keysis a proper prefix of one or more bindings; the caller should keep buffering (and, for timed bindings, run its timer).Match::NoMatch—keysis not on any binding path; the caller should clear the buffer and handle the keys itself (e.g. pass through).
Examples found in repository?
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}Sourcepub fn bindings(&self) -> impl Iterator<Item = (Vec<KeyInput>, &A)>
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.
Sourcepub fn continuations(
&self,
prefix: &[KeyInput],
) -> impl Iterator<Item = (KeyInput, Continuation<'_, A>)>
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>
impl<A: Clone> Clone for SequenceKeymap<A>
Source§fn clone(&self) -> SequenceKeymap<A>
fn clone(&self) -> SequenceKeymap<A>
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more