Skip to main content

rmut_front/
key.rs

1//! Keys as a front end reports them, with no toolkit's type in the
2//! signature. Shaped like crossterm's so the terminal front end maps
3//! one to the other at its read site and nothing else notices.
4
5/// Which key.
6#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
7pub enum KeyCode {
8    Char(char),
9    Enter,
10    Esc,
11    Tab,
12    Backspace,
13    Delete,
14    Up,
15    Down,
16    Left,
17    Right,
18    PageUp,
19    PageDown,
20    Home,
21    End,
22    /// Anything a front end reports that rmut has no name for. Never
23    /// bound, never matched.
24    Other,
25}
26
27/// Which modifiers were held: a set of the three rmut cares about.
28#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Default)]
29pub struct KeyModifiers(u8);
30
31impl KeyModifiers {
32    pub const NONE: KeyModifiers = KeyModifiers(0);
33    pub const SHIFT: KeyModifiers = KeyModifiers(1);
34    pub const CONTROL: KeyModifiers = KeyModifiers(2);
35    pub const ALT: KeyModifiers = KeyModifiers(4);
36
37    pub fn contains(self, other: KeyModifiers) -> bool {
38        self.0 & other.0 == other.0
39    }
40
41    pub fn is_empty(self) -> bool {
42        self.0 == 0
43    }
44}
45
46impl std::ops::BitOr for KeyModifiers {
47    type Output = KeyModifiers;
48    fn bitor(self, rhs: KeyModifiers) -> KeyModifiers {
49        KeyModifiers(self.0 | rhs.0)
50    }
51}
52
53impl std::ops::BitOrAssign for KeyModifiers {
54    fn bitor_assign(&mut self, rhs: KeyModifiers) {
55        self.0 |= rhs.0;
56    }
57}
58
59impl std::ops::BitAnd for KeyModifiers {
60    type Output = KeyModifiers;
61    fn bitand(self, rhs: KeyModifiers) -> KeyModifiers {
62        KeyModifiers(self.0 & rhs.0)
63    }
64}
65
66/// One key press.
67#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
68pub struct KeyEvent {
69    pub code: KeyCode,
70    pub modifiers: KeyModifiers,
71}
72
73impl KeyEvent {
74    pub fn new(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent {
75        KeyEvent { code, modifiers }
76    }
77}
78
79/// mutt's Esc: the first key of a two-key sequence rather than a key
80/// of its own. mutt spells every Alt binding `Esc x`, and its keymap
81/// waits after an Esc for the key that finishes the sequence
82/// (`km_dokey`), so Esc, a pause, then `/` is search-reverse there
83/// however slowly it is typed. A terminal sends Alt+/ as the same two
84/// bytes, but only when they arrive together does the key reader
85/// merge them; typed apart they reached rmut as a bare Esc and a
86/// plain `/`. This merges them again: an Esc is held, and the next
87/// key comes out with Alt added. A second Esc gives the Esc itself
88/// back, so whatever Esc is bound to on its own still has a spelling.
89#[derive(Default, Debug)]
90pub struct EscPrefix {
91    pending: bool,
92}
93
94impl EscPrefix {
95    /// The key to dispatch, or None while an Esc waits for its second
96    /// half.
97    pub fn apply(&mut self, key: KeyEvent) -> Option<KeyEvent> {
98        let bare_esc = key.code == KeyCode::Esc && key.modifiers == KeyModifiers::NONE;
99        if std::mem::take(&mut self.pending) {
100            if bare_esc {
101                return Some(key);
102            }
103            return Some(KeyEvent::new(key.code, key.modifiers | KeyModifiers::ALT));
104        }
105        if bare_esc {
106            self.pending = true;
107            return None;
108        }
109        Some(key)
110    }
111
112    /// Drop a held Esc: the menu it was typed in is gone.
113    pub fn clear(&mut self) {
114        self.pending = false;
115    }
116
117    pub fn is_pending(&self) -> bool {
118        self.pending
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn modifier_sets() {
128        let m = KeyModifiers::CONTROL | KeyModifiers::SHIFT;
129        assert!(m.contains(KeyModifiers::CONTROL));
130        assert!(m.contains(KeyModifiers::SHIFT));
131        assert!(!m.contains(KeyModifiers::ALT));
132        assert!(m.contains(KeyModifiers::NONE));
133        assert_eq!(
134            m & (KeyModifiers::CONTROL | KeyModifiers::ALT),
135            KeyModifiers::CONTROL
136        );
137        let mut n = KeyModifiers::NONE;
138        n |= KeyModifiers::ALT;
139        assert_eq!(n, KeyModifiers::ALT);
140    }
141
142    #[test]
143    fn esc_then_a_key_is_that_key_with_alt() {
144        let mut esc = EscPrefix::default();
145        let plain = |c| KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE);
146        let alt = |c| KeyEvent::new(KeyCode::Char(c), KeyModifiers::ALT);
147        let bare_esc = KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE);
148        assert_eq!(esc.apply(bare_esc), None);
149        assert!(esc.is_pending());
150        assert_eq!(esc.apply(plain('/')), Some(alt('/')));
151        // Only the one key after it: the next one is plain again.
152        assert_eq!(esc.apply(plain('/')), Some(plain('/')));
153        // Already Alt stays Alt.
154        assert_eq!(esc.apply(alt('/')), Some(alt('/')));
155        // Esc Esc is the Esc itself.
156        assert_eq!(esc.apply(bare_esc), None);
157        assert_eq!(esc.apply(bare_esc), Some(bare_esc));
158        // A held Esc can be dropped.
159        assert_eq!(esc.apply(bare_esc), None);
160        esc.clear();
161        assert_eq!(esc.apply(plain('a')), Some(plain('a')));
162        // Tab keeps its own modifiers and gains Alt (mutt's Esc Tab).
163        esc.apply(bare_esc);
164        assert_eq!(
165            esc.apply(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE)),
166            Some(KeyEvent::new(KeyCode::Tab, KeyModifiers::ALT))
167        );
168    }
169}