ricecoder_keybinds/
profile.rs

1//! Profile management for keybind configurations
2
3use std::time::SystemTime;
4
5use crate::error::ProfileError;
6use crate::models::Keybind;
7use std::collections::HashMap;
8
9/// Represents a keybind profile
10#[derive(Debug, Clone)]
11pub struct Profile {
12    pub name: String,
13    pub keybinds: Vec<Keybind>,
14    pub created_at: SystemTime,
15    pub modified_at: SystemTime,
16}
17
18// Manual serialization for Profile to handle SystemTime
19impl serde::Serialize for Profile {
20    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
21    where
22        S: serde::Serializer,
23    {
24        use serde::ser::SerializeStruct;
25        let mut state = serializer.serialize_struct("Profile", 2)?;
26        state.serialize_field("name", &self.name)?;
27        state.serialize_field("keybinds", &self.keybinds)?;
28        state.end()
29    }
30}
31
32// Manual deserialization for Profile
33impl<'de> serde::Deserialize<'de> for Profile {
34    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
35    where
36        D: serde::Deserializer<'de>,
37    {
38        use serde::de::{self, MapAccess, Visitor};
39        use std::fmt;
40
41        #[derive(serde::Deserialize)]
42        #[serde(field_identifier, rename_all = "lowercase")]
43        enum Field {
44            Name,
45            Keybinds,
46        }
47
48        struct ProfileVisitor;
49
50        impl<'de> Visitor<'de> for ProfileVisitor {
51            type Value = Profile;
52
53            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
54                formatter.write_str("struct Profile")
55            }
56
57            fn visit_map<V>(self, mut map: V) -> Result<Profile, V::Error>
58            where
59                V: MapAccess<'de>,
60            {
61                let mut name = None;
62                let mut keybinds = None;
63
64                while let Some(key) = map.next_key()? {
65                    match key {
66                        Field::Name => {
67                            if name.is_some() {
68                                return Err(de::Error::duplicate_field("name"));
69                            }
70                            name = Some(map.next_value()?);
71                        }
72                        Field::Keybinds => {
73                            if keybinds.is_some() {
74                                return Err(de::Error::duplicate_field("keybinds"));
75                            }
76                            keybinds = Some(map.next_value()?);
77                        }
78                    }
79                }
80
81                let name = name.ok_or_else(|| de::Error::missing_field("name"))?;
82                let keybinds = keybinds.ok_or_else(|| de::Error::missing_field("keybinds"))?;
83                let now = SystemTime::now();
84
85                Ok(Profile {
86                    name,
87                    keybinds,
88                    created_at: now,
89                    modified_at: now,
90                })
91            }
92        }
93
94        deserializer.deserialize_struct("Profile", &["name", "keybinds"], ProfileVisitor)
95    }
96}
97
98impl Profile {
99    /// Create a new profile
100    pub fn new(name: impl Into<String>, keybinds: Vec<Keybind>) -> Self {
101        let now = SystemTime::now();
102        Profile {
103            name: name.into(),
104            keybinds,
105            created_at: now,
106            modified_at: now,
107        }
108    }
109}
110
111/// Manages keybind profiles
112pub struct ProfileManager {
113    profiles: HashMap<String, Profile>,
114    active_profile: Option<String>,
115}
116
117impl ProfileManager {
118    /// Create a new profile manager
119    pub fn new() -> Self {
120        ProfileManager {
121            profiles: HashMap::new(),
122            active_profile: None,
123        }
124    }
125
126    /// Create a new profile
127    pub fn create_profile(
128        &mut self,
129        name: impl Into<String>,
130        keybinds: Vec<Keybind>,
131    ) -> Result<(), ProfileError> {
132        let name = name.into();
133
134        if name.is_empty() {
135            return Err(ProfileError::InvalidProfileName(
136                "Profile name cannot be empty".to_string(),
137            ));
138        }
139
140        if self.profiles.contains_key(&name) {
141            return Err(ProfileError::ProfileAlreadyExists(name));
142        }
143
144        let profile = Profile::new(name.clone(), keybinds);
145        self.profiles.insert(name, profile);
146
147        // Set as active if it's the first profile
148        if self.active_profile.is_none() {
149            self.active_profile = Some(self.profiles.keys().next().unwrap().clone());
150        }
151
152        Ok(())
153    }
154
155    /// Select a profile as active
156    pub fn select_profile(&mut self, name: &str) -> Result<(), ProfileError> {
157        if !self.profiles.contains_key(name) {
158            return Err(ProfileError::ProfileNotFound(name.to_string()));
159        }
160
161        self.active_profile = Some(name.to_string());
162        Ok(())
163    }
164
165    /// Delete a profile
166    pub fn delete_profile(&mut self, name: &str) -> Result<(), ProfileError> {
167        if let Some(active) = &self.active_profile {
168            if active == name {
169                return Err(ProfileError::CannotDeleteActiveProfile(name.to_string()));
170            }
171        }
172
173        if self.profiles.remove(name).is_none() {
174            return Err(ProfileError::ProfileNotFound(name.to_string()));
175        }
176
177        Ok(())
178    }
179
180    /// List all profiles
181    pub fn list_profiles(&self) -> Vec<&Profile> {
182        self.profiles.values().collect()
183    }
184
185    /// Get the active profile
186    pub fn get_active_profile(&self) -> Result<&Profile, ProfileError> {
187        let name = self
188            .active_profile
189            .as_ref()
190            .ok_or(ProfileError::NoActiveProfile)?;
191
192        self.profiles
193            .get(name)
194            .ok_or_else(|| ProfileError::ProfileNotFound(name.clone()))
195    }
196
197    /// Get a profile by name
198    pub fn get_profile(&self, name: &str) -> Option<&Profile> {
199        self.profiles.get(name)
200    }
201
202    /// Get the active profile name
203    pub fn active_profile_name(&self) -> Option<&str> {
204        self.active_profile.as_deref()
205    }
206
207    /// Update a profile's keybinds
208    pub fn update_profile(
209        &mut self,
210        name: &str,
211        keybinds: Vec<Keybind>,
212    ) -> Result<(), ProfileError> {
213        let profile = self
214            .profiles
215            .get_mut(name)
216            .ok_or_else(|| ProfileError::ProfileNotFound(name.to_string()))?;
217
218        profile.keybinds = keybinds;
219        profile.modified_at = SystemTime::now();
220
221        Ok(())
222    }
223
224    /// Get number of profiles
225    pub fn len(&self) -> usize {
226        self.profiles.len()
227    }
228
229    /// Check if profile manager is empty
230    pub fn is_empty(&self) -> bool {
231        self.profiles.is_empty()
232    }
233}
234
235impl Default for ProfileManager {
236    fn default() -> Self {
237        Self::new()
238    }
239}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244
245    #[test]
246    fn test_create_profile() {
247        let mut manager = ProfileManager::new();
248        let keybinds = vec![Keybind::new("editor.save", "Ctrl+S", "editing", "Save")];
249
250        assert!(manager.create_profile("default", keybinds).is_ok());
251        assert_eq!(manager.len(), 1);
252    }
253
254    #[test]
255    fn test_create_duplicate_profile() {
256        let mut manager = ProfileManager::new();
257        let keybinds = vec![Keybind::new("editor.save", "Ctrl+S", "editing", "Save")];
258
259        manager.create_profile("default", keybinds.clone()).unwrap();
260        assert!(manager.create_profile("default", keybinds).is_err());
261    }
262
263    #[test]
264    fn test_select_profile() {
265        let mut manager = ProfileManager::new();
266        let keybinds = vec![Keybind::new("editor.save", "Ctrl+S", "editing", "Save")];
267
268        manager.create_profile("default", keybinds.clone()).unwrap();
269        manager.create_profile("vim", keybinds).unwrap();
270
271        assert!(manager.select_profile("vim").is_ok());
272        assert_eq!(manager.active_profile_name(), Some("vim"));
273    }
274
275    #[test]
276    fn test_delete_profile() {
277        let mut manager = ProfileManager::new();
278        let keybinds = vec![Keybind::new("editor.save", "Ctrl+S", "editing", "Save")];
279
280        manager.create_profile("default", keybinds.clone()).unwrap();
281        manager.create_profile("vim", keybinds).unwrap();
282
283        manager.select_profile("vim").unwrap();
284        assert!(manager.delete_profile("default").is_ok());
285        assert_eq!(manager.len(), 1);
286    }
287
288    #[test]
289    fn test_cannot_delete_active_profile() {
290        let mut manager = ProfileManager::new();
291        let keybinds = vec![Keybind::new("editor.save", "Ctrl+S", "editing", "Save")];
292
293        manager.create_profile("default", keybinds).unwrap();
294        assert!(manager.delete_profile("default").is_err());
295    }
296
297    #[test]
298    fn test_get_active_profile() {
299        let mut manager = ProfileManager::new();
300        let keybinds = vec![Keybind::new("editor.save", "Ctrl+S", "editing", "Save")];
301
302        manager.create_profile("default", keybinds).unwrap();
303        let profile = manager.get_active_profile().unwrap();
304        assert_eq!(profile.name, "default");
305    }
306
307    #[test]
308    fn test_list_profiles() {
309        let mut manager = ProfileManager::new();
310        let keybinds = vec![Keybind::new("editor.save", "Ctrl+S", "editing", "Save")];
311
312        manager.create_profile("default", keybinds.clone()).unwrap();
313        manager.create_profile("vim", keybinds).unwrap();
314
315        let profiles = manager.list_profiles();
316        assert_eq!(profiles.len(), 2);
317    }
318
319    #[test]
320    fn test_update_profile() {
321        let mut manager = ProfileManager::new();
322        let keybinds = vec![Keybind::new("editor.save", "Ctrl+S", "editing", "Save")];
323
324        manager.create_profile("default", keybinds).unwrap();
325
326        let new_keybinds = vec![
327            Keybind::new("editor.save", "Ctrl+S", "editing", "Save"),
328            Keybind::new("editor.undo", "Ctrl+Z", "editing", "Undo"),
329        ];
330
331        assert!(manager.update_profile("default", new_keybinds).is_ok());
332        let profile = manager.get_profile("default").unwrap();
333        assert_eq!(profile.keybinds.len(), 2);
334    }
335
336    #[test]
337    fn test_profile_metadata_creation_time() {
338        let mut manager = ProfileManager::new();
339        let keybinds = vec![Keybind::new("editor.save", "Ctrl+S", "editing", "Save")];
340
341        let before_creation = SystemTime::now();
342        manager.create_profile("default", keybinds).unwrap();
343        let after_creation = SystemTime::now();
344
345        let profile = manager.get_profile("default").unwrap();
346
347        // Verify created_at is set and within expected time range
348        assert!(profile.created_at >= before_creation);
349        assert!(profile.created_at <= after_creation);
350
351        // Verify modified_at is also set to creation time
352        assert!(profile.modified_at >= before_creation);
353        assert!(profile.modified_at <= after_creation);
354    }
355
356    #[test]
357    fn test_profile_metadata_modification_time() {
358        let mut manager = ProfileManager::new();
359        let keybinds = vec![Keybind::new("editor.save", "Ctrl+S", "editing", "Save")];
360
361        manager.create_profile("default", keybinds).unwrap();
362        let profile_after_creation = manager.get_profile("default").unwrap();
363        let created_at = profile_after_creation.created_at;
364        let modified_at_after_creation = profile_after_creation.modified_at;
365
366        // Wait a bit to ensure time difference
367        std::thread::sleep(std::time::Duration::from_millis(10));
368
369        // Update the profile
370        let new_keybinds = vec![
371            Keybind::new("editor.save", "Ctrl+S", "editing", "Save"),
372            Keybind::new("editor.undo", "Ctrl+Z", "editing", "Undo"),
373        ];
374
375        let before_update = SystemTime::now();
376        manager.update_profile("default", new_keybinds).unwrap();
377        let after_update = SystemTime::now();
378
379        let profile_after_update = manager.get_profile("default").unwrap();
380
381        // Verify created_at hasn't changed
382        assert_eq!(profile_after_update.created_at, created_at);
383
384        // Verify modified_at has been updated
385        assert!(profile_after_update.modified_at >= before_update);
386        assert!(profile_after_update.modified_at <= after_update);
387        assert!(profile_after_update.modified_at > modified_at_after_creation);
388    }
389
390    #[test]
391    fn test_profile_metadata_preserved_in_name_and_keybinds() {
392        let mut manager = ProfileManager::new();
393        let keybinds = vec![
394            Keybind::new("editor.save", "Ctrl+S", "editing", "Save"),
395            Keybind::new("editor.undo", "Ctrl+Z", "editing", "Undo"),
396        ];
397
398        manager.create_profile("my_profile", keybinds.clone()).unwrap();
399
400        let profile = manager.get_profile("my_profile").unwrap();
401
402        // Verify profile name is stored
403        assert_eq!(profile.name, "my_profile");
404
405        // Verify keybinds are stored
406        assert_eq!(profile.keybinds.len(), 2);
407        assert_eq!(profile.keybinds[0].action_id, "editor.save");
408        assert_eq!(profile.keybinds[1].action_id, "editor.undo");
409    }
410
411    #[test]
412    fn test_prevent_deletion_of_active_profile_with_metadata() {
413        let mut manager = ProfileManager::new();
414        let keybinds = vec![Keybind::new("editor.save", "Ctrl+S", "editing", "Save")];
415
416        manager.create_profile("default", keybinds).unwrap();
417
418        // Verify profile has metadata
419        let profile = manager.get_profile("default").unwrap();
420        assert!(profile.created_at <= SystemTime::now());
421        assert!(profile.modified_at <= SystemTime::now());
422
423        // Store metadata before mutable borrow
424        let created_at = profile.created_at;
425        let modified_at = profile.modified_at;
426
427        // Try to delete active profile - should fail
428        assert!(manager.delete_profile("default").is_err());
429
430        // Verify profile still exists with same metadata
431        let profile_after_failed_delete = manager.get_profile("default").unwrap();
432        assert_eq!(profile_after_failed_delete.created_at, created_at);
433        assert_eq!(profile_after_failed_delete.modified_at, modified_at);
434    }
435
436    #[test]
437    fn test_profile_metadata_across_multiple_profiles() {
438        let mut manager = ProfileManager::new();
439        let keybinds1 = vec![Keybind::new("editor.save", "Ctrl+S", "editing", "Save")];
440        let keybinds2 = vec![Keybind::new("editor.undo", "Ctrl+Z", "editing", "Undo")];
441
442        let before_profile1 = SystemTime::now();
443        manager.create_profile("profile1", keybinds1).unwrap();
444        let after_profile1 = SystemTime::now();
445
446        // Small delay to ensure different timestamps
447        std::thread::sleep(std::time::Duration::from_millis(10));
448
449        let before_profile2 = SystemTime::now();
450        manager.create_profile("profile2", keybinds2).unwrap();
451        let after_profile2 = SystemTime::now();
452
453        let profile1 = manager.get_profile("profile1").unwrap();
454        let profile2 = manager.get_profile("profile2").unwrap();
455
456        // Verify profile1 metadata
457        assert!(profile1.created_at >= before_profile1);
458        assert!(profile1.created_at <= after_profile1);
459
460        // Verify profile2 metadata
461        assert!(profile2.created_at >= before_profile2);
462        assert!(profile2.created_at <= after_profile2);
463
464        // Verify profile2 was created after profile1
465        assert!(profile2.created_at >= profile1.created_at);
466    }
467}