Skip to main content

rusty_bubbletea/
key.rs

1//! Cleanroom Rust port of upstream Go source file: `key.go`
2//! Upstream Target Tag / Version: `v2.0.8`
3//!
4//! <public-docs>
5//! # Key Messages & Events
6//!
7//! Key structures (`Key`, `KeyPressMsg`, `KeyReleaseMsg`), modifiers (`KeyMod`), and key symbol constants.
8//! </public-docs>
9
10use std::fmt;
11
12/// KeyMod bitflags representing modifier keys (ctrl, alt, shift, meta, hyper, super).
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
14pub struct KeyMod(pub u8);
15
16impl KeyMod {
17    // NOTE: modifier bit values match the kitty keyboard protocol bitmask
18    // (upstream tea aliases `uv.KeyMod`): shift=1, alt=2, ctrl=4, ...
19    /// Shift key modifier flag.
20    pub const SHIFT: KeyMod = KeyMod(1 << 0);
21    /// Alt key modifier flag.
22    pub const ALT: KeyMod = KeyMod(1 << 1);
23    /// Ctrl key modifier flag.
24    pub const CTRL: KeyMod = KeyMod(1 << 2);
25    /// Meta key modifier flag.
26    pub const META: KeyMod = KeyMod(1 << 3);
27    /// Hyper key modifier flag.
28    pub const HYPER: KeyMod = KeyMod(1 << 4);
29    /// Super key modifier flag.
30    pub const SUPER: KeyMod = KeyMod(1 << 5);
31    /// CapsLock key lock state flag.
32    pub const CAPS_LOCK: KeyMod = KeyMod(1 << 6);
33    /// NumLock key lock state flag.
34    pub const NUM_LOCK: KeyMod = KeyMod(1 << 7);
35
36    /// Checks if a modifier flag is contained.
37    pub fn contains(&self, flag: KeyMod) -> bool {
38        (self.0 & flag.0) != 0
39    }
40}
41
42/// Special key symbols.
43pub const KEY_UP: char = '\u{E000}';
44/// KeyDown symbol.
45pub const KEY_DOWN: char = '\u{E001}';
46/// KeyRight symbol.
47pub const KEY_RIGHT: char = '\u{E002}';
48/// KeyLeft symbol.
49pub const KEY_LEFT: char = '\u{E003}';
50/// KeyHome symbol.
51pub const KEY_HOME: char = '\u{E004}';
52/// KeyEnd symbol.
53pub const KEY_END: char = '\u{E005}';
54/// KeyPgUp symbol.
55pub const KEY_PG_UP: char = '\u{E006}';
56/// KeyPgDown symbol.
57pub const KEY_PG_DOWN: char = '\u{E007}';
58/// KeyEnter symbol.
59pub const KEY_ENTER: char = '\r';
60/// KeyTab symbol.
61pub const KEY_TAB: char = '\t';
62/// KeyBackspace symbol.
63pub const KEY_BACKSPACE: char = '\u{007F}';
64/// KeyEscape symbol.
65pub const KEY_ESCAPE: char = '\u{001B}';
66/// KeySpace symbol.
67pub const KEY_SPACE: char = ' ';
68
69/// Key represents a Key press or release event in v2.0.8.
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct Key {
72    /// Text contains actual characters received.
73    pub text: String,
74    /// Modifier keys pressed.
75    pub mod_keys: KeyMod,
76    /// Key code character/rune.
77    pub code: char,
78    /// Shifted key code.
79    pub shifted_code: Option<char>,
80    /// Base key code on US PC-101 layout.
81    pub base_code: Option<char>,
82    /// Whether key is auto-repeating.
83    pub is_repeat: bool,
84}
85
86impl Key {
87    /// Creates a new simple Key.
88    pub fn new(code: char, text: &str, mod_keys: KeyMod) -> Self {
89        Self {
90            text: text.to_string(),
91            mod_keys,
92            code,
93            shifted_code: None,
94            base_code: None,
95            is_repeat: false,
96        }
97    }
98
99    /// String representation of key event. Returns "space" for spacebar.
100    pub fn string(&self) -> String {
101        if self.code == ' ' {
102            "space".to_string()
103        } else if !self.text.is_empty() {
104            self.text.clone()
105        } else {
106            self.keystroke()
107        }
108    }
109
110    /// Keystroke representation with explicit modifier ordering (`ctrl+alt+shift...`).
111    pub fn keystroke(&self) -> String {
112        let mut parts: Vec<String> = Vec::new();
113        if self.mod_keys.contains(KeyMod::CTRL) {
114            parts.push("ctrl".to_string());
115        }
116        if self.mod_keys.contains(KeyMod::ALT) {
117            parts.push("alt".to_string());
118        }
119        if self.mod_keys.contains(KeyMod::SHIFT) {
120            parts.push("shift".to_string());
121        }
122        if self.mod_keys.contains(KeyMod::META) {
123            parts.push("meta".to_string());
124        }
125
126        let name = match self.code {
127            KEY_UP => "up".to_string(),
128            KEY_DOWN => "down".to_string(),
129            KEY_RIGHT => "right".to_string(),
130            KEY_LEFT => "left".to_string(),
131            KEY_HOME => "home".to_string(),
132            KEY_END => "end".to_string(),
133            KEY_PG_UP => "pgup".to_string(),
134            KEY_PG_DOWN => "pgdown".to_string(),
135            KEY_ENTER => "enter".to_string(),
136            KEY_TAB => "tab".to_string(),
137            KEY_BACKSPACE => "backspace".to_string(),
138            KEY_ESCAPE => "esc".to_string(),
139            ' ' => "space".to_string(),
140            c => c.to_string(),
141        };
142
143        if parts.is_empty() {
144            name
145        } else {
146            parts.push(name);
147            parts.join("+")
148        }
149    }
150}
151
152impl fmt::Display for Key {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        write!(f, "{}", self.string())
155    }
156}
157
158/// KeyPressMsg represents a key press message.
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub struct KeyPressMsg(pub Key);
161
162impl fmt::Display for KeyPressMsg {
163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        write!(f, "{}", self.0.string())
165    }
166}
167
168/// KeyReleaseMsg represents a key release message.
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub struct KeyReleaseMsg(pub Key);
171
172impl fmt::Display for KeyReleaseMsg {
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        write!(f, "{}", self.0.string())
175    }
176}
177
178/// KeyMsg trait/enum abstraction covering KeyPressMsg and KeyReleaseMsg.
179#[derive(Debug, Clone, PartialEq, Eq)]
180pub enum KeyMsg {
181    /// Key press variant.
182    Press(KeyPressMsg),
183    /// Key release variant.
184    Release(KeyReleaseMsg),
185}
186
187impl KeyMsg {
188    /// Returns the underlying Key struct.
189    pub fn key(&self) -> &Key {
190        match self {
191            KeyMsg::Press(k) => &k.0,
192            KeyMsg::Release(k) => &k.0,
193        }
194    }
195}
196
197impl fmt::Display for KeyMsg {
198    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199        write!(f, "{}", self.key().string())
200    }
201}