Skip to main content

ratatui_input_manager/
lib.rs

1#![forbid(unsafe_code)]
2#![warn(missing_docs)]
3#![doc = include_str!("../README.md")]
4
5/// Ratatui widgets displaying keybinds
6#[cfg(feature = "widgets")]
7pub mod widgets;
8
9pub use ratatui_input_manager_derive::keymap;
10use std::fmt::Display;
11
12/// A backend that provides types for key events
13pub trait Backend {
14    /// The key type used by this backend
15    type Key: 'static;
16    /// The modifier type used by this backend
17    type Modifiers: 'static;
18    /// The event type used by this backend
19    type Event;
20}
21
22/// Backend for crossterm
23#[cfg(feature = "crossterm")]
24pub struct CrosstermBackend;
25
26#[cfg(feature = "crossterm")]
27impl Backend for CrosstermBackend {
28    type Key = crossterm::event::KeyCode;
29    type Modifiers = crossterm::event::KeyModifiers;
30    type Event = crossterm::event::Event;
31}
32
33/// Backend for termion
34#[cfg(feature = "termion")]
35pub struct TermionBackend;
36
37#[cfg(feature = "termion")]
38impl Backend for TermionBackend {
39    type Key = termion::event::Key;
40    type Modifiers = ();
41    type Event = termion::event::Event;
42}
43
44/// Backend for termwiz
45#[cfg(feature = "termwiz")]
46pub struct TermwizBackend;
47
48#[cfg(feature = "termwiz")]
49impl Backend for TermwizBackend {
50    type Key = termwiz::input::KeyCode;
51    type Modifiers = termwiz::input::Modifiers;
52    type Event = termwiz::input::InputEvent;
53}
54
55/// A mapping between keybinds and methods on the type which mutate the state
56pub trait KeyMap<B: Backend + 'static> {
57    /// Metadata about the key presses which are handled
58    const KEYBINDS: &'static [KeyBind<B>];
59
60    /// Handle an event by calling the appropriate handler method
61    fn handle(&mut self, event: &B::Event) -> bool;
62}
63
64/// A dyn compatible equivalent to [`KeyMap`]
65#[expect(missing_docs)]
66pub trait DynKeyMap<B: Backend + 'static> {
67    fn keybinds(&self) -> &'static [KeyBind<B>];
68
69    fn handle(&mut self, event: &B::Event) -> bool;
70}
71
72impl<B: Backend + 'static, T: KeyMap<B>> DynKeyMap<B> for T {
73    fn keybinds(&self) -> &'static [KeyBind<B>] {
74        T::KEYBINDS
75    }
76
77    fn handle(&mut self, event: &B::Event) -> bool {
78        self.handle(event)
79    }
80}
81
82/// Key binding metadata, including the handled key presses and a description of behaviour
83pub struct KeyBind<B: Backend + 'static> {
84    /// The key press event handlded by this binding
85    pub pressed: &'static [KeyPress<B>],
86    /// A brief description of the expected behaviour
87    pub description: &'static str,
88}
89
90/// A key press event, combining a key code with modifiers
91pub struct KeyPress<B: Backend> {
92    /// The keys which is pressed
93    pub key: B::Key,
94    /// The modifiers which are activated during the key press
95    pub modifiers: B::Modifiers,
96}
97
98#[cfg(feature = "crossterm")]
99impl Display for KeyPress<CrosstermBackend> {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        let modifiers = self.modifiers;
102        write!(f, "<")?;
103        if modifiers.contains(crossterm::event::KeyModifiers::CONTROL) {
104            write!(f, "C-")?;
105        }
106        if modifiers.contains(crossterm::event::KeyModifiers::ALT) {
107            write!(f, "M-")?;
108        }
109        if modifiers.contains(crossterm::event::KeyModifiers::SUPER) {
110            write!(f, "D-")?;
111        }
112        if modifiers.contains(crossterm::event::KeyModifiers::SHIFT) {
113            write!(f, "S-")?;
114        }
115        match self.key {
116            crossterm::event::KeyCode::Backspace => write!(f, "BS"),
117            crossterm::event::KeyCode::Enter => write!(f, "CR"),
118            crossterm::event::KeyCode::Left => write!(f, "Left"),
119            crossterm::event::KeyCode::Right => write!(f, "Right"),
120            crossterm::event::KeyCode::Up => write!(f, "Up"),
121            crossterm::event::KeyCode::Down => write!(f, "Down"),
122            crossterm::event::KeyCode::Home => write!(f, "Home"),
123            crossterm::event::KeyCode::End => write!(f, "End"),
124            crossterm::event::KeyCode::PageUp => write!(f, "PageUp"),
125            crossterm::event::KeyCode::PageDown => write!(f, "PageDown"),
126            crossterm::event::KeyCode::Tab => write!(f, "Tab"),
127            crossterm::event::KeyCode::BackTab => write!(f, "S-Tab"),
128            crossterm::event::KeyCode::Delete => write!(f, "Del"),
129            crossterm::event::KeyCode::Insert => write!(f, "Insert"),
130            crossterm::event::KeyCode::F(num) => write!(f, "F{num}"),
131            crossterm::event::KeyCode::Char(c) => write!(f, "{c}"),
132            crossterm::event::KeyCode::Null => write!(f, "Nul"),
133            crossterm::event::KeyCode::Esc => write!(f, "Esc"),
134            crossterm::event::KeyCode::CapsLock => write!(f, "CapsLock"),
135            crossterm::event::KeyCode::NumLock => write!(f, "NumLock"),
136            crossterm::event::KeyCode::ScrollLock => write!(f, "ScrollLock"),
137            crossterm::event::KeyCode::PrintScreen => write!(f, "PrintScreen"),
138            crossterm::event::KeyCode::Pause => write!(f, "Pause"),
139            crossterm::event::KeyCode::Menu => write!(f, "Menu"),
140            crossterm::event::KeyCode::KeypadBegin => write!(f, "kBegin"),
141            crossterm::event::KeyCode::Media(code) => match code {
142                crossterm::event::MediaKeyCode::Play => write!(f, "MediaPlay"),
143                crossterm::event::MediaKeyCode::Pause => write!(f, "MediaPause"),
144                crossterm::event::MediaKeyCode::PlayPause => write!(f, "MediaPlayPause"),
145                crossterm::event::MediaKeyCode::Reverse => write!(f, "MediaReverse"),
146                crossterm::event::MediaKeyCode::Stop => write!(f, "MediaStop"),
147                crossterm::event::MediaKeyCode::FastForward => write!(f, "MediaFastForward"),
148                crossterm::event::MediaKeyCode::Rewind => write!(f, "MediaRewind"),
149                crossterm::event::MediaKeyCode::TrackNext => write!(f, "MediaTrackNext"),
150                crossterm::event::MediaKeyCode::TrackPrevious => write!(f, "MediaTrackPrevious"),
151                crossterm::event::MediaKeyCode::Record => write!(f, "MediaRecord"),
152                crossterm::event::MediaKeyCode::LowerVolume => write!(f, "VolumeDown"),
153                crossterm::event::MediaKeyCode::RaiseVolume => write!(f, "VolumeUp"),
154                crossterm::event::MediaKeyCode::MuteVolume => write!(f, "VolumeMute"),
155            },
156            crossterm::event::KeyCode::Modifier(code) => match code {
157                crossterm::event::ModifierKeyCode::LeftShift => write!(f, "S-Left"),
158                crossterm::event::ModifierKeyCode::LeftControl => write!(f, "C-Left"),
159                crossterm::event::ModifierKeyCode::LeftAlt => write!(f, "M-Left"),
160                crossterm::event::ModifierKeyCode::LeftSuper => write!(f, "D-Left"),
161                crossterm::event::ModifierKeyCode::LeftHyper => write!(f, "H-Left"),
162                crossterm::event::ModifierKeyCode::LeftMeta => write!(f, "Meta-Left"),
163                crossterm::event::ModifierKeyCode::RightShift => write!(f, "S-Right"),
164                crossterm::event::ModifierKeyCode::RightControl => write!(f, "C-Right"),
165                crossterm::event::ModifierKeyCode::RightAlt => write!(f, "M-Right"),
166                crossterm::event::ModifierKeyCode::RightSuper => write!(f, "D-Right"),
167                crossterm::event::ModifierKeyCode::RightHyper => write!(f, "H-Right"),
168                crossterm::event::ModifierKeyCode::RightMeta => write!(f, "Meta-Right"),
169                crossterm::event::ModifierKeyCode::IsoLevel3Shift => {
170                    write!(f, "ISO_Level3_Shift")
171                }
172                crossterm::event::ModifierKeyCode::IsoLevel5Shift => {
173                    write!(f, "ISO_Level5_Shift")
174                }
175            },
176        }?;
177        write!(f, ">")
178    }
179}
180
181#[cfg(feature = "crossterm")]
182impl std::fmt::Debug for KeyPress<CrosstermBackend> {
183    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184        f.debug_struct("KeyPress")
185            .field("key", &self.key)
186            .field("modifiers", &self.modifiers)
187            .finish()
188    }
189}
190
191#[cfg(feature = "termion")]
192impl Display for KeyPress<TermionBackend> {
193    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194        match self.key {
195            termion::event::Key::Backspace => write!(f, "<BS>"),
196            termion::event::Key::Left => write!(f, "<Left>"),
197            termion::event::Key::ShiftLeft => write!(f, "<S-Left>"),
198            termion::event::Key::AltLeft => write!(f, "<M-Left>"),
199            termion::event::Key::CtrlLeft => write!(f, "<C-Left>"),
200            termion::event::Key::Right => write!(f, "<Right>"),
201            termion::event::Key::ShiftRight => write!(f, "<S-Right>"),
202            termion::event::Key::AltRight => write!(f, "<M-Right>"),
203            termion::event::Key::CtrlRight => write!(f, "<C-Right>"),
204            termion::event::Key::Up => write!(f, "<Up>"),
205            termion::event::Key::ShiftUp => write!(f, "<S-Up>"),
206            termion::event::Key::AltUp => write!(f, "<M-Up>"),
207            termion::event::Key::CtrlUp => write!(f, "<C-Up>"),
208            termion::event::Key::Down => write!(f, "<Down>"),
209            termion::event::Key::ShiftDown => write!(f, "<S-Down>"),
210            termion::event::Key::AltDown => write!(f, "<M-Down>"),
211            termion::event::Key::CtrlDown => write!(f, "<C-Down>"),
212            termion::event::Key::Home => write!(f, "<Home>"),
213            termion::event::Key::CtrlHome => write!(f, "<C-Home>"),
214            termion::event::Key::End => write!(f, "<End>"),
215            termion::event::Key::CtrlEnd => write!(f, "<C-End>"),
216            termion::event::Key::PageUp => write!(f, "<PageUp>"),
217            termion::event::Key::PageDown => write!(f, "<PageDown>"),
218            termion::event::Key::BackTab => write!(f, "<S-Tab>"),
219            termion::event::Key::Delete => write!(f, "<Del>"),
220            termion::event::Key::Insert => write!(f, "<Insert>"),
221            termion::event::Key::F(num) => write!(f, "<F{num}>"),
222            termion::event::Key::Char(c) => write!(f, "{c}"),
223            termion::event::Key::Alt(c) => write!(f, "<M-{c}>"),
224            termion::event::Key::Ctrl(c) => write!(f, "<C-{c}>"),
225            termion::event::Key::Null => write!(f, "<Nul>"),
226            termion::event::Key::Esc => write!(f, "<Esc>"),
227            _ => write!(f, "<Unknown>"),
228        }
229    }
230}
231
232#[cfg(feature = "termion")]
233impl std::fmt::Debug for KeyPress<TermionBackend> {
234    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235        f.debug_struct("KeyPress")
236            .field("key", &self.key)
237            .field("modifiers", &self.modifiers)
238            .finish()
239    }
240}
241
242#[cfg(feature = "termwiz")]
243impl Display for KeyPress<TermwizBackend> {
244    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245        let modifiers = self.modifiers;
246        write!(f, "<")?;
247        if modifiers.contains(termwiz::input::Modifiers::CTRL) {
248            write!(f, "C-")?;
249        }
250        if modifiers.contains(termwiz::input::Modifiers::ALT) {
251            write!(f, "M-")?;
252        }
253        if modifiers.contains(termwiz::input::Modifiers::SUPER) {
254            write!(f, "D-")?;
255        }
256        if modifiers.contains(termwiz::input::Modifiers::SHIFT) {
257            write!(f, "S-")?;
258        }
259        match self.key {
260            termwiz::input::KeyCode::Char(c) => write!(f, "{c}"),
261            termwiz::input::KeyCode::Hyper => write!(f, "Hyper"),
262            termwiz::input::KeyCode::Super => write!(f, "Super"),
263            termwiz::input::KeyCode::Meta => write!(f, "M"),
264            termwiz::input::KeyCode::Cancel => write!(f, "Cancel"),
265            termwiz::input::KeyCode::Backspace => write!(f, "BS"),
266            termwiz::input::KeyCode::Tab => write!(f, "Tab"),
267            termwiz::input::KeyCode::Clear => write!(f, "Clear"),
268            termwiz::input::KeyCode::Enter => write!(f, "CR"),
269            termwiz::input::KeyCode::Shift => write!(f, "S"),
270            termwiz::input::KeyCode::Escape => write!(f, "Esc"),
271            termwiz::input::KeyCode::LeftShift => write!(f, "S-Left"),
272            termwiz::input::KeyCode::RightShift => write!(f, "S-Right"),
273            termwiz::input::KeyCode::Control => write!(f, "C"),
274            termwiz::input::KeyCode::LeftControl => write!(f, "C-Left"),
275            termwiz::input::KeyCode::RightControl => write!(f, "C-Right"),
276            termwiz::input::KeyCode::Alt => write!(f, "M"),
277            termwiz::input::KeyCode::LeftAlt => write!(f, "M-Left"),
278            termwiz::input::KeyCode::RightAlt => write!(f, "M-Right"),
279            termwiz::input::KeyCode::Menu => write!(f, "Menu"),
280            termwiz::input::KeyCode::LeftMenu => write!(f, "Menu-Left"),
281            termwiz::input::KeyCode::RightMenu => write!(f, "Menu-Right"),
282            termwiz::input::KeyCode::Pause => write!(f, "Pause"),
283            termwiz::input::KeyCode::CapsLock => write!(f, "CapsLock"),
284            termwiz::input::KeyCode::PageUp => write!(f, "PageUp"),
285            termwiz::input::KeyCode::PageDown => write!(f, "PageDown"),
286            termwiz::input::KeyCode::End => write!(f, "End"),
287            termwiz::input::KeyCode::Home => write!(f, "Home"),
288            termwiz::input::KeyCode::LeftArrow => write!(f, "Left"),
289            termwiz::input::KeyCode::RightArrow => write!(f, "Right"),
290            termwiz::input::KeyCode::UpArrow => write!(f, "Up"),
291            termwiz::input::KeyCode::DownArrow => write!(f, "Down"),
292            termwiz::input::KeyCode::Select => write!(f, "Select"),
293            termwiz::input::KeyCode::Print => write!(f, "Print"),
294            termwiz::input::KeyCode::Execute => write!(f, "Execute"),
295            termwiz::input::KeyCode::PrintScreen => write!(f, "PrintScreen"),
296            termwiz::input::KeyCode::Insert => write!(f, "Insert"),
297            termwiz::input::KeyCode::Delete => write!(f, "Del"),
298            termwiz::input::KeyCode::Help => write!(f, "Help"),
299            termwiz::input::KeyCode::LeftWindows => write!(f, "Windows-Left"),
300            termwiz::input::KeyCode::RightWindows => write!(f, "Windows-Right"),
301            termwiz::input::KeyCode::Applications => write!(f, "Apps"),
302            termwiz::input::KeyCode::Sleep => write!(f, "Sleep"),
303            termwiz::input::KeyCode::Numpad0 => write!(f, "k0"),
304            termwiz::input::KeyCode::Numpad1 => write!(f, "k1"),
305            termwiz::input::KeyCode::Numpad2 => write!(f, "k2"),
306            termwiz::input::KeyCode::Numpad3 => write!(f, "k3"),
307            termwiz::input::KeyCode::Numpad4 => write!(f, "k4"),
308            termwiz::input::KeyCode::Numpad5 => write!(f, "k5"),
309            termwiz::input::KeyCode::Numpad6 => write!(f, "k6"),
310            termwiz::input::KeyCode::Numpad7 => write!(f, "k7"),
311            termwiz::input::KeyCode::Numpad8 => write!(f, "k8"),
312            termwiz::input::KeyCode::Numpad9 => write!(f, "k9"),
313            termwiz::input::KeyCode::Multiply => write!(f, "kMultiply"),
314            termwiz::input::KeyCode::Add => write!(f, "kAdd"),
315            termwiz::input::KeyCode::Separator => write!(f, "kSeparator"),
316            termwiz::input::KeyCode::Subtract => write!(f, "kSubtract"),
317            termwiz::input::KeyCode::Decimal => write!(f, "kDecimal"),
318            termwiz::input::KeyCode::Divide => write!(f, "kDivide"),
319            termwiz::input::KeyCode::Function(n) => write!(f, "F{n}"),
320            termwiz::input::KeyCode::NumLock => write!(f, "NumLock"),
321            termwiz::input::KeyCode::ScrollLock => write!(f, "ScrollLock"),
322            termwiz::input::KeyCode::Copy => write!(f, "Copy"),
323            termwiz::input::KeyCode::Cut => write!(f, "Cut"),
324            termwiz::input::KeyCode::Paste => write!(f, "Paste"),
325            termwiz::input::KeyCode::BrowserBack => write!(f, "BrowserBack"),
326            termwiz::input::KeyCode::BrowserForward => write!(f, "BrowserForward"),
327            termwiz::input::KeyCode::BrowserRefresh => write!(f, "BrowserRefresh"),
328            termwiz::input::KeyCode::BrowserStop => write!(f, "BrowserStop"),
329            termwiz::input::KeyCode::BrowserSearch => write!(f, "BrowserSearch"),
330            termwiz::input::KeyCode::BrowserFavorites => write!(f, "BrowserFavorites"),
331            termwiz::input::KeyCode::BrowserHome => write!(f, "BrowserHome"),
332            termwiz::input::KeyCode::VolumeMute => write!(f, "VolumeMute"),
333            termwiz::input::KeyCode::VolumeDown => write!(f, "VolumeDown"),
334            termwiz::input::KeyCode::VolumeUp => write!(f, "VolumeUp"),
335            termwiz::input::KeyCode::MediaNextTrack => write!(f, "MediaNextTrack"),
336            termwiz::input::KeyCode::MediaPrevTrack => write!(f, "MediaPrevTrack"),
337            termwiz::input::KeyCode::MediaStop => write!(f, "MediaStop"),
338            termwiz::input::KeyCode::MediaPlayPause => write!(f, "MediaPlayPause"),
339            termwiz::input::KeyCode::ApplicationLeftArrow => write!(f, "App-Left"),
340            termwiz::input::KeyCode::ApplicationRightArrow => write!(f, "App-Right"),
341            termwiz::input::KeyCode::ApplicationUpArrow => write!(f, "App-Up"),
342            termwiz::input::KeyCode::ApplicationDownArrow => write!(f, "App-Down"),
343            termwiz::input::KeyCode::KeyPadHome => write!(f, "kHome"),
344            termwiz::input::KeyCode::KeyPadEnd => write!(f, "kEnd"),
345            termwiz::input::KeyCode::KeyPadPageUp => write!(f, "kPageUp"),
346            termwiz::input::KeyCode::KeyPadPageDown => write!(f, "kPageDown"),
347            termwiz::input::KeyCode::KeyPadBegin => write!(f, "kBegin"),
348            _ => write!(f, "Unknown"),
349        }?;
350        write!(f, ">")
351    }
352}