Skip to main content

void_audit_tui/
keybind.rs

1//! Keybinding system for the audit TUI.
2//!
3//! Adapted from void-graph TUI, which was based on
4//! [Serie](https://github.com/lusingander/serie) by lusingander.
5
6use std::ops::{Deref, DerefMut};
7
8use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
9use rustc_hash::FxHashMap;
10use serde::{
11    de::{Deserializer, Error as DeError},
12    Deserialize,
13};
14
15use crate::event::UserEvent;
16
17const DEFAULT_KEY_BIND: &str = include_str!("../assets/default-keybind.toml");
18
19#[derive(Debug, Default, Clone, PartialEq, Eq)]
20pub struct KeyBind(FxHashMap<KeyEvent, UserEvent>);
21
22impl Deref for KeyBind {
23    type Target = FxHashMap<KeyEvent, UserEvent>;
24
25    fn deref(&self) -> &Self::Target {
26        &self.0
27    }
28}
29
30impl DerefMut for KeyBind {
31    fn deref_mut(&mut self) -> &mut Self::Target {
32        &mut self.0
33    }
34}
35
36impl KeyBind {
37    pub fn new() -> Self {
38        toml::from_str(DEFAULT_KEY_BIND).expect("default key bind should be correct")
39    }
40
41    pub fn keys_for_event(&self, user_event: UserEvent) -> Vec<String> {
42        let mut key_events: Vec<KeyEvent> = self
43            .iter()
44            .filter(|(_, ue)| **ue == user_event)
45            .map(|(ke, _)| *ke)
46            .collect();
47        key_events.sort_by(|a, b| a.partial_cmp(b).unwrap());
48        key_events.into_iter().map(key_event_to_string).collect()
49    }
50}
51
52impl<'de> Deserialize<'de> for KeyBind {
53    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
54    where
55        D: Deserializer<'de>,
56    {
57        let parsed_map = FxHashMap::<UserEvent, Vec<String>>::deserialize(deserializer)?;
58        let mut key_map = FxHashMap::<KeyEvent, UserEvent>::default();
59        for (user_event, key_events) in parsed_map {
60            for key_event_str in key_events {
61                let key_event = match parse_key_event(&key_event_str) {
62                    Ok(e) => e,
63                    Err(s) => {
64                        let msg = format!("{key_event_str:?} is not a valid key event: {s:}");
65                        return Err(DeError::custom(msg));
66                    }
67                };
68                if let Some(conflict_user_event) = key_map.insert(key_event, user_event) {
69                    let msg = format!(
70                        "{key_event:?} map to multiple events: {user_event:?}, {conflict_user_event:?}"
71                    );
72                    return Err(DeError::custom(msg));
73                }
74            }
75        }
76
77        Ok(KeyBind(key_map))
78    }
79}
80
81fn parse_key_event(raw: &str) -> Result<KeyEvent, String> {
82    let raw_lower = raw.to_ascii_lowercase().replace(' ', "");
83    let (remaining, modifiers) = extract_modifiers(&raw_lower);
84    parse_key_code_with_modifiers(remaining, modifiers)
85}
86
87fn extract_modifiers(raw: &str) -> (&str, KeyModifiers) {
88    let mut modifiers = KeyModifiers::empty();
89    let mut current = raw;
90
91    loop {
92        match current {
93            rest if rest.starts_with("ctrl-") => {
94                modifiers.insert(KeyModifiers::CONTROL);
95                current = &rest[5..];
96            }
97            rest if rest.starts_with("alt-") => {
98                modifiers.insert(KeyModifiers::ALT);
99                current = &rest[4..];
100            }
101            rest if rest.starts_with("shift-") => {
102                modifiers.insert(KeyModifiers::SHIFT);
103                current = &rest[6..];
104            }
105            _ => break,
106        };
107    }
108
109    (current, modifiers)
110}
111
112fn parse_key_code_with_modifiers(
113    raw: &str,
114    mut modifiers: KeyModifiers,
115) -> Result<KeyEvent, String> {
116    let c = match raw {
117        "esc" => KeyCode::Esc,
118        "enter" => KeyCode::Enter,
119        "left" => KeyCode::Left,
120        "right" => KeyCode::Right,
121        "up" => KeyCode::Up,
122        "down" => KeyCode::Down,
123        "home" => KeyCode::Home,
124        "end" => KeyCode::End,
125        "pageup" => KeyCode::PageUp,
126        "pagedown" => KeyCode::PageDown,
127        "backtab" => {
128            modifiers.insert(KeyModifiers::SHIFT);
129            KeyCode::BackTab
130        }
131        "backspace" => KeyCode::Backspace,
132        "delete" => KeyCode::Delete,
133        "insert" => KeyCode::Insert,
134        "f1" => KeyCode::F(1),
135        "f2" => KeyCode::F(2),
136        "f3" => KeyCode::F(3),
137        "f4" => KeyCode::F(4),
138        "f5" => KeyCode::F(5),
139        "f6" => KeyCode::F(6),
140        "f7" => KeyCode::F(7),
141        "f8" => KeyCode::F(8),
142        "f9" => KeyCode::F(9),
143        "f10" => KeyCode::F(10),
144        "f11" => KeyCode::F(11),
145        "f12" => KeyCode::F(12),
146        "space" => KeyCode::Char(' '),
147        "hyphen" => KeyCode::Char('-'),
148        "minus" => KeyCode::Char('-'),
149        "tab" => KeyCode::Tab,
150        c if c.len() == 1 => {
151            let mut c = c.chars().next().unwrap();
152            if modifiers.contains(KeyModifiers::SHIFT) {
153                c = c.to_ascii_uppercase();
154            }
155            KeyCode::Char(c)
156        }
157        _ => return Err(format!("Unable to parse {raw}")),
158    };
159    Ok(KeyEvent::new(c, modifiers))
160}
161
162fn key_event_to_string(key_event: KeyEvent) -> String {
163    if let KeyCode::Char(c) = key_event.code {
164        if key_event.modifiers == KeyModifiers::SHIFT {
165            return c.to_ascii_uppercase().into();
166        }
167    }
168
169    let char;
170    let key_code = match key_event.code {
171        KeyCode::Backspace => "Backspace",
172        KeyCode::Enter => "Enter",
173        KeyCode::Left => "Left",
174        KeyCode::Right => "Right",
175        KeyCode::Up => "Up",
176        KeyCode::Down => "Down",
177        KeyCode::Home => "Home",
178        KeyCode::End => "End",
179        KeyCode::PageUp => "PageUp",
180        KeyCode::PageDown => "PageDown",
181        KeyCode::Tab => "Tab",
182        KeyCode::BackTab => "BackTab",
183        KeyCode::Delete => "Delete",
184        KeyCode::Insert => "Insert",
185        KeyCode::F(n) => {
186            char = format!("F{n}");
187            &char
188        }
189        KeyCode::Char(' ') => "Space",
190        KeyCode::Char(c) => {
191            char = c.to_string();
192            &char
193        }
194        KeyCode::Esc => "Esc",
195        KeyCode::Null => "",
196        KeyCode::CapsLock => "",
197        KeyCode::Menu => "",
198        KeyCode::ScrollLock => "",
199        KeyCode::Media(_) => "",
200        KeyCode::NumLock => "",
201        KeyCode::PrintScreen => "",
202        KeyCode::Pause => "",
203        KeyCode::KeypadBegin => "",
204        KeyCode::Modifier(_) => "",
205    };
206
207    let mut modifiers_vec = Vec::with_capacity(3);
208
209    if key_event.modifiers.intersects(KeyModifiers::CONTROL) {
210        modifiers_vec.push("Ctrl");
211    }
212
213    if key_event.modifiers.intersects(KeyModifiers::SHIFT) {
214        modifiers_vec.push("Shift");
215    }
216
217    if key_event.modifiers.intersects(KeyModifiers::ALT) {
218        modifiers_vec.push("Alt");
219    }
220
221    let mut key = modifiers_vec.join("-");
222
223    if !key.is_empty() {
224        key.push('-');
225    }
226    key.push_str(key_code);
227
228    key
229}