1use crate::event::KeyEvent;
4use crossterm::event::{KeyCode, KeyModifiers};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8pub struct KeyBinding {
9 pub code: KeyCode,
10 pub modifiers: KeyModifiers,
11}
12
13impl KeyBinding {
14 pub fn new(code: KeyCode) -> Self {
15 Self {
16 code,
17 modifiers: KeyModifiers::NONE,
18 }
19 }
20
21 pub fn ctrl(code: KeyCode) -> Self {
22 Self {
23 code,
24 modifiers: KeyModifiers::CONTROL,
25 }
26 }
27
28 pub fn alt(code: KeyCode) -> Self {
29 Self {
30 code,
31 modifiers: KeyModifiers::ALT,
32 }
33 }
34
35 pub fn shift(code: KeyCode) -> Self {
36 Self {
37 code,
38 modifiers: KeyModifiers::SHIFT,
39 }
40 }
41
42 pub fn with_modifiers(code: KeyCode, modifiers: KeyModifiers) -> Self {
43 Self { code, modifiers }
44 }
45
46 pub fn matches(&self, event: &KeyEvent) -> bool {
47 self.code == event.code && self.modifiers == event.modifiers
48 }
49
50 pub fn display(&self) -> String {
51 let mut parts = Vec::new();
52 if self.modifiers.contains(KeyModifiers::CONTROL) {
53 parts.push("Ctrl".to_string());
54 }
55 if self.modifiers.contains(KeyModifiers::ALT) {
56 parts.push("Alt".to_string());
57 }
58 if self.modifiers.contains(KeyModifiers::SHIFT) {
59 parts.push("Shift".to_string());
60 }
61 parts.push(key_name(self.code));
62 parts.join("+")
63 }
64}
65
66impl From<&KeyEvent> for KeyBinding {
67 fn from(event: &KeyEvent) -> Self {
68 Self {
69 code: event.code,
70 modifiers: event.modifiers,
71 }
72 }
73}
74
75pub struct Keymap<A: Clone> {
76 bindings: HashMap<KeyBinding, (A, String)>,
77}
78
79impl<A: Clone> Keymap<A> {
80 pub fn new() -> Self {
81 Self {
82 bindings: HashMap::new(),
83 }
84 }
85
86 pub fn bind(mut self, key: KeyBinding, action: A, description: &str) -> Self {
87 self.bindings.insert(key, (action, description.to_string()));
88 self
89 }
90
91 pub fn register(&mut self, key: KeyBinding, action: A, description: &str) {
92 self.bindings.insert(key, (action, description.to_string()));
93 }
94
95 pub fn unbind(&mut self, key: &KeyBinding) {
96 self.bindings.remove(key);
97 }
98
99 pub fn resolve(&self, event: &KeyEvent) -> Option<A> {
100 let binding = KeyBinding::from(event);
101 self.bindings
102 .get(&binding)
103 .map(|(action, _)| action.clone())
104 }
105
106 pub fn help(&self) -> Vec<(String, String)> {
107 self.bindings
108 .iter()
109 .map(|(key, (_, desc))| (key.display(), desc.clone()))
110 .collect()
111 }
112}
113
114impl<A: Clone> Default for Keymap<A> {
115 fn default() -> Self {
116 Self::new()
117 }
118}
119
120fn key_name(code: KeyCode) -> String {
121 match code {
122 KeyCode::Char(c) => c.to_string(),
123 KeyCode::Enter => "Enter".to_string(),
124 KeyCode::Esc => "Esc".to_string(),
125 KeyCode::Backspace => "Backspace".to_string(),
126 KeyCode::Tab => "Tab".to_string(),
127 KeyCode::Delete => "Delete".to_string(),
128 KeyCode::Up => "Up".to_string(),
129 KeyCode::Down => "Down".to_string(),
130 KeyCode::Left => "Left".to_string(),
131 KeyCode::Right => "Right".to_string(),
132 KeyCode::Home => "Home".to_string(),
133 KeyCode::End => "End".to_string(),
134 KeyCode::PageUp => "PageUp".to_string(),
135 KeyCode::PageDown => "PageDown".to_string(),
136 KeyCode::F(n) => format!("F{}", n),
137 _ => "?".to_string(),
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[derive(Clone, Debug, PartialEq)]
146 enum Action {
147 Quit,
148 Save,
149 Help,
150 }
151
152 #[test]
153 fn keybinding_matches_event() {
154 let binding = KeyBinding::new(KeyCode::Char('q'));
155 let event = KeyEvent {
156 code: KeyCode::Char('q'),
157 modifiers: KeyModifiers::NONE,
158 };
159 assert!(binding.matches(&event));
160 }
161
162 #[test]
163 fn keybinding_ctrl_modifier() {
164 let binding = KeyBinding::ctrl(KeyCode::Char('c'));
165 let event = KeyEvent {
166 code: KeyCode::Char('c'),
167 modifiers: KeyModifiers::CONTROL,
168 };
169 assert!(binding.matches(&event));
170
171 let plain = KeyEvent {
172 code: KeyCode::Char('c'),
173 modifiers: KeyModifiers::NONE,
174 };
175 assert!(!binding.matches(&plain));
176 }
177
178 #[test]
179 fn keybinding_display() {
180 assert_eq!(KeyBinding::new(KeyCode::Char('q')).display(), "q");
181 assert_eq!(KeyBinding::ctrl(KeyCode::Char('c')).display(), "Ctrl+c");
182 assert_eq!(KeyBinding::alt(KeyCode::Char('x')).display(), "Alt+x");
183 assert_eq!(KeyBinding::new(KeyCode::F(1)).display(), "F1");
184 }
185
186 #[test]
187 fn keymap_resolve() {
188 let keymap = Keymap::new()
189 .bind(KeyBinding::new(KeyCode::Char('q')), Action::Quit, "Quit")
190 .bind(KeyBinding::ctrl(KeyCode::Char('s')), Action::Save, "Save");
191
192 let event = KeyEvent {
193 code: KeyCode::Char('q'),
194 modifiers: KeyModifiers::NONE,
195 };
196 assert_eq!(keymap.resolve(&event), Some(Action::Quit));
197
198 let event2 = KeyEvent {
199 code: KeyCode::Char('s'),
200 modifiers: KeyModifiers::CONTROL,
201 };
202 assert_eq!(keymap.resolve(&event2), Some(Action::Save));
203 }
204
205 #[test]
206 fn keymap_resolve_unbound_returns_none() {
207 let keymap: Keymap<Action> = Keymap::new();
208 let event = KeyEvent {
209 code: KeyCode::Char('z'),
210 modifiers: KeyModifiers::NONE,
211 };
212 assert_eq!(keymap.resolve(&event), None);
213 }
214
215 #[test]
216 fn keymap_unbind() {
217 let mut keymap =
218 Keymap::new().bind(KeyBinding::new(KeyCode::Char('q')), Action::Quit, "Quit");
219 keymap.unbind(&KeyBinding::new(KeyCode::Char('q')));
220 let event = KeyEvent {
221 code: KeyCode::Char('q'),
222 modifiers: KeyModifiers::NONE,
223 };
224 assert_eq!(keymap.resolve(&event), None);
225 }
226
227 #[test]
228 fn keymap_help_lists_bindings() {
229 let keymap = Keymap::new()
230 .bind(
231 KeyBinding::new(KeyCode::Char('q')),
232 Action::Quit,
233 "Quit app",
234 )
235 .bind(
236 KeyBinding::new(KeyCode::Char('?')),
237 Action::Help,
238 "Show help",
239 );
240 let help = keymap.help();
241 assert_eq!(help.len(), 2);
242 }
243}