shep 0.25.0

The shep binary: a process manager that keeps a flock of long-running processes alive on macOS, Linux and Windows, with logs, watch and cron restarts, and webhook alerts
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
//! What the keymap overlay prints, derived from `map_key` rather than
//! listed beside it.
//!
//! [`binding`] is an exhaustive match over [`KeyPress`] with no wildcard
//! arm, so a new variant does not compile until it has a row here, and
//! [`rows`] builds the list by pushing [`PROBE`] through
//! [`super::input::map_key`] itself. A binding cannot reach the reducer
//! without also reaching the overlay, and a row cannot name a key nothing
//! is bound to.

use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};

use super::app::{ActionVerb, InputMode, KeyPress};
use super::input::map_key;
use super::textbox::TextEdit;
use crate::vocabulary::Role;

/// How many entry rows one column of the overlay has.
///
/// The box is nineteen rows: a border pair, a heading row, twelve entries,
/// a blank, the gate line, and two closing lines. Zero spare, which
/// `every_group_fits_its_column` is what guards.
pub(super) const ENTRY_ROWS: usize = 12;

/// How wide the key-caption cell is.
pub(super) const KEY_CELL: u16 = 12;

/// How wide the sentence cell is.
pub(super) const TEXT_CELL: u16 = 17;

/// One of the overlay's four columns, grouped by what the keys in it do.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Group {
    Moving,
    Looking,
    Changing,
    Doing,
    /// Not a column. The one row that draws on the overlay's closing line
    /// instead: `q  ctrl-c`, which has to come out of [`binding`] like
    /// every other variant but must not take one of `Looking`'s twelve
    /// rows, since `Looking` is already at twelve.
    Closing,
}

impl Group {
    /// The four that draw as columns, left to right. [`Self::Closing`] is
    /// not among them.
    pub(super) const DRAWN: [Self; 4] = [Self::Moving, Self::Looking, Self::Changing, Self::Doing];

    /// The column's heading, or `""` for [`Self::Closing`], which has no
    /// column to head.
    pub(super) const fn heading(self) -> &'static str {
        match self {
            Self::Moving => "MOVING",
            Self::Looking => "LOOKING",
            Self::Changing => "CHANGING",
            Self::Doing => "DOING",
            Self::Closing => "",
        }
    }

    /// The heading's colour role. `Doing` is bark because its three keys
    /// are the destructive ones; the rest are meadow.
    pub(super) const fn role(self) -> Role {
        match self {
            Self::Doing => Role::Bark,
            Self::Moving | Self::Looking | Self::Changing | Self::Closing => Role::Meadow,
        }
    }
}

/// One row: the key column, its group, and the sentence beside it.
///
/// `keys` is at most [`KEY_CELL`] characters and `does` at most
/// [`TEXT_CELL`]; `the_cells_fit_their_widths` asserts both rather than
/// leaving a long one to be cut on screen.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct Binding {
    pub keys: &'static str,
    pub group: Group,
    pub does: &'static str,
}

