keymap_seq/progress.rs
1//! Caller-owned progress through a [`SequenceKeymap`].
2
3use keymap_core::KeyInput;
4
5use crate::{Match, SequenceKeymap};
6
7/// A caller-owned pending buffer for resolving multi-key sequences, with the
8/// clear/retain bookkeeping folded in.
9///
10/// [`SequenceKeymap::lookup`] answers "exact / prefix / miss" for the keys so
11/// far, but leaves the *caller* to push each key, clear the buffer on a hit or a
12/// miss, and keep it on a prefix — the mechanical, easy-to-get-wrong half of
13/// `keymap-seq`'s "the caller owns the buffer" contract (see the crate docs and
14/// the `leader_sequence` example). `PendingSequence` owns exactly that buffer and
15/// that bookkeeping, and nothing else: like the rest of `keymap-rs` it holds **no
16/// clock**.
17///
18/// The idle window a `jj`-style binding needs — "abandon a half-typed prefix if
19/// the next key is too slow" — stays the caller's policy. The library cannot see
20/// time, so it cannot decide a prefix was abandoned; the caller runs its own
21/// timer and calls [`flush`](Self::flush) when that timer fires. Making the idle
22/// flush an explicit method (rather than a buffer the caller forgets to drain) is
23/// the point: it is the one step the old example could only describe in a comment.
24///
25/// It is generic over no lifetime — the [`SequenceKeymap`] is passed to
26/// [`feed`](Self::feed) per call rather than borrowed for the buffer's lifetime —
27/// so a `PendingSequence` can live as a field beside the map it reads. Holding a
28/// borrow would make that a self-referential struct, which is exactly the layout
29/// a modal-UI caller wants (`struct App { map: SequenceKeymap<A>, pending:
30/// PendingSequence }`).
31///
32/// ```
33/// use keymap_core::{Key, KeyInput, Modifiers};
34/// use keymap_seq::{PendingSequence, SequenceKeymap, Step};
35///
36/// #[derive(Debug, PartialEq)]
37/// enum Action {
38/// Save,
39/// }
40///
41/// let cx = KeyInput::new(Key::Char('x'), Modifiers::CTRL);
42/// let cs = KeyInput::new(Key::Char('s'), Modifiers::CTRL);
43/// let mut map = SequenceKeymap::new();
44/// map.bind([cx, cs], Action::Save).unwrap();
45///
46/// let mut pending = PendingSequence::new();
47/// assert!(matches!(pending.feed(&map, cx), Step::Pending));
48/// assert!(matches!(pending.feed(&map, cs), Step::Fired(&Action::Save)));
49/// // Fired cleared the buffer, so the next key starts a fresh sequence.
50/// assert!(pending.is_empty());
51/// ```
52#[derive(Debug, Clone, Default)]
53pub struct PendingSequence {
54 pending: Vec<KeyInput>,
55}
56
57impl PendingSequence {
58 /// Creates an empty pending buffer.
59 #[must_use]
60 pub fn new() -> Self {
61 PendingSequence::default()
62 }
63
64 /// Pushes `key` onto the buffer, resolves the buffer against `map`, and
65 /// returns what the caller should do — applying the clear/retain bookkeeping
66 /// itself.
67 ///
68 /// This is the [`SequenceKeymap::lookup`] trichotomy turned into an action:
69 /// - [`Match::Exact`] → [`Step::Fired`], and the buffer is **cleared** (the
70 /// sequence completed; the next `feed` starts fresh).
71 /// - [`Match::Prefix`] → [`Step::Pending`], and the buffer is **kept** (more
72 /// keys may follow; (re)start your idle timer).
73 /// - [`Match::NoMatch`] → [`Step::PassThrough`] carrying the **whole** buffer
74 /// (every key buffered so far, in order), and the buffer is **cleared**.
75 ///
76 /// The whole buffer — not just `key` — is returned on a miss because the
77 /// earlier keys were held on the promise of a longer binding that did not
78 /// materialise, so they are now the caller's to handle (replay, pass to the
79 /// PTY, …). A key that would have started its own sequence but arrived
80 /// mid-prefix is passed through with the rest, not re-fed: "is this key a
81 /// fresh start or the tail of an abandoned prefix?" is lookahead policy, which
82 /// stays the caller's (the same reason the library has no clock).
83 pub fn feed<'a, A>(&mut self, map: &'a SequenceKeymap<A>, key: KeyInput) -> Step<'a, A> {
84 self.pending.push(key);
85 match map.lookup(&self.pending) {
86 Match::Exact(action) => {
87 self.pending.clear();
88 Step::Fired(action)
89 }
90 Match::Prefix => Step::Pending,
91 Match::NoMatch => Step::PassThrough(std::mem::take(&mut self.pending)),
92 }
93 }
94
95 /// Drains the pending buffer, returning the keys held in it (empty if none),
96 /// and leaves the buffer empty.
97 ///
98 /// This is the **idle flush**: call it when your timer says a pending prefix
99 /// was abandoned (no further key arrived in time). The returned keys are
100 /// literals the caller now owns — the dangling `j` of a half-typed `jj` that
101 /// must still reach the application as a plain `j`. It takes no time argument
102 /// and consults no window: the decision that the prefix is stale is the
103 /// caller's; `flush` only performs the drain the caller decided on. Flushing
104 /// an empty buffer is a no-op that returns an empty `Vec`.
105 pub fn flush(&mut self) -> Vec<KeyInput> {
106 std::mem::take(&mut self.pending)
107 }
108
109 /// The keys buffered so far (a proper prefix of some binding), in press order.
110 #[must_use]
111 pub fn pending(&self) -> &[KeyInput] {
112 &self.pending
113 }
114
115 /// Returns `true` if no keys are buffered.
116 #[must_use]
117 pub fn is_empty(&self) -> bool {
118 self.pending.is_empty()
119 }
120}
121
122/// What a [`PendingSequence::feed`] resolved to — the caller's next action.
123///
124/// `Step` is the *action-oriented* dual of [`Match`]: where `Match` states a fact
125/// about the table (exact / prefix / miss), `Step` states what the caller does
126/// (fire it / keep waiting / take these keys back). The trichotomy is the same
127/// closed set — `feed` is a thin translation of the [`SequenceKeymap::lookup`]
128/// result into buffer operations, and a pure trie lookup has no fourth outcome —
129/// so, like `Match`, this enum is **not** `#[non_exhaustive]`: match all three
130/// arms and let the compiler check coverage. (`flush`'s drain is a `Vec`, not a
131/// `Step`, so it adds no variant.)
132#[derive(Debug, PartialEq, Eq)]
133#[must_use]
134pub enum Step<'a, A> {
135 /// The buffer completed a binding, resolving to this action; the buffer has
136 /// been cleared.
137 Fired(&'a A),
138 /// The buffer is a proper prefix of a longer binding; it has been kept. Keep
139 /// reading (and, for a timed binding, (re)start your idle timer).
140 Pending,
141 /// The buffer is not on any binding path; it has been cleared and its keys —
142 /// every key buffered so far, in order — are handed back for the caller to
143 /// handle.
144 PassThrough(Vec<KeyInput>),
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use keymap_core::{Key, Modifiers};
151
152 #[derive(Debug, Clone, PartialEq)]
153 enum Action {
154 Save,
155 Quit,
156 Top,
157 Solo,
158 }
159
160 fn ctrl(c: char) -> KeyInput {
161 KeyInput::new(Key::Char(c), Modifiers::CTRL)
162 }
163
164 fn plain(c: char) -> KeyInput {
165 KeyInput::new(Key::Char(c), Modifiers::NONE)
166 }
167
168 /// `ctrl+x ctrl+s` -> Save, `ctrl+x ctrl+c` -> Quit, `g g` -> Top, `q` -> Solo.
169 fn sample() -> SequenceKeymap<Action> {
170 let mut map = SequenceKeymap::new();
171 map.bind([ctrl('x'), ctrl('s')], Action::Save).unwrap();
172 map.bind([ctrl('x'), ctrl('c')], Action::Quit).unwrap();
173 map.bind([plain('g'), plain('g')], Action::Top).unwrap();
174 map.bind([plain('q')], Action::Solo).unwrap();
175 map
176 }
177
178 #[test]
179 fn prefix_then_exact_fires_and_clears() {
180 let map = sample();
181 let mut p = PendingSequence::new();
182 assert_eq!(p.feed(&map, ctrl('x')), Step::Pending);
183 assert_eq!(p.pending(), &[ctrl('x')]);
184 assert_eq!(p.feed(&map, ctrl('s')), Step::Fired(&Action::Save));
185 // Fired cleared the buffer.
186 assert!(p.is_empty());
187 }
188
189 #[test]
190 fn clear_after_exact_lets_the_next_sequence_start_fresh() {
191 // The "clear 忘れ" case, asserted through the *next* transition: a third
192 // `g` after a completed `g g` must be a fresh Prefix, not a phantom Exact.
193 let map = sample();
194 let mut p = PendingSequence::new();
195 assert_eq!(p.feed(&map, plain('g')), Step::Pending);
196 assert_eq!(p.feed(&map, plain('g')), Step::Fired(&Action::Top));
197 assert_eq!(p.feed(&map, plain('g')), Step::Pending);
198 assert_eq!(p.pending(), &[plain('g')]);
199 }
200
201 #[test]
202 fn passthrough_returns_the_whole_buffer_not_just_the_last_key() {
203 // An abandoned prefix (`ctrl+x` then an unrelated key) hands back both
204 // keys, in order — dropping the held `ctrl+x` would silently eat input.
205 let map = sample();
206 let mut p = PendingSequence::new();
207 assert_eq!(p.feed(&map, ctrl('x')), Step::Pending);
208 assert_eq!(
209 p.feed(&map, plain('z')),
210 Step::PassThrough(vec![ctrl('x'), plain('z')])
211 );
212 assert!(p.is_empty());
213 }
214
215 #[test]
216 fn length_one_sequence_fires_on_the_first_key() {
217 let map = sample();
218 let mut p = PendingSequence::new();
219 assert_eq!(p.feed(&map, plain('q')), Step::Fired(&Action::Solo));
220 assert!(p.is_empty());
221 }
222
223 #[test]
224 fn empty_map_passes_every_key_through() {
225 let map: SequenceKeymap<Action> = SequenceKeymap::new();
226 let mut p = PendingSequence::new();
227 // No binding can exist, so a key is never a Prefix to wait on.
228 assert_eq!(
229 p.feed(&map, plain('a')),
230 Step::PassThrough(vec![plain('a')])
231 );
232 assert!(p.is_empty());
233 }
234
235 #[test]
236 fn abandoned_prefix_does_not_contaminate_a_fresh_sequence() {
237 // The example's own stream: an abandoned `ctrl+x z`, then a clean `g g`.
238 let map = sample();
239 let mut p = PendingSequence::new();
240 assert_eq!(p.feed(&map, ctrl('x')), Step::Pending);
241 assert_eq!(
242 p.feed(&map, plain('z')),
243 Step::PassThrough(vec![ctrl('x'), plain('z')])
244 );
245 assert_eq!(p.feed(&map, plain('g')), Step::Pending);
246 assert_eq!(p.feed(&map, plain('g')), Step::Fired(&Action::Top));
247 assert!(p.is_empty());
248 }
249
250 #[test]
251 fn a_key_that_could_start_a_sequence_is_passed_through_mid_prefix() {
252 // `a b` and `c` bound; `a` then `c` makes `[a, c]` a miss — `c` is handed
253 // back *with* `a`, not re-fed as a fresh start (lookahead is the caller's).
254 let mut map = SequenceKeymap::new();
255 map.bind([plain('a'), plain('b')], Action::Save).unwrap();
256 map.bind([plain('c')], Action::Solo).unwrap();
257 let mut p = PendingSequence::new();
258 assert_eq!(p.feed(&map, plain('a')), Step::Pending);
259 assert_eq!(
260 p.feed(&map, plain('c')),
261 Step::PassThrough(vec![plain('a'), plain('c')])
262 );
263 }
264
265 #[test]
266 fn branching_prefix_reaches_both_leaves() {
267 let map = sample();
268 let mut p = PendingSequence::new();
269 assert_eq!(p.feed(&map, ctrl('x')), Step::Pending);
270 assert_eq!(p.feed(&map, ctrl('c')), Step::Fired(&Action::Quit));
271 assert_eq!(p.feed(&map, ctrl('x')), Step::Pending);
272 assert_eq!(p.feed(&map, ctrl('s')), Step::Fired(&Action::Save));
273 }
274
275 #[test]
276 fn deep_prefix_is_pending_at_every_step_then_fires() {
277 let mut map = SequenceKeymap::new();
278 map.bind([plain('a'), plain('b'), plain('c')], Action::Top)
279 .unwrap();
280 let mut p = PendingSequence::new();
281 assert_eq!(p.feed(&map, plain('a')), Step::Pending);
282 assert_eq!(p.feed(&map, plain('b')), Step::Pending);
283 assert_eq!(p.feed(&map, plain('c')), Step::Fired(&Action::Top));
284 }
285
286 // --- flush: the idle-flush litmus and its abnormal cases. ---
287
288 #[test]
289 fn flush_drains_a_pending_prefix_as_literals_exactly_once() {
290 // The litmus the old example could only narrate: a prefix held, then the
291 // caller's idle timer flushes the dangling key as a literal, once, and the
292 // buffer ends empty.
293 let mut map = SequenceKeymap::new();
294 map.bind([plain('j'), plain('j')], Action::Top).unwrap();
295 let mut p = PendingSequence::new();
296 assert_eq!(p.feed(&map, plain('j')), Step::Pending);
297 assert_eq!(p.pending(), &[plain('j')]);
298 assert_eq!(p.flush(), vec![plain('j')]);
299 assert!(p.is_empty());
300 // Idempotent: a second flush has nothing left to drain.
301 assert_eq!(p.flush(), Vec::<KeyInput>::new());
302 }
303
304 #[test]
305 fn flush_on_empty_buffer_is_a_no_op() {
306 let mut p = PendingSequence::new();
307 assert_eq!(p.flush(), Vec::<KeyInput>::new());
308 assert!(p.is_empty());
309 }
310
311 #[test]
312 fn flush_after_fired_or_passthrough_is_empty() {
313 // Fired and PassThrough both already cleared the buffer, so flush must not
314 // replay their keys (which would double-emit them).
315 let map = sample();
316 let mut p = PendingSequence::new();
317 assert_eq!(p.feed(&map, plain('q')), Step::Fired(&Action::Solo)); // cleared
318 assert_eq!(p.flush(), Vec::<KeyInput>::new());
319 assert_eq!(
320 p.feed(&map, plain('z')),
321 Step::PassThrough(vec![plain('z')])
322 ); // cleared
323 assert_eq!(p.flush(), Vec::<KeyInput>::new());
324 }
325
326 #[test]
327 fn no_key_is_dropped_or_duplicated_across_a_stream() {
328 // Conservation law (Theresa): the keys returned by PassThrough + flush,
329 // plus the keys consumed by each Fired sequence, equal the input stream in
330 // order, with nothing lost or repeated. Catches clear-bugs and the
331 // mid-prefix pass-through edge in one assertion.
332 let map = sample();
333 let stream = [
334 ctrl('x'),
335 ctrl('s'), // completes Save: consumes [ctrl+x, ctrl+s]
336 ctrl('x'),
337 plain('z'), // miss: passes through [ctrl+x, z]
338 plain('g'), // dangling prefix at end: flushed
339 ];
340 let mut p = PendingSequence::new();
341 let mut seen: Vec<KeyInput> = Vec::new();
342 let mut buf: Vec<KeyInput> = Vec::new();
343 for key in stream {
344 buf.push(key);
345 match p.feed(&map, key) {
346 Step::Fired(_) => {
347 seen.append(&mut buf);
348 }
349 Step::Pending => {}
350 Step::PassThrough(keys) => {
351 // The buffered keys must equal what we tracked independently.
352 assert_eq!(keys, buf);
353 seen.append(&mut buf);
354 }
355 }
356 }
357 // Whatever is still pending is what flush will hand back.
358 let mut flushed = p.flush();
359 seen.append(&mut flushed);
360 buf.clear();
361 assert_eq!(seen, stream);
362 }
363}