ricecoder_keybinds/
help.rs

1//! Help system for displaying keybinds
2
3use crate::models::Keybind;
4
5/// Pagination information
6#[derive(Debug, Clone)]
7pub struct Page<T> {
8    pub items: Vec<T>,
9    pub current_page: usize,
10    pub total_pages: usize,
11}
12
13/// Keybind help system
14pub struct KeybindHelp;
15
16impl KeybindHelp {
17    /// Display all keybinds
18    pub fn display_all(keybinds: &[&Keybind]) -> String {
19        let mut output = String::from("# All Keybinds\n\n");
20
21        if keybinds.is_empty() {
22            output.push_str("No keybinds configured.\n");
23            return output;
24        }
25
26        // Group by category
27        let mut categories: std::collections::HashMap<String, Vec<&Keybind>> =
28            std::collections::HashMap::new();
29
30        for keybind in keybinds {
31            categories
32                .entry(keybind.category.clone())
33                .or_default()
34                .push(keybind);
35        }
36
37        // Sort categories
38        let mut sorted_categories: Vec<_> = categories.keys().collect();
39        sorted_categories.sort();
40
41        for category in sorted_categories {
42            output.push_str(&format!("## {}\n\n", category));
43
44            let mut keybinds_in_category = categories[category].clone();
45            keybinds_in_category.sort_by(|a, b| a.action_id.cmp(&b.action_id));
46
47            for keybind in keybinds_in_category {
48                output.push_str(&format!(
49                    "- **{}**: `{}` - {}\n",
50                    keybind.action_id, keybind.key, keybind.description
51                ));
52            }
53
54            output.push('\n');
55        }
56
57        output
58    }
59
60    /// Display keybinds by category
61    pub fn display_by_category(keybinds: &[&Keybind], category: &str) -> String {
62        let filtered: Vec<&Keybind> = keybinds
63            .iter()
64            .filter(|kb| kb.category == category)
65            .copied()
66            .collect();
67
68        if filtered.is_empty() {
69            return format!("No keybinds found in category: {}\n", category);
70        }
71
72        let mut output = format!("# {} Keybinds\n\n", category);
73
74        for keybind in filtered {
75            output.push_str(&format!(
76                "- **{}**: `{}` - {}\n",
77                keybind.action_id, keybind.key, keybind.description
78            ));
79        }
80
81        output
82    }
83
84    /// Search keybinds by query
85    pub fn search<'a>(keybinds: &[&'a Keybind], query: &str) -> Vec<&'a Keybind> {
86        let query_lower = query.to_lowercase();
87
88        keybinds
89            .iter()
90            .filter(|kb| {
91                kb.action_id.to_lowercase().contains(&query_lower)
92                    || kb.key.to_lowercase().contains(&query_lower)
93                    || kb.description.to_lowercase().contains(&query_lower)
94            })
95            .copied()
96            .collect()
97    }
98
99    /// Paginate keybinds
100    pub fn paginate<'a>(keybinds: &[&'a Keybind], page: usize, page_size: usize) -> Page<&'a Keybind> {
101        if page_size == 0 {
102            return Page {
103                items: Vec::new(),
104                current_page: 0,
105                total_pages: 0,
106            };
107        }
108
109        let total_pages = keybinds.len().div_ceil(page_size);
110
111        if page == 0 || page > total_pages {
112            return Page {
113                items: Vec::new(),
114                current_page: page,
115                total_pages,
116            };
117        }
118
119        let start = (page - 1) * page_size;
120        let end = std::cmp::min(start + page_size, keybinds.len());
121
122        Page {
123            items: keybinds[start..end].to_vec(),
124            current_page: page,
125            total_pages,
126        }
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_display_all() {
136        let keybinds = vec![
137            Keybind::new("editor.save", "Ctrl+S", "editing", "Save"),
138            Keybind::new("editor.undo", "Ctrl+Z", "editing", "Undo"),
139            Keybind::new("nav.next", "Tab", "navigation", "Next"),
140        ];
141
142        let keybind_refs: Vec<&Keybind> = keybinds.iter().collect();
143        let output = KeybindHelp::display_all(&keybind_refs);
144
145        assert!(output.contains("# All Keybinds"));
146        assert!(output.contains("## editing"));
147        assert!(output.contains("## navigation"));
148        assert!(output.contains("editor.save"));
149    }
150
151    #[test]
152    fn test_display_by_category() {
153        let keybinds = vec![
154            Keybind::new("editor.save", "Ctrl+S", "editing", "Save"),
155            Keybind::new("editor.undo", "Ctrl+Z", "editing", "Undo"),
156            Keybind::new("nav.next", "Tab", "navigation", "Next"),
157        ];
158
159        let keybind_refs: Vec<&Keybind> = keybinds.iter().collect();
160        let output = KeybindHelp::display_by_category(&keybind_refs, "editing");
161
162        assert!(output.contains("# editing Keybinds"));
163        assert!(output.contains("editor.save"));
164        assert!(output.contains("editor.undo"));
165        assert!(!output.contains("nav.next"));
166    }
167
168    #[test]
169    fn test_search() {
170        let keybinds = vec![
171            Keybind::new("editor.save", "Ctrl+S", "editing", "Save file"),
172            Keybind::new("editor.undo", "Ctrl+Z", "editing", "Undo change"),
173            Keybind::new("nav.next", "Tab", "navigation", "Next item"),
174        ];
175
176        let keybind_refs: Vec<&Keybind> = keybinds.iter().collect();
177        let results = KeybindHelp::search(&keybind_refs, "save");
178
179        assert_eq!(results.len(), 1);
180        assert_eq!(results[0].action_id, "editor.save");
181    }
182
183    #[test]
184    fn test_search_by_key() {
185        let keybinds = vec![
186            Keybind::new("editor.save", "Ctrl+S", "editing", "Save file"),
187            Keybind::new("editor.undo", "Ctrl+Z", "editing", "Undo change"),
188        ];
189
190        let keybind_refs: Vec<&Keybind> = keybinds.iter().collect();
191        let results = KeybindHelp::search(&keybind_refs, "Ctrl+S");
192
193        assert_eq!(results.len(), 1);
194        assert_eq!(results[0].action_id, "editor.save");
195    }
196
197    #[test]
198    fn test_paginate() {
199        let keybinds = vec![
200            Keybind::new("editor.save", "Ctrl+S", "editing", "Save"),
201            Keybind::new("editor.undo", "Ctrl+Z", "editing", "Undo"),
202            Keybind::new("nav.next", "Tab", "navigation", "Next"),
203            Keybind::new("nav.prev", "Shift+Tab", "navigation", "Previous"),
204        ];
205
206        let keybind_refs: Vec<&Keybind> = keybinds.iter().collect();
207        let page = KeybindHelp::paginate(&keybind_refs, 1, 2);
208
209        assert_eq!(page.current_page, 1);
210        assert_eq!(page.total_pages, 2);
211        assert_eq!(page.items.len(), 2);
212    }
213
214    #[test]
215    fn test_paginate_invalid_page() {
216        let keybinds = vec![
217            Keybind::new("editor.save", "Ctrl+S", "editing", "Save"),
218            Keybind::new("editor.undo", "Ctrl+Z", "editing", "Undo"),
219        ];
220
221        let keybind_refs: Vec<&Keybind> = keybinds.iter().collect();
222        let page = KeybindHelp::paginate(&keybind_refs, 10, 2);
223
224        assert_eq!(page.items.len(), 0);
225    }
226}