/// The overlay's row for `press`.
///
/// Exhaustive, with no wildcard arm and no `Action(_)` catch-all, so both
/// [`KeyPress`] and [`ActionVerb`] have to grow a row here before they
/// compile. That is the whole guard against a binding that dispatches and
/// does not print.
const fn binding(press: &KeyPress) -> Binding {
    // `row` keeps each arm to one line, so a reader checks forty
    // captions rather than forty struct literals. `the_rows_are_
    // deduplicated` asserts the count; `rows`'s own doc gives the
    // thirty-nine that land in a DRAWN column.
    const fn row(keys: &'static str, group: Group, does: &'static str) -> Binding {
        Binding { keys, group, does }
    }
    match press {
        KeyPress::SelectDown | KeyPress::SelectUp => {
            row("j/k  \u{2193}/\u{2191}", Group::Moving, "select a row")
        }
        KeyPress::SelectFirst | KeyPress::SelectLast => {
            row("g/G home/end", Group::Moving, "first / last")
        }
        KeyPress::StepDown | KeyPress::StepUp => row("J/K", Group::Moving, "next / prev sheep"),
        KeyPress::PageDown | KeyPress::PageUp => row("ctrl-d/u", Group::Moving, "page the feed"),
        KeyPress::TabPrev | KeyPress::TabNext => row(TAB_KEYS, Group::Moving, "environment tab"),
        // One row for both meanings of the same two keys. `map_key` gives
        // them to groups in `Normal` and to a key/value editor's fields in
        // `Text`, and the overlay can never be raised from inside a box, so
        // the caption it prints is always the one that was in force.
        KeyPress::NextGroup | KeyPress::PrevGroup | KeyPress::NextField | KeyPress::PrevField => {
            row("tab/S-tab", Group::Moving, "next / prev group")
        }
        KeyPress::Group(_) => row("1-9", Group::Moving, "jump to a group"),
        KeyPress::MatchNext | KeyPress::MatchPrev => row("n/N", Group::Moving, "next / prev match"),
        KeyPress::TextEdit(
            TextEdit::Left | TextEdit::Right | TextEdit::WordLeft | TextEdit::WordRight,
        ) => row(
            "\u{2190}/\u{2192} alt-b/f",
            Group::Moving,
            "char / word move",
        ),
        KeyPress::TextEdit(TextEdit::Home | TextEdit::End) => {
            row("ctrl-a/e", Group::Moving, "home/end in a box")
        }
        KeyPress::Confirm => row("\u{21b5}", Group::Moving, "open selection"),
        KeyPress::Escape => row("esc", Group::Moving, "back one level"),

        KeyPress::Help => row("h  ?", Group::Looking, "this keymap"),
        KeyPress::Bleats => row("b", Group::Looking, "feed, full screen"),
        KeyPress::FoldView => row("F", Group::Looking, "gather by fold"),
        KeyPress::Collapse => row("z", Group::Looking, "collapse a fold"),
        KeyPress::StreamCycle => row("o", Group::Looking, "out / err / both"),
        KeyPress::LevelCycle => row("m", Group::Looking, "minimum level"),
        KeyPress::FollowToggle => row("f", Group::Looking, "follow the tail"),
        KeyPress::WrapToggle => row("w", Group::Looking, "wrap long lines"),
        KeyPress::FilterStart => row("/", Group::Looking, "filter, or match"),
        KeyPress::Refresh => row("r", Group::Looking, "refresh now"),
        KeyPress::Reveal => row("v", Group::Looking, "reveal for 10s"),
        KeyPress::Copy => row("y", Group::Looking, "copy the value"),

        KeyPress::Edit => row("e", Group::Changing, "the config pane"),
        KeyPress::Cycle => row("space", Group::Changing, "cycle a value"),
        KeyPress::Remove => row("d", Group::Changing, "restore default"),
        KeyPress::Undo => row("u", Group::Changing, "undo the edit"),
        KeyPress::Settings => row("s", Group::Changing, "shepherd settings"),
        KeyPress::Secrets => row("S", Group::Changing, "secrets"),
        KeyPress::ArmDelete => row("D", Group::Changing, "delete a key"),
        KeyPress::Continue => row("c", Group::Changing, "leave it running"),
        KeyPress::TextChar(_) => row("any char", Group::Changing, "types into a box"),
        KeyPress::TextBackspace
        | KeyPress::TextEdit(TextEdit::Delete)
        | KeyPress::TextApply
        | KeyPress::TextAbandon => row(
            "\u{232b}/del \u{21b5} esc",
            Group::Changing,
            "erase, file, drop",
        ),
        KeyPress::TextEdit(
            TextEdit::DeleteWordBack | TextEdit::DeleteToStart | TextEdit::DeleteToEnd,
        ) => row("ctrl-w/u/k", Group::Changing, "cut word, to ends"),
        KeyPress::TextEdit(TextEdit::Reveal) => row("ctrl-r", Group::Changing, "unmask a secret"),

        KeyPress::Action(ActionVerb::Stop) => row("x", Group::Doing, "stop"),
        KeyPress::Action(ActionVerb::Restart) => row("R", Group::Doing, "restart"),
        KeyPress::Action(ActionVerb::Reload) => row("L", Group::Doing, "reload"),
        KeyPress::Catalogue => row("i", Group::Doing, "dog catalogue"),

        // `q` draws on the closing line beside `h or ? closes this`, not in
        // a column: `Looking` is at twelve of twelve and this would be its
        // thirteenth. `Group::Closing` is how it comes out of this match
        // without taking a column row.
        KeyPress::Quit => row("q  ctrl-c", Group::Closing, "quit"),
    }
}

