ricecoder_keybinds/
registry.rs

1//! Keybind registry with fast lookup capabilities
2
3use std::collections::HashMap;
4
5use crate::error::RegistryError;
6use crate::models::{Keybind, KeyCombo};
7
8/// Registry for storing and looking up keybinds
9pub struct KeybindRegistry {
10    /// Map from action_id to keybind
11    by_action: HashMap<String, Keybind>,
12    /// Map from key_combo to action_id
13    by_key: HashMap<String, String>,
14}
15
16impl KeybindRegistry {
17    /// Create a new empty registry
18    pub fn new() -> Self {
19        KeybindRegistry {
20            by_action: HashMap::new(),
21            by_key: HashMap::new(),
22        }
23    }
24
25    /// Register a keybind
26    pub fn register(&mut self, keybind: Keybind) -> Result<(), RegistryError> {
27        // Validate action_id format
28        if keybind.action_id.is_empty() {
29            return Err(RegistryError::InvalidActionIdFormat(
30                "Action ID cannot be empty".to_string(),
31            ));
32        }
33
34        // Parse the key combination
35        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        // Check for duplicate key combinations
42        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        // Register the keybind
52        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    /// Lookup keybind by action ID
60    pub fn lookup_by_action(&self, action_id: &str) -> Option<&Keybind> {
61        self.by_action.get(action_id)
62    }
63
64    /// Lookup action ID by key combination
65    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    /// Get all keybinds
71    pub fn all_keybinds(&self) -> Vec<&Keybind> {
72        self.by_action.values().collect()
73    }
74
75    /// Get number of registered keybinds
76    pub fn len(&self) -> usize {
77        self.by_action.len()
78    }
79
80    /// Check if registry is empty
81    pub fn is_empty(&self) -> bool {
82        self.by_action.is_empty()
83    }
84
85    /// Clear all keybinds
86    pub fn clear(&mut self) {
87        self.by_action.clear();
88        self.by_key.clear();
89    }
90
91    /// Get all keybinds for a category
92    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    /// Get all unique categories
100    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}