Skip to main content

twrite_core/
keycode.rs

1//! Structured key identity for hooks, hints, and keymaps.
2//!
3//! GPUI hands out key *strings* ([`gpui::Keystroke`]); this module interprets
4//! them once, at the translation boundary
5//! (`twrite_gpui::translate_key_down`). Everything downstream — hook
6//! matching, menu hints, vim-style sequences — speaks [`KeyCode`], so
7//! inconsistent spellings (`"arrowup"` vs `"up"`, `"ctrl + l"` vs `"Ctrl+U"`)
8//! are impossible by construction.
9
10use std::fmt;
11
12/// A single key, crossterm-style: one [`KeyCode::Char`] variant absorbs all
13/// printable characters instead of per-letter variants.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum KeyCode {
16    /// Layout-aware produced character (`'a'`, `'A'`, `'?'`, `' '`).
17    ///
18    /// Space is a `Char(' ')`, not a named variant - there is no invisible
19    /// `" "` string to confuse with emptiness.
20    Char(char),
21    /// The Enter / Return key.
22    Enter,
23    /// The Tab key.
24    Tab,
25    /// The Escape key.
26    Escape,
27    /// The Backspace key.
28    Backspace,
29    /// The Delete / Forward-delete key.
30    Delete,
31    /// The Insert key.
32    Insert,
33    /// Arrow up.
34    Up,
35    /// Arrow down.
36    Down,
37    /// Arrow left.
38    Left,
39    /// Arrow right.
40    Right,
41    /// The Home key.
42    Home,
43    /// The End key.
44    End,
45    /// The Page Up key.
46    PageUp,
47    /// The Page Down key.
48    PageDown,
49    /// A function key (`F(1)` is F1).
50    F(u8),
51    /// A platform key with no mapping. Matches must pass it through, never
52    /// consume it: it carries no actionable meaning.
53    Unidentified,
54}
55
56impl KeyCode {
57    /// Canonical display form for kbd chips and logs: arrows as glyphs,
58    /// well-known names as words, `F(n)` uppercased, characters verbatim,
59    /// unidentified keys as `?`.
60    pub fn display(&self) -> String {
61        match self {
62            KeyCode::Char(c) => c.to_string(),
63            KeyCode::Enter => "Enter".to_string(),
64            KeyCode::Tab => "Tab".to_string(),
65            KeyCode::Escape => "Esc".to_string(),
66            KeyCode::Backspace => "Backspace".to_string(),
67            KeyCode::Delete => "Delete".to_string(),
68            KeyCode::Insert => "Insert".to_string(),
69            KeyCode::Up => "↑".to_string(),
70            KeyCode::Down => "↓".to_string(),
71            KeyCode::Left => "←".to_string(),
72            KeyCode::Right => "→".to_string(),
73            KeyCode::Home => "Home".to_string(),
74            KeyCode::End => "End".to_string(),
75            KeyCode::PageUp => "PgUp".to_string(),
76            KeyCode::PageDown => "PgDn".to_string(),
77            KeyCode::F(n) => format!("F{n}"),
78            KeyCode::Unidentified => "?".to_string(),
79        }
80    }
81}
82
83impl fmt::Display for KeyCode {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        write!(f, "{}", self.display())
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn display_covers_named_keys() {
95        assert_eq!(KeyCode::Enter.to_string(), "Enter");
96        assert_eq!(KeyCode::Escape.to_string(), "Esc");
97        assert_eq!(KeyCode::Backspace.to_string(), "Backspace");
98        assert_eq!(KeyCode::Delete.to_string(), "Delete");
99        assert_eq!(KeyCode::Up.to_string(), "↑");
100        assert_eq!(KeyCode::Left.to_string(), "←");
101        assert_eq!(KeyCode::F(5).to_string(), "F5");
102        assert_eq!(KeyCode::F(12).to_string(), "F12");
103    }
104
105    #[test]
106    fn display_chars_verbatim() {
107        assert_eq!(KeyCode::Char('a').to_string(), "a");
108        assert_eq!(KeyCode::Char(' ').to_string(), " ");
109        assert_eq!(KeyCode::Char('?').to_string(), "?");
110    }
111
112    #[test]
113    fn display_unidentified_is_placeholder() {
114        assert_eq!(KeyCode::Unidentified.to_string(), "?");
115    }
116}