1use crate::key::{Chord, ChordSeq, Key, KeyParseError};
14use crate::mode::Mode;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct Binding {
18 pub mode: Mode,
19 pub seq: ChordSeq,
20 pub command: &'static str,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct PendingEntry {
26 pub next: Chord,
27 pub command: Option<&'static str>,
29 pub bindings: usize,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum Resolution {
35 Count(u32),
37 Pending { candidates: Vec<PendingEntry> },
39 Run {
41 id: &'static str,
42 count: Option<u32>,
43 },
44 Unbound { flushed: Vec<Chord> },
47}
48
49#[derive(Debug, Clone, Default, PartialEq, Eq)]
51pub struct Pending {
52 pub count: Option<u32>,
53 pub chords: Vec<Chord>,
54}
55
56impl Pending {
57 pub fn clear(&mut self) {
58 self.count = None;
59 self.chords.clear();
60 }
61 pub fn is_idle(&self) -> bool {
62 self.count.is_none() && self.chords.is_empty()
63 }
64 pub fn display(&self) -> String {
66 let mut s = String::new();
67 if let Some(c) = self.count {
68 s.push_str(&c.to_string());
69 }
70 for ch in &self.chords {
71 s.push_str(&ch.to_string());
72 }
73 s
74 }
75}
76
77pub struct Keymap {
78 bindings: Vec<Binding>,
79 leader: Chord,
80}
81
82const MAX_COUNT: u32 = 100_000;
84
85impl Keymap {
86 pub fn new(leader: Chord) -> Self {
87 Self {
88 bindings: Vec::new(),
89 leader,
90 }
91 }
92
93 pub fn bind(
94 &mut self,
95 mode: Mode,
96 seq: &str,
97 command: &'static str,
98 ) -> Result<(), KeyParseError> {
99 let seq = ChordSeq::parse(seq, self.leader)?;
100 self.bindings.retain(|b| !(b.mode == mode && b.seq == seq));
102 self.bindings.push(Binding { mode, seq, command });
103 Ok(())
104 }
105
106 pub fn leader(&self) -> Chord {
107 self.leader
108 }
109 pub fn bindings(&self) -> &[Binding] {
110 &self.bindings
111 }
112
113 pub fn keys_for(&self, command: &str) -> Vec<&Binding> {
115 self.bindings
116 .iter()
117 .filter(|b| b.command == command)
118 .collect()
119 }
120
121 pub fn resolve(&self, mode: Mode, pending: &mut Pending, chord: Chord) -> Resolution {
122 if mode.takes_counts()
125 && pending.chords.is_empty()
126 && let Key::Char(c) = chord.key
127 && chord.mods.is_none()
128 && let Some(d) = c.to_digit(10)
129 && !(d == 0 && pending.count.is_none())
130 {
131 let next = pending
132 .count
133 .unwrap_or(0)
134 .saturating_mul(10)
135 .saturating_add(d);
136 pending.count = Some(next.min(MAX_COUNT));
137 return Resolution::Count(pending.count.unwrap());
138 }
139
140 pending.chords.push(chord);
141
142 if let Some(b) = self
143 .bindings
144 .iter()
145 .find(|b| b.mode == mode && b.seq.0 == pending.chords)
146 {
147 let count = pending.count;
148 pending.clear();
149 return Resolution::Run {
150 id: b.command,
151 count,
152 };
153 }
154
155 let depth = pending.chords.len();
156 let mut candidates: Vec<PendingEntry> = Vec::new();
157 for b in &self.bindings {
158 if b.mode != mode || b.seq.len() <= depth || !b.seq.starts_with(&pending.chords) {
159 continue;
160 }
161 let next = b.seq.0[depth];
162 let completes = b.seq.len() == depth + 1;
163 match candidates.iter_mut().find(|e| e.next == next) {
164 Some(e) => {
165 e.bindings += 1;
166 if completes {
167 e.command = Some(b.command);
168 }
169 }
170 None => candidates.push(PendingEntry {
171 next,
172 command: completes.then_some(b.command),
173 bindings: 1,
174 }),
175 }
176 }
177
178 if !candidates.is_empty() {
179 candidates.sort_by_key(|e| e.next);
180 return Resolution::Pending { candidates };
181 }
182
183 let flushed = std::mem::take(&mut pending.chords);
184 pending.count = None;
185 Resolution::Unbound { flushed }
186 }
187}
188
189pub fn default_keymap() -> Keymap {
197 let mut m = Keymap::new(Chord::ch(' '));
198 let mut bind = |mode, seq, cmd| {
199 m.bind(mode, seq, cmd)
200 .unwrap_or_else(|e| panic!("bad default binding `{seq}`: {e}"));
201 };
202
203 for mode in [Mode::Normal, Mode::Visual, Mode::VisualLine] {
204 bind(mode, "j", "motion.down");
205 bind(mode, "k", "motion.up");
206 bind(mode, "<Down>", "motion.down");
207 bind(mode, "<Up>", "motion.up");
208 bind(mode, "gg", "motion.top");
209 bind(mode, "G", "motion.bottom");
210 bind(mode, "<C-d>", "motion.half-down");
211 bind(mode, "<C-u>", "motion.half-up");
212 bind(mode, "y", "yank.field");
213 bind(mode, "Y", "yank.record");
214 bind(mode, "<Esc>", "app.cancel");
215 bind(mode, ":", "app.command-line");
216 bind(mode, "?", "app.help");
217 bind(mode, "R", "app.refresh");
218 bind(mode, "<leader>q", "app.quit");
219 bind(mode, "<C-c>", "app.quit");
220 }
221
222 for mode in [Mode::Normal, Mode::Visual, Mode::VisualLine] {
226 bind(mode, "<CR>", "nav.open");
227 }
228 bind(Mode::Normal, "-", "nav.up");
229 bind(Mode::Normal, "<C-o>", "nav.jump-back");
238 bind(Mode::Normal, "<C-i>", "nav.jump-forward");
239 bind(Mode::Normal, "<Tab>", "nav.jump-forward");
240 bind(Mode::Normal, "gs", "nav.schedules");
243 bind(Mode::Normal, "gw", "nav.workflows");
244
245 for mode in [Mode::Normal, Mode::Visual, Mode::VisualLine] {
249 bind(mode, "za", "history.fold");
250 bind(mode, "zR", "history.expand-all");
251 bind(mode, "zM", "history.collapse-all");
252 bind(mode, "zp", "history.plumbing");
253 bind(mode, "]f", "history.next-failure");
255 bind(mode, "[f", "history.prev-failure");
256 bind(mode, "F", "history.follow");
257 bind(mode, "K", "history.detail");
260 bind(mode, "<C-e>", "history.detail-down");
263 bind(mode, "<C-y>", "history.detail-up");
264 bind(mode, "!", "payload.pipe");
266 bind(mode, "<leader>ya", "yank.payload");
270 bind(mode, "<leader>yi", "yank.payload-input");
271 bind(mode, "<leader>yr", "yank.payload-result");
272
273 bind(mode, "<leader>sv", "window.split-right");
277 bind(mode, "<leader>sh", "window.split-down");
278 bind(mode, "<leader>sx", "window.close");
279 bind(mode, "<leader>se", "window.equalize");
280 bind(mode, "<C-w>h", "window.focus-left");
281 bind(mode, "<C-w>j", "window.focus-down");
282 bind(mode, "<C-w>k", "window.focus-up");
283 bind(mode, "<C-w>l", "window.focus-right");
284 bind(mode, "<leader>rh", "window.grow-left");
285 bind(mode, "<leader>rj", "window.grow-down");
286 bind(mode, "<leader>rk", "window.grow-up");
287 bind(mode, "<leader>rl", "window.grow-right");
288 bind(mode, "<leader>mc", "workflow.cancel");
291 bind(mode, "<leader>mt", "workflow.terminate");
292 bind(mode, "<leader>ms", "workflow.signal");
293 bind(mode, "<leader>md", "workflow.delete");
294 bind(mode, "<leader>mr", "workflow.reset");
295 bind(mode, "<leader>mu", "workflow.update");
296 bind(mode, "<leader>mp", "schedule.pause");
297 bind(mode, "<leader>mg", "schedule.trigger");
298 bind(mode, "<leader>mD", "schedule.delete");
299 bind(mode, "<leader>mb", "schedule.backfill");
300 bind(mode, "<leader>mn", "schedule.create");
301
302 bind(mode, "<leader>to", "tab.new");
303 bind(mode, "<leader>tx", "tab.close");
304 bind(mode, "<leader>tn", "tab.next");
305 bind(mode, "<leader>tp", "tab.previous");
306 }
307
308 bind(Mode::Normal, "/", "search.open");
312 bind(Mode::Normal, "n", "search.next");
313 bind(Mode::Normal, "N", "search.previous");
314
315 bind(Mode::Normal, "<leader>ff", "find.workflow");
319 bind(Mode::Normal, "<leader>fl", "find.event");
320 bind(Mode::Normal, "<leader>fb", "find.pane");
321 bind(Mode::Normal, "<leader>fh", "find.command");
322 bind(Mode::Normal, "<leader>fg", "find.filter");
323 bind(Mode::Normal, "<leader>N", "find.namespace");
326 bind(Mode::Normal, "<leader>xx", "list.problems");
327 bind(Mode::Normal, "<leader>e", "payload.edit");
328
329 bind(Mode::Normal, "i", "mode.insert");
330 bind(Mode::Normal, "v", "mode.visual");
331 bind(Mode::Normal, "V", "mode.visual-line");
332
333 bind(Mode::Insert, "jk", "mode.normal");
335 bind(Mode::Insert, "<Esc>", "mode.normal");
336
337 m
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343
344 fn map() -> Keymap {
345 default_keymap()
346 }
347
348 fn feed(m: &Keymap, mode: Mode, p: &mut Pending, keys: &[Chord]) -> Resolution {
349 let mut last = Resolution::Unbound { flushed: vec![] };
350 for &c in keys {
351 last = m.resolve(mode, p, c);
352 }
353 last
354 }
355
356 #[test]
357 fn every_default_binding_names_a_command_that_exists() {
358 let registry = crate::command::Registry::builtin();
363 let map = map();
364 let missing: Vec<&str> = map
365 .bindings()
366 .iter()
367 .map(|b| b.command)
368 .filter(|id| registry.get(id).is_none())
369 .collect();
370 assert!(missing.is_empty(), "bound to nothing: {missing:?}");
371 }
372
373 #[test]
374 fn no_two_default_bindings_claim_the_same_keys_in_one_mode() {
375 let map = map();
378 let mut seen: Vec<(Mode, &ChordSeq)> = Vec::new();
379 let mut clashes = Vec::new();
380 for b in map.bindings() {
381 if seen.iter().any(|(m, s)| *m == b.mode && *s == &b.seq) {
382 clashes.push(format!("{:?} {:?} -> {}", b.mode, b.seq, b.command));
383 }
384 seen.push((b.mode, &b.seq));
385 }
386 assert!(clashes.is_empty(), "duplicate bindings: {clashes:?}");
387 }
388
389 #[test]
390 fn resolves_a_single_key() {
391 let (m, mut p) = (map(), Pending::default());
392 assert_eq!(
393 m.resolve(Mode::Normal, &mut p, Chord::ch('j')),
394 Resolution::Run {
395 id: "motion.down",
396 count: None
397 }
398 );
399 assert!(p.is_idle(), "pending state must reset after a match");
400 }
401
402 #[test]
403 fn accumulates_multi_digit_counts() {
404 let (m, mut p) = (map(), Pending::default());
405 assert_eq!(
406 m.resolve(Mode::Normal, &mut p, Chord::ch('1')),
407 Resolution::Count(1)
408 );
409 assert_eq!(
410 m.resolve(Mode::Normal, &mut p, Chord::ch('2')),
411 Resolution::Count(12)
412 );
413 assert_eq!(
414 m.resolve(Mode::Normal, &mut p, Chord::ch('j')),
415 Resolution::Run {
416 id: "motion.down",
417 count: Some(12)
418 }
419 );
420 }
421
422 #[test]
423 fn leading_zero_is_not_a_count() {
424 let (m, mut p) = (map(), Pending::default());
426 assert!(matches!(
427 m.resolve(Mode::Normal, &mut p, Chord::ch('0')),
428 Resolution::Unbound { .. }
429 ));
430 p.clear();
431 m.resolve(Mode::Normal, &mut p, Chord::ch('1'));
432 assert_eq!(
433 m.resolve(Mode::Normal, &mut p, Chord::ch('0')),
434 Resolution::Count(10)
435 );
436 }
437
438 #[test]
439 fn counts_are_capped() {
440 let (m, mut p) = (map(), Pending::default());
441 for _ in 0..12 {
442 m.resolve(Mode::Normal, &mut p, Chord::ch('9'));
443 }
444 assert_eq!(p.count, Some(MAX_COUNT));
445 }
446
447 #[test]
448 fn multi_key_sequences_report_pending_then_run() {
449 let (m, mut p) = (map(), Pending::default());
450 let r = m.resolve(Mode::Normal, &mut p, Chord::ch('g'));
451 match r {
452 Resolution::Pending { candidates } => {
453 let gg = candidates
456 .iter()
457 .find(|c| c.next == Chord::ch('g'))
458 .expect("gg should be reachable from g");
459 assert_eq!(gg.command, Some("motion.top"));
460 }
461 other => panic!("expected Pending, got {other:?}"),
462 }
463 assert_eq!(
464 m.resolve(Mode::Normal, &mut p, Chord::ch('g')),
465 Resolution::Run {
466 id: "motion.top",
467 count: None
468 }
469 );
470 }
471
472 #[test]
473 fn leader_lists_its_candidates() {
474 let (m, mut p) = (map(), Pending::default());
475 match m.resolve(Mode::Normal, &mut p, Chord::ch(' ')) {
476 Resolution::Pending { candidates } => {
477 assert!(candidates.iter().any(|c| c.command == Some("app.quit")));
478 }
479 other => panic!("expected Pending, got {other:?}"),
480 }
481 }
482
483 #[test]
484 fn counts_survive_a_multi_key_sequence() {
485 let (m, mut p) = (map(), Pending::default());
486 let r = feed(
487 &m,
488 Mode::Normal,
489 &mut p,
490 &[Chord::ch('5'), Chord::ch('g'), Chord::ch('g')],
491 );
492 assert_eq!(
493 r,
494 Resolution::Run {
495 id: "motion.top",
496 count: Some(5)
497 }
498 );
499 }
500
501 #[test]
502 fn jk_leaves_insert_mode() {
503 let (m, mut p) = (map(), Pending::default());
504 assert!(matches!(
505 m.resolve(Mode::Insert, &mut p, Chord::ch('j')),
506 Resolution::Pending { .. }
507 ));
508 assert_eq!(
509 m.resolve(Mode::Insert, &mut p, Chord::ch('k')),
510 Resolution::Run {
511 id: "mode.normal",
512 count: None
513 }
514 );
515 }
516
517 #[test]
518 fn a_held_j_is_flushed_when_the_sequence_fails() {
519 let (m, mut p) = (map(), Pending::default());
521 m.resolve(Mode::Insert, &mut p, Chord::ch('j'));
522 match m.resolve(Mode::Insert, &mut p, Chord::ch('a')) {
523 Resolution::Unbound { flushed } => {
524 assert_eq!(flushed, vec![Chord::ch('j'), Chord::ch('a')]);
525 }
526 other => panic!("expected Unbound with both chords, got {other:?}"),
527 }
528 assert!(p.is_idle());
529 }
530
531 #[test]
532 fn insert_mode_ignores_counts() {
533 let (m, mut p) = (map(), Pending::default());
534 match m.resolve(Mode::Insert, &mut p, Chord::ch('7')) {
535 Resolution::Unbound { flushed } => assert_eq!(flushed, vec![Chord::ch('7')]),
536 other => panic!("digits must be literal in Insert, got {other:?}"),
537 }
538 }
539
540 #[test]
541 fn ctrl_hjkl_is_never_bound() {
542 let m = map();
544 for c in ['h', 'j', 'k', 'l'] {
545 let chord = Chord::ctrl(c);
546 assert!(
547 !m.bindings().iter().any(|b| b.seq.0 == vec![chord]),
548 "<C-{c}> must not be bound; tmux eats it"
549 );
550 }
551 }
552
553 #[test]
554 fn later_bindings_override_earlier_ones() {
555 let mut m = Keymap::new(Chord::ch(' '));
556 m.bind(Mode::Normal, "j", "motion.down").unwrap();
557 m.bind(Mode::Normal, "j", "motion.up").unwrap();
558 assert_eq!(m.bindings().len(), 1);
559 let mut p = Pending::default();
560 assert_eq!(
561 m.resolve(Mode::Normal, &mut p, Chord::ch('j')),
562 Resolution::Run {
563 id: "motion.up",
564 count: None
565 }
566 );
567 }
568
569 #[test]
570 fn pending_display_matches_what_was_typed() {
571 let (m, mut p) = (map(), Pending::default());
572 feed(&m, Mode::Normal, &mut p, &[Chord::ch('2'), Chord::ch('g')]);
573 assert_eq!(p.display(), "2g");
574 }
575
576 #[test]
577 fn every_bound_command_exists_in_the_registry() {
578 let reg = crate::command::Registry::builtin();
580 for b in map().bindings() {
581 assert!(
582 reg.get(b.command).is_some(),
583 "binding {} points at unknown command `{}`",
584 b.seq,
585 b.command
586 );
587 }
588 }
589}