Skip to main content

textual_rs/event/
keybinding.rs

1//! Key binding declarations and matching for widget keyboard shortcut handling.
2
3use crossterm::event::{KeyCode, KeyModifiers};
4
5/// A key binding declaration on a widget.
6/// Widgets return these from `key_bindings()` to declare keyboard shortcuts.
7#[derive(Debug, Clone)]
8pub struct KeyBinding {
9    /// The key that triggers this binding.
10    pub key: KeyCode,
11    /// Required modifier keys (e.g., CONTROL, SHIFT). Use KeyModifiers::NONE for no modifiers.
12    pub modifiers: KeyModifiers,
13    /// Action string dispatched to on_action when this binding fires.
14    pub action: &'static str,
15    /// Human-readable description (shown in Footer widget).
16    pub description: &'static str,
17    /// Whether to display this binding in the Footer. Set false for internal bindings.
18    pub show: bool,
19}
20
21impl KeyBinding {
22    /// Check if a key event matches this binding.
23    pub fn matches(&self, key: KeyCode, modifiers: KeyModifiers) -> bool {
24        self.key == key && self.modifiers == modifiers
25    }
26}
27
28#[cfg(test)]
29mod tests {
30    use super::*;
31    use crossterm::event::{KeyCode, KeyModifiers};
32
33    #[test]
34    fn keybinding_holds_fields() {
35        let kb = KeyBinding {
36            key: KeyCode::Char('s'),
37            modifiers: KeyModifiers::CONTROL,
38            action: "save",
39            description: "Save the file",
40            show: true,
41        };
42        assert_eq!(kb.key, KeyCode::Char('s'));
43        assert_eq!(kb.modifiers, KeyModifiers::CONTROL);
44        assert_eq!(kb.action, "save");
45        assert_eq!(kb.description, "Save the file");
46        assert!(kb.show);
47    }
48
49    #[test]
50    fn matches_returns_true_for_exact_match() {
51        let kb = KeyBinding {
52            key: KeyCode::Char('s'),
53            modifiers: KeyModifiers::CONTROL,
54            action: "save",
55            description: "Save",
56            show: true,
57        };
58        assert!(kb.matches(KeyCode::Char('s'), KeyModifiers::CONTROL));
59    }
60
61    #[test]
62    fn matches_returns_false_for_wrong_key() {
63        let kb = KeyBinding {
64            key: KeyCode::Char('s'),
65            modifiers: KeyModifiers::CONTROL,
66            action: "save",
67            description: "Save",
68            show: true,
69        };
70        assert!(!kb.matches(KeyCode::Char('x'), KeyModifiers::CONTROL));
71    }
72
73    #[test]
74    fn matches_returns_false_for_wrong_modifier() {
75        let kb = KeyBinding {
76            key: KeyCode::Char('s'),
77            modifiers: KeyModifiers::CONTROL,
78            action: "save",
79            description: "Save",
80            show: true,
81        };
82        assert!(!kb.matches(KeyCode::Char('s'), KeyModifiers::NONE));
83    }
84
85    #[test]
86    fn matches_no_modifiers() {
87        let kb = KeyBinding {
88            key: KeyCode::Enter,
89            modifiers: KeyModifiers::NONE,
90            action: "submit",
91            description: "Submit",
92            show: false,
93        };
94        assert!(kb.matches(KeyCode::Enter, KeyModifiers::NONE));
95        assert!(!kb.matches(KeyCode::Enter, KeyModifiers::SHIFT));
96    }
97}