My personal-knowledge-system, with deeply integrated task tracking and long term goal planning capabilities.
at tui 209 lines 6.2 kB view raw
1use std::{ 2 collections::HashMap, 3 ops::{Deref, DerefMut}, 4}; 5 6use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; 7use kdl::KdlNode; 8use strum::IntoEnumIterator; 9 10use crate::{app::Region, signal::Signal}; 11 12#[derive(Debug, Clone)] 13pub struct KeyMap(pub HashMap<Region, HashMap<Vec<KeyEvent>, Signal>>); 14 15impl TryFrom<&KdlNode> for KeyMap { 16 type Error = color_eyre::Report; 17 18 fn try_from(value: &KdlNode) -> std::result::Result<Self, Self::Error> { 19 let mut all_binds = HashMap::new(); 20 21 for region in Region::iter() { 22 let mut region_binds = HashMap::new(); 23 let Some(binds) = value 24 .children() 25 .expect("Keymap must have children.") 26 .get(&region.to_string()) 27 else { 28 continue; 29 }; 30 31 // now we iter through the things children 32 for child in binds.iter_children() { 33 let key_combo_str = child.name().to_string(); 34 let key_combo_str = key_combo_str.trim(); 35 36 let signal_str = child 37 .entries() 38 .first() 39 .expect("A bind must map to an entry") 40 .to_string(); 41 let signal_str = signal_str.trim(); 42 43 let signal: Signal = signal_str.parse().expect("Must be a \"bindable\" Signal"); 44 let key_combo = parse_key_sequence(key_combo_str).unwrap(); 45 46 let _ = region_binds.insert(key_combo, signal); 47 } 48 49 let _ = all_binds.insert(region, region_binds); 50 } 51 52 Ok(Self(all_binds)) 53 } 54} 55 56impl Deref for KeyMap { 57 type Target = HashMap<Region, HashMap<Vec<KeyEvent>, Signal>>; 58 59 fn deref(&self) -> &Self::Target { 60 &self.0 61 } 62} 63 64impl DerefMut for KeyMap { 65 fn deref_mut(&mut self) -> &mut Self::Target { 66 &mut self.0 67 } 68} 69 70pub fn parse_key_sequence(raw: &str) -> color_eyre::Result<Vec<KeyEvent>, String> { 71 if raw.chars().filter(|c| *c == '>').count() != raw.chars().filter(|c| *c == '<').count() { 72 return Err(format!("Unable to parse `{raw}`")); 73 } 74 let raw = if raw.contains("><") { 75 raw 76 } else { 77 let raw = raw.strip_prefix('<').unwrap_or(raw); 78 79 raw.strip_prefix('>').unwrap_or(raw) 80 }; 81 82 raw.split("><") 83 .map(|seq| { 84 seq.strip_prefix('<') 85 .unwrap_or_else(|| seq.strip_suffix('>').map_or(seq, |s| s)) 86 }) 87 .map(parse_key_event) 88 .collect() 89} 90 91fn parse_key_event(raw: &str) -> color_eyre::Result<KeyEvent, String> { 92 let raw_lower = raw.to_ascii_lowercase(); 93 let (remaining, modifiers) = extract_modifiers(&raw_lower); 94 parse_key_code_with_modifiers(remaining, modifiers) 95} 96 97fn extract_modifiers(raw: &str) -> (&str, KeyModifiers) { 98 let mut modifiers = KeyModifiers::empty(); 99 let mut current = raw; 100 101 loop { 102 match current { 103 rest if rest.starts_with("ctrl-") => { 104 modifiers.insert(KeyModifiers::CONTROL); 105 current = &rest[5..]; 106 } 107 rest if rest.starts_with("alt-") => { 108 modifiers.insert(KeyModifiers::ALT); 109 current = &rest[4..]; 110 } 111 rest if rest.starts_with("shift-") => { 112 modifiers.insert(KeyModifiers::SHIFT); 113 current = &rest[6..]; 114 } 115 _ => break, // break out of the loop if no known prefix is detected 116 } 117 } 118 119 (current, modifiers) 120} 121 122fn parse_key_code_with_modifiers( 123 raw: &str, 124 mut modifiers: KeyModifiers, 125) -> color_eyre::Result<KeyEvent, String> { 126 let c = match raw { 127 "esc" => KeyCode::Esc, 128 "enter" => KeyCode::Enter, 129 "left" => KeyCode::Left, 130 "right" => KeyCode::Right, 131 "up" => KeyCode::Up, 132 "down" => KeyCode::Down, 133 "home" => KeyCode::Home, 134 "end" => KeyCode::End, 135 "pageup" => KeyCode::PageUp, 136 "pagedown" => KeyCode::PageDown, 137 "backtab" => { 138 modifiers.insert(KeyModifiers::SHIFT); 139 KeyCode::BackTab 140 } 141 "backspace" => KeyCode::Backspace, 142 "delete" => KeyCode::Delete, 143 "insert" => KeyCode::Insert, 144 "f1" => KeyCode::F(1), 145 "f2" => KeyCode::F(2), 146 "f3" => KeyCode::F(3), 147 "f4" => KeyCode::F(4), 148 "f5" => KeyCode::F(5), 149 "f6" => KeyCode::F(6), 150 "f7" => KeyCode::F(7), 151 "f8" => KeyCode::F(8), 152 "f9" => KeyCode::F(9), 153 "f10" => KeyCode::F(10), 154 "f11" => KeyCode::F(11), 155 "f12" => KeyCode::F(12), 156 "space" => KeyCode::Char(' '), 157 "hyphen" | "minuc" => KeyCode::Char('-'), 158 "tab" => KeyCode::Tab, 159 c if c.len() == 1 => { 160 let mut c = c.chars().next().unwrap(); 161 if modifiers.contains(KeyModifiers::SHIFT) { 162 c = c.to_ascii_uppercase(); 163 } 164 KeyCode::Char(c) 165 } 166 _ => return Err(format!("Unable to parse {raw}")), 167 }; 168 Ok(KeyEvent::new(c, modifiers)) 169} 170 171#[cfg(test)] 172mod test { 173 use crossterm::event::{KeyEvent, KeyModifiers}; 174 use kdl::KdlNode; 175 176 use crate::{keymap::KeyMap, signal::Signal}; 177 178 #[test] 179 fn test_quit_in_home_region() { 180 let keymap_str = " 181 keymap { 182 Home { 183 q Quit 184 <Ctrl-C> Quit 185 } 186 } 187 "; 188 189 let kdl: &KdlNode = &keymap_str 190 .parse() 191 .expect("Keymap_str should be a valid KDL document"); 192 193 let keymap: KeyMap = kdl.try_into().expect("Must be a valid keymap"); 194 195 let map = keymap 196 .get(&crate::app::Region::Home) 197 .expect("Home region must exist in keymap"); 198 199 let signal = map 200 .get(&vec![KeyEvent::new_with_kind( 201 crossterm::event::KeyCode::Char('q'), 202 KeyModifiers::empty(), 203 crossterm::event::KeyEventKind::Press, 204 )]) 205 .expect("Must resolve to a signal"); 206 207 assert_eq!(*signal, Signal::Quit); 208 } 209}