ricecoder_keybinds/
registry.rs1use std::collections::HashMap;
4
5use crate::error::RegistryError;
6use crate::models::{Keybind, KeyCombo};
7
8pub struct KeybindRegistry {
10 by_action: HashMap<String, Keybind>,
12 by_key: HashMap<String, String>,
14}
15
16impl KeybindRegistry {
17 pub fn new() -> Self {
19 KeybindRegistry {
20 by_action: HashMap::new(),
21 by_key: HashMap::new(),
22 }
23 }
24
25 pub fn register(&mut self, keybind: Keybind) -> Result<(), RegistryError> {
27 if keybind.action_id.is_empty() {
29 return Err(RegistryError::InvalidActionIdFormat(
30 "Action ID cannot be empty".to_string(),
31 ));
32 }
33
34 let key_combo = keybind.parse_key().map_err(|e| {
36 RegistryError::InvalidActionIdFormat(format!("Invalid key: {}", e))
37 })?;
38
39 let key_str = key_combo.to_string();
40
41 if let Some(existing_action) = self.by_key.get(&key_str) {
43 if existing_action != &keybind.action_id {
44 return Err(RegistryError::DuplicateActionId(format!(
45 "Key {} already bound to {}",
46 key_str, existing_action
47 )));
48 }
49 }
50
51 let action_id = keybind.action_id.clone();
53 self.by_action.insert(action_id.clone(), keybind);
54 self.by_key.insert(key_str, action_id);
55
56 Ok(())
57 }
58
59 pub fn lookup_by_action(&self, action_id: &str) -> Option<&Keybind> {
61 self.by_action.get(action_id)
62 }
63
64 pub fn lookup_by_key(&self, key: &KeyCombo) -> Option<&str> {
66 let key_str = key.to_string();
67 self.by_key.get(&key_str).map(|s| s.as_str())
68 }
69
70 pub fn all_keybinds(&self) -> Vec<&Keybind> {
72 self.by_action.values().collect()
73 }
74
75 pub fn len(&self) -> usize {
77 self.by_action.len()
78 }
79
80 pub fn is_empty(&self) -> bool {
82 self.by_action.is_empty()
83 }
84
85 pub fn clear(&mut self) {
87 self.by_action.clear();
88 self.by_key.clear();
89 }
90
91 pub fn keybinds_by_category(&self, category: &str) -> Vec<&Keybind> {
93 self.by_action
94 .values()
95 .filter(|kb| kb.category == category)
96 .collect()
97 }
98
99 pub fn categories(&self) -> Vec<String> {
101 let mut categories: Vec<String> = self
102 .by_action
103 .values()
104 .map(|kb| kb.category.clone())
105 .collect();
106 categories.sort();
107 categories.dedup();
108 categories
109 }
110}
111
112impl Default for KeybindRegistry {
113 fn default() -> Self {
114 Self::new()
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn test_register_keybind() {
124 let mut registry = KeybindRegistry::new();
125 let kb = Keybind::new("editor.save", "Ctrl+S", "editing", "Save file");
126 assert!(registry.register(kb).is_ok());
127 assert_eq!(registry.len(), 1);
128 }
129
130 #[test]
131 fn test_lookup_by_action() {
132 let mut registry = KeybindRegistry::new();
133 let kb = Keybind::new("editor.save", "Ctrl+S", "editing", "Save file");
134 registry.register(kb).unwrap();
135
136 let found = registry.lookup_by_action("editor.save");
137 assert!(found.is_some());
138 assert_eq!(found.unwrap().key, "Ctrl+S");
139 }
140
141 #[test]
142 fn test_lookup_by_key() {
143 let mut registry = KeybindRegistry::new();
144 let kb = Keybind::new("editor.save", "Ctrl+S", "editing", "Save file");
145 registry.register(kb).unwrap();
146
147 let key_combo = KeyCombo::from_str("Ctrl+S").unwrap();
148 let action = registry.lookup_by_key(&key_combo);
149 assert_eq!(action, Some("editor.save"));
150 }
151
152 #[test]
153 fn test_duplicate_key_detection() {
154 let mut registry = KeybindRegistry::new();
155 let kb1 = Keybind::new("editor.save", "Ctrl+S", "editing", "Save file");
156 let kb2 = Keybind::new("editor.save_all", "Ctrl+S", "editing", "Save all");
157
158 registry.register(kb1).unwrap();
159 assert!(registry.register(kb2).is_err());
160 }
161
162 #[test]
163 fn test_all_keybinds() {
164 let mut registry = KeybindRegistry::new();
165 registry
166 .register(Keybind::new("editor.save", "Ctrl+S", "editing", "Save"))
167 .unwrap();
168 registry
169 .register(Keybind::new("editor.undo", "Ctrl+Z", "editing", "Undo"))
170 .unwrap();
171
172 let all = registry.all_keybinds();
173 assert_eq!(all.len(), 2);
174 }
175
176 #[test]
177 fn test_keybinds_by_category() {
178 let mut registry = KeybindRegistry::new();
179 registry
180 .register(Keybind::new("editor.save", "Ctrl+S", "editing", "Save"))
181 .unwrap();
182 registry
183 .register(Keybind::new("nav.next", "Tab", "navigation", "Next"))
184 .unwrap();
185
186 let editing = registry.keybinds_by_category("editing");
187 assert_eq!(editing.len(), 1);
188 assert_eq!(editing[0].action_id, "editor.save");
189 }
190
191 #[test]
192 fn test_categories() {
193 let mut registry = KeybindRegistry::new();
194 registry
195 .register(Keybind::new("editor.save", "Ctrl+S", "editing", "Save"))
196 .unwrap();
197 registry
198 .register(Keybind::new("nav.next", "Tab", "navigation", "Next"))
199 .unwrap();
200 registry
201 .register(Keybind::new("editor.undo", "Ctrl+Z", "editing", "Undo"))
202 .unwrap();
203
204 let categories = registry.categories();
205 assert_eq!(categories.len(), 2);
206 assert!(categories.contains(&"editing".to_string()));
207 assert!(categories.contains(&"navigation".to_string()));
208 }
209
210 use std::str::FromStr;
211}