Skip to main content

wisp/
keybindings.rs

1use tui::{KeyCode, KeyEvent, KeyModifiers};
2
3#[doc = include_str!("docs/key_binding.md")]
4#[derive(Clone, Debug)]
5pub struct KeyBinding {
6    pub code: KeyCode,
7    pub modifiers: KeyModifiers,
8}
9
10impl KeyBinding {
11    pub fn new(code: KeyCode, modifiers: KeyModifiers) -> Self {
12        Self { code, modifiers }
13    }
14
15    pub fn matches(&self, event: KeyEvent) -> bool {
16        self.code == event.code && event.modifiers.contains(self.modifiers)
17    }
18
19    pub fn char(&self) -> Option<char> {
20        match self.code {
21            KeyCode::Char(c) => Some(c),
22            _ => None,
23        }
24    }
25}
26
27#[doc = include_str!("docs/keybindings.md")]
28#[derive(Clone, Debug)]
29pub struct Keybindings {
30    pub exit: KeyBinding,
31    pub cancel: KeyBinding,
32    pub cycle_reasoning: KeyBinding,
33    pub cycle_mode: KeyBinding,
34    pub submit: KeyBinding,
35    pub open_command_picker: KeyBinding,
36    pub open_file_picker: KeyBinding,
37    pub toggle_git_diff: KeyBinding,
38    pub open_prompt_search: KeyBinding,
39}
40
41impl Default for Keybindings {
42    fn default() -> Self {
43        Self {
44            exit: KeyBinding::new(KeyCode::Char('c'), KeyModifiers::CONTROL),
45            cancel: KeyBinding::new(KeyCode::Esc, KeyModifiers::NONE),
46            cycle_reasoning: KeyBinding::new(KeyCode::Tab, KeyModifiers::NONE),
47            cycle_mode: KeyBinding::new(KeyCode::BackTab, KeyModifiers::NONE),
48            submit: KeyBinding::new(KeyCode::Enter, KeyModifiers::NONE),
49            open_command_picker: KeyBinding::new(KeyCode::Char('/'), KeyModifiers::NONE),
50            open_file_picker: KeyBinding::new(KeyCode::Char('@'), KeyModifiers::NONE),
51            toggle_git_diff: KeyBinding::new(KeyCode::Char('g'), KeyModifiers::CONTROL),
52            open_prompt_search: KeyBinding::new(KeyCode::Char('r'), KeyModifiers::CONTROL),
53        }
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    fn key(code: KeyCode) -> KeyEvent {
62        KeyEvent::new(code, KeyModifiers::NONE)
63    }
64
65    #[test]
66    fn matches_simple_key() {
67        let binding = KeyBinding::new(KeyCode::Tab, KeyModifiers::NONE);
68        assert!(binding.matches(key(KeyCode::Tab)));
69        assert!(!binding.matches(key(KeyCode::Enter)));
70    }
71
72    #[test]
73    fn matches_key_with_modifier() {
74        let binding = KeyBinding::new(KeyCode::Char('c'), KeyModifiers::CONTROL);
75        assert!(binding.matches(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL)));
76        assert!(!binding.matches(key(KeyCode::Char('c'))));
77    }
78
79    #[test]
80    fn matches_ignores_extra_modifiers() {
81        let binding = KeyBinding::new(KeyCode::Char('c'), KeyModifiers::CONTROL);
82        let event = KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL | KeyModifiers::SHIFT);
83        assert!(binding.matches(event));
84    }
85
86    #[test]
87    fn char_returns_char_for_char_key() {
88        let binding = KeyBinding::new(KeyCode::Char('/'), KeyModifiers::NONE);
89        assert_eq!(binding.char(), Some('/'));
90    }
91
92    #[test]
93    fn char_returns_none_for_non_char_key() {
94        let binding = KeyBinding::new(KeyCode::Enter, KeyModifiers::NONE);
95        assert_eq!(binding.char(), None);
96    }
97
98    #[test]
99    fn default_keybindings_have_expected_values() {
100        let kb = Keybindings::default();
101        assert_eq!(kb.exit.code, KeyCode::Char('c'));
102        assert_eq!(kb.exit.modifiers, KeyModifiers::CONTROL);
103        assert_eq!(kb.cancel.code, KeyCode::Esc);
104        assert_eq!(kb.cycle_reasoning.code, KeyCode::Tab);
105        assert_eq!(kb.cycle_mode.code, KeyCode::BackTab);
106        assert_eq!(kb.submit.code, KeyCode::Enter);
107        assert_eq!(kb.open_command_picker.code, KeyCode::Char('/'));
108        assert_eq!(kb.open_file_picker.code, KeyCode::Char('@'));
109        assert_eq!(kb.toggle_git_diff.code, KeyCode::Char('g'));
110        assert_eq!(kb.toggle_git_diff.modifiers, KeyModifiers::CONTROL);
111        assert_eq!(kb.open_prompt_search.code, KeyCode::Char('r'));
112        assert_eq!(kb.open_prompt_search.modifiers, KeyModifiers::CONTROL);
113    }
114}