/// Every key the overlay claims, as `map_key` would see it.
///
/// One entry per row rather than one per key: `Char('a')` stands for every
/// letter the text-mode row covers and `Char('1')` for the nine group
/// digits. `every_key_map_key_binds_is_in_the_probe` sweeps the whole
/// keyboard against this, so a key missing here is a test failure rather
/// than a row missing from the box.
const PROBE: &[(KeyCode, KeyModifiers)] = &[
    (KeyCode::Char('j'), KeyModifiers::NONE),
    (KeyCode::Char('g'), KeyModifiers::NONE),
    (KeyCode::Char('J'), KeyModifiers::SHIFT),
    (KeyCode::Char('d'), KeyModifiers::CONTROL),
    (KeyCode::Left, KeyModifiers::NONE),
    (KeyCode::Char('b'), KeyModifiers::ALT),
    (KeyCode::Char('a'), KeyModifiers::CONTROL),
    (KeyCode::Tab, KeyModifiers::NONE),
    (KeyCode::BackTab, KeyModifiers::SHIFT),
    (KeyCode::Char('1'), KeyModifiers::NONE),
    (KeyCode::Char('n'), KeyModifiers::NONE),
    (KeyCode::Enter, KeyModifiers::NONE),
    (KeyCode::Esc, KeyModifiers::NONE),
    (KeyCode::Char('h'), KeyModifiers::NONE),
    (KeyCode::Char('b'), KeyModifiers::NONE),
    (KeyCode::Char('F'), KeyModifiers::SHIFT),
    (KeyCode::Char('z'), KeyModifiers::NONE),
    (KeyCode::Char('o'), KeyModifiers::NONE),
    (KeyCode::Char('m'), KeyModifiers::NONE),
    (KeyCode::Char('f'), KeyModifiers::NONE),
    (KeyCode::Char('w'), KeyModifiers::NONE),
    (KeyCode::Char('/'), KeyModifiers::NONE),
    (KeyCode::Char('r'), KeyModifiers::NONE),
    (KeyCode::Char('v'), KeyModifiers::NONE),
    (KeyCode::Char('y'), KeyModifiers::NONE),
    (KeyCode::Char('e'), KeyModifiers::NONE),
    (KeyCode::Char(' '), KeyModifiers::NONE),
    (KeyCode::Char('d'), KeyModifiers::NONE),
    (KeyCode::Char('u'), KeyModifiers::NONE),
    (KeyCode::Char('s'), KeyModifiers::NONE),
    (KeyCode::Char('S'), KeyModifiers::SHIFT),
    (KeyCode::Char('D'), KeyModifiers::SHIFT),
    (KeyCode::Char('c'), KeyModifiers::NONE),
    (KeyCode::Char('a'), KeyModifiers::NONE),
    (KeyCode::Backspace, KeyModifiers::NONE),
    (KeyCode::Delete, KeyModifiers::NONE),
    (KeyCode::Char('w'), KeyModifiers::CONTROL),
    (KeyCode::Char('r'), KeyModifiers::CONTROL),
    (KeyCode::Char('x'), KeyModifiers::NONE),
    (KeyCode::Char('R'), KeyModifiers::SHIFT),
    (KeyCode::Char('L'), KeyModifiers::SHIFT),
    (KeyCode::Char('i'), KeyModifiers::NONE),
    (KeyCode::Char('q'), KeyModifiers::NONE),
];

/// [`rows`], generalised over which probe entries to run, so
/// `every_probe_entry_binds_something` can ask "what would this draw
/// without entry N" without a second copy of the loop.
fn rows_from(probe: &[(KeyCode, KeyModifiers)]) -> Vec<Binding> {
    let mut out: Vec<Binding> = Vec::new();
    for group in Group::DRAWN.into_iter().chain([Group::Closing]) {
        for (code, modifiers) in probe {
            let event = Event::Key(KeyEvent::new(*code, *modifiers));
            // Both modes, not `.or_else`: a code with a mapping in each
            // needs both to reach `binding`, or whichever mode lost the
            // race drops out of the overlay silently. `Backspace` has no
            // `Normal` mapping, which is why `or_else` reads as correct.
            for mode in [InputMode::Normal, InputMode::Text, InputMode::Pane] {
                let Some(press) = map_key(&event, mode) else {
                    continue;
                };
                // Not the function's own name: shadowing it here would
                // silently resolve a later call to `binding` inside this
                // loop to this value instead.
                let entry = binding(&press);
                if entry.group == group && !out.iter().any(|seen| seen.keys == entry.keys) {
                    out.push(entry);
                }
            }
        }
    }
    out
}

/// Every row the overlay draws, grouped, in the order each column lists
/// them.
///
/// Built by running [`PROBE`] through [`map_key`], so nothing here names a
/// key the reducer does not dispatch on.
pub(super) fn rows() -> Vec<Binding> {
    rows_from(PROBE)
}

/// [`rows`] as the overlay draws them over a pane: `←/→` closes and opens a
/// table when `tree` says the pane under the overlay is a tree of tables,
/// and keeps the environment-tab caption everywhere else.
pub(super) fn rows_over(tree: bool) -> Vec<Binding> {
    let mut out = rows();
    if tree {
        for row in &mut out {
            if row.keys == TAB_KEYS {
                row.does = "close/open table";
            }
        }
    }
    out
}

/// The caption of the `←/→` row.
const TAB_KEYS: &str = "\u{2190}/\u{2192}";

#[cfg(test)]
mod tests {
    use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};

    use super::*;
    use crate::output::width::visible_width;

    /// Every `KeyPress` `map_key` can produce is produced by some `PROBE`
    /// entry too.
    ///
    /// A binding added to `map_key` and not to `PROBE` fails here rather
    /// than going missing from the overlay. The rows are built by running
    /// `map_key`, not listed beside it.
    #[test]
    fn every_key_map_key_binds_is_in_the_probe() {
        let probed: Vec<KeyPress> = PROBE
            .iter()
            .filter_map(|(code, modifiers)| {
                map_key(
                    &Event::Key(KeyEvent::new(*code, *modifiers)),
                    InputMode::Normal,
                )
            })
            .chain(PROBE.iter().filter_map(|(code, modifiers)| {
                map_key(
                    &Event::Key(KeyEvent::new(*code, *modifiers)),
                    InputMode::Text,
                )
            }))
            .collect();

        for (code, modifiers) in every_key() {
            for mode in [InputMode::Normal, InputMode::Text, InputMode::Pane] {
                let Some(press) = map_key(&Event::Key(KeyEvent::new(code, modifiers)), mode) else {
                    continue;
                };
                assert!(
                    probed.iter().any(|seen| same_row(seen, &press)),
                    "{code:?} with {modifiers:?} in {mode:?} binds {press:?}, \
                     which no PROBE entry produces"
                );
            }
        }
    }

    /// No `PROBE` entry binds nothing: every `(code, modifiers)` pair
    /// pinned here reaches a real `KeyPress` in at least one of
    /// `map_key`'s two modes.
    ///
    /// `rows_from` checks both modes for every entry, so a letter with no
    /// `Normal` meaning is no less necessary than one that has one.
    #[test]
    fn every_probe_entry_binds_something() {
        for (code, modifiers) in PROBE {
            let event = Event::Key(KeyEvent::new(*code, *modifiers));
            let normal = map_key(&event, InputMode::Normal);
            let text = map_key(&event, InputMode::Text);
            assert!(
                normal.or(text).is_some(),
                "{code:?} with {modifiers:?} is in PROBE and binds nothing"
            );
        }
    }

    /// `rows_from` checks both modes for every entry rather than picking
    /// whichever wins first, so the `any char` row does not depend on
    /// `Char('a')` alone: an ordinary letter with its own `Normal` meaning
    /// still reaches `TextChar` too. Removing the one entry with no
    /// `Normal` meaning at all must not lose the row.
    #[test]
    fn any_char_survives_losing_its_only_normal_less_entry() {
        let (only_normal_less, _) = PROBE
            .iter()
            .enumerate()
            .find(|(_, (code, modifiers))| {
                let event = Event::Key(KeyEvent::new(*code, *modifiers));
                map_key(&event, InputMode::Normal).is_none()
                    && matches!(
                        map_key(&event, InputMode::Text),
                        Some(KeyPress::TextChar(_))
                    )
            })
            .expect("PROBE needs a letter with no Normal meaning to make this test mean anything");
        let without_it: Vec<(KeyCode, KeyModifiers)> = PROBE
            .iter()
            .enumerate()
            .filter(|(index, _)| *index != only_normal_less)
            .map(|(_, entry)| *entry)
            .collect();
        assert!(
            rows_from(&without_it)
                .iter()
                .any(|row| row.keys == "any char"),
            "any char disappeared once its only Normal-less representative was gone, so no \
             ordinary letter's own Text-mode mapping is reaching it independently"
        );
    }

    /// No group has more entries than its column has rows.
    ///
    /// Zero spare at the time of writing: `Looking` has twelve against
    /// twelve. A forty-third binding compiles, because `binding` gives it a
    /// row, and then overflows the box. This is the test that names which
    /// group grew.
    #[test]
    fn every_group_fits_its_column() {
        let rows = rows();
        for group in Group::DRAWN {
            let count = rows.iter().filter(|row| row.group == group).count();
            assert!(
                count <= ENTRY_ROWS,
                "{group:?} has {count} entries against {ENTRY_ROWS} rows"
            );
        }
    }

    /// Forty rows, thirty-nine of them in a drawn column, and no two
    /// share a key caption.
    ///
    /// The fortieth is `q  ctrl-c`, in `Group::Closing`: it has no row
    /// of its own in the box, which is what bought `Looking` its twelfth
    /// entry. Both counts are asserted, because one alone would pass if a
    /// row migrated between a column and the closing line.
    #[test]
    fn the_rows_are_deduplicated() {
        let rows = rows();
        let captions: Vec<&str> = rows.iter().map(|row| row.keys).collect();
        assert_eq!(rows.len(), 41, "{captions:?}");
        assert_eq!(
            rows.iter()
                .filter(|row| Group::DRAWN.contains(&row.group))
                .count(),
            40,
            "{captions:?}"
        );
        for (index, row) in rows.iter().enumerate() {
            assert!(
                !rows[index + 1..].iter().any(|other| other.keys == row.keys),
                "{} appears twice",
                row.keys
            );
        }
    }

    /// The two keys the design named wrongly, asserted here as well as in
    /// `input.rs`, because this is the file the overlay prints from.
    #[test]
    fn the_overlay_names_the_keys_that_shipped_not_the_ones_drawn() {
        let rows = rows();
        assert!(
            rows.iter()
                .any(|row| row.keys == "b" && row.does.contains("feed")),
            "the feed is on `b`, not the design's `l`"
        );
        assert!(
            rows.iter()
                .any(|row| row.keys == "S" && row.does.contains("secrets")),
            "secrets is on `S`, not the design's `g`"
        );
        assert!(
            !rows.iter().any(|row| row.keys == "l"),
            "`l` is unbound and must not appear"
        );
    }

    /// Every key the overlay prints in a caption is a key `map_key` binds.
    ///
    /// Single-character captions only: `g/G home/end` names four keys in
    /// one cell. ASCII only: a non-ASCII character is a symbol standing for
    /// a named `KeyCode`, not something anyone types, so looking it up as
    /// `Char(that_symbol)` is the wrong query.
    #[test]
    fn a_single_character_caption_names_a_bound_key() {
        for row in rows() {
            let mut chars = row.keys.chars();
            let Some(only) = chars.next() else { continue };
            if chars.next().is_some() || !only.is_ascii() {
                continue;
            }
            assert!(
                map_key(
                    &Event::Key(KeyEvent::new(KeyCode::Char(only), KeyModifiers::NONE)),
                    InputMode::Normal
                )
                .is_some(),
                "the overlay prints `{only}` and `map_key` does not bind it"
            );
        }
    }

    /// `DRAWN`'s headings, which several `contains` assertions rest on.
    ///
    /// Non-empty first: `str::contains("")` is always true, so an empty
    /// heading passes every "reached the screen" assertion. Then pairwise:
    /// a heading that is a substring of another is counted for both, so a
    /// column count can be right for the wrong reason.
    #[test]
    fn every_drawn_heading_is_a_word_of_its_own() {
        for group in Group::DRAWN {
            assert!(!group.heading().is_empty(), "{group:?} draws a bank header");
        }
        assert!(
            Group::Closing.heading().is_empty(),
            "the quit row draws no bank header, so its heading stays empty"
        );
        for (index, one) in Group::DRAWN.iter().enumerate() {
            for other in &Group::DRAWN[index + 1..] {
                assert!(
                    !one.heading().contains(other.heading())
                        && !other.heading().contains(one.heading()),
                    "{one:?} and {other:?} share a heading substring"
                );
            }
        }
    }

    /// Every caption fits the cell it draws into. The widths are
    /// `view::keymap`'s, asserted here because this is where the strings
    /// are written.
    ///
    /// Both bounds are a pair: `visible_width("")` is 0 and fits every
    /// cell, so an upper bound alone cannot see a caption that is too short.
    #[test]
    fn the_cells_fit_their_widths() {
        for row in rows().into_iter().chain(rows_over(true)) {
            assert!(
                (1..=usize::from(KEY_CELL)).contains(&visible_width(row.keys)),
                "the `{}` caption is {} cells against {KEY_CELL}",
                row.keys,
                visible_width(row.keys)
            );
            assert!(
                (1..=usize::from(TEXT_CELL)).contains(&visible_width(row.does)),
                "`{}` is {} cells against {TEXT_CELL}",
                row.does,
                visible_width(row.does)
            );
        }
    }

    /// Every `KeyCode` variant `map_key` can match, bare and with CONTROL,
    /// SHIFT and ALT.
    fn every_key() -> Vec<(KeyCode, KeyModifiers)> {
        let mut keys: Vec<KeyCode> = (0x20u8..=0x7E).map(|b| KeyCode::Char(b as char)).collect();
        keys.extend([
            KeyCode::Esc,
            KeyCode::Enter,
            KeyCode::Backspace,
            KeyCode::Tab,
            KeyCode::BackTab,
            KeyCode::Up,
            KeyCode::Down,
            KeyCode::Left,
            KeyCode::Right,
            KeyCode::Home,
            KeyCode::End,
            KeyCode::Delete,
        ]);
        keys.extend((1..=12).map(KeyCode::F));
        keys.into_iter()
            .flat_map(|code| {
                [
                    (code, KeyModifiers::NONE),
                    (code, KeyModifiers::CONTROL),
                    (code, KeyModifiers::SHIFT),
                    (code, KeyModifiers::ALT),
                ]
            })
            .collect()
    }

    /// Whether two presses land on the same overlay row. `TextChar('a')`
    /// and `TextChar('z')` are one row, and `Group(1)` and `Group(8)` are
    /// one row, so equality is the wrong comparison.
    fn same_row(left: &KeyPress, right: &KeyPress) -> bool {
        binding(left).keys == binding(right).keys
    }
}