cell_sheet_core/help/
mod.rs1pub mod entries;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum HelpCategory {
5 Normal,
6 Insert,
7 Visual,
8 Command,
9 Formula,
10}
11
12impl HelpCategory {
13 pub fn label(&self) -> &'static str {
14 match self {
15 HelpCategory::Normal => "NORMAL MODE",
16 HelpCategory::Insert => "INSERT MODE",
17 HelpCategory::Visual => "VISUAL MODE",
18 HelpCategory::Command => "COMMANDS",
19 HelpCategory::Formula => "FORMULAS",
20 }
21 }
22}
23
24#[derive(Debug)]
25pub struct HelpEntry {
26 pub tags: &'static [&'static str],
27 pub category: HelpCategory,
28 pub summary: &'static str,
29 pub detail: &'static str,
30}
31
32pub struct HelpRegistry {
33 entries: Vec<&'static HelpEntry>,
34}
35
36impl Default for HelpRegistry {
37 fn default() -> Self {
38 Self::new()
39 }
40}
41
42impl HelpRegistry {
43 pub fn new() -> Self {
45 use entries::*;
46 Self::from_entries(&[
47 NORMAL_ENTRIES,
48 INSERT_ENTRIES,
49 VISUAL_ENTRIES,
50 COMMAND_ENTRIES,
51 FORMULA_ENTRIES,
52 ])
53 }
54
55 pub fn from_entries(slices: &[&'static [HelpEntry]]) -> Self {
57 let mut entries = Vec::new();
58 for slice in slices {
59 for entry in *slice {
60 entries.push(entry);
61 }
62 }
63 HelpRegistry { entries }
64 }
65
66 pub fn find(&self, tag: &str) -> Option<&'static HelpEntry> {
68 let tag_lower = tag.to_lowercase();
69 for entry in &self.entries {
70 for t in entry.tags {
71 if t.to_lowercase() == tag_lower {
72 return Some(entry);
73 }
74 }
75 }
76 None
77 }
78
79 pub fn by_category(&self, category: HelpCategory) -> Vec<&'static HelpEntry> {
81 self.entries
82 .iter()
83 .copied()
84 .filter(|e| e.category == category)
85 .collect()
86 }
87
88 pub fn categories(&self) -> Vec<HelpCategory> {
90 use HelpCategory::*;
91 let order = [Normal, Insert, Visual, Command, Formula];
92 order
93 .iter()
94 .copied()
95 .filter(|cat| self.entries.iter().any(|e| e.category == *cat))
96 .collect()
97 }
98
99 pub fn all_entries(&self) -> &[&'static HelpEntry] {
101 &self.entries
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 static TEST_ENTRIES: &[HelpEntry] = &[
110 HelpEntry {
111 tags: &["h"],
112 category: HelpCategory::Normal,
113 summary: "Move cursor left",
114 detail: "Move the cursor one column to the left.",
115 },
116 HelpEntry {
117 tags: &[":w", ":write"],
118 category: HelpCategory::Command,
119 summary: "Save file",
120 detail: "Write the current sheet to disk.",
121 },
122 ];
123
124 #[test]
125 fn find_by_tag() {
126 let registry = HelpRegistry::from_entries(&[TEST_ENTRIES]);
127 let entry = registry.find("h").unwrap();
128 assert_eq!(entry.summary, "Move cursor left");
129 }
130
131 #[test]
132 fn find_by_alias_tag() {
133 let registry = HelpRegistry::from_entries(&[TEST_ENTRIES]);
134 let entry = registry.find(":write").unwrap();
135 assert_eq!(entry.summary, "Save file");
136 }
137
138 #[test]
139 fn find_case_insensitive() {
140 let registry = HelpRegistry::from_entries(&[TEST_ENTRIES]);
141 let entry = registry.find("H").unwrap();
142 assert_eq!(entry.summary, "Move cursor left");
143 }
144
145 #[test]
146 fn find_not_found() {
147 let registry = HelpRegistry::from_entries(&[TEST_ENTRIES]);
148 assert!(registry.find("zzz").is_none());
149 }
150
151 #[test]
152 fn by_category() {
153 let registry = HelpRegistry::from_entries(&[TEST_ENTRIES]);
154 let normals = registry.by_category(HelpCategory::Normal);
155 assert_eq!(normals.len(), 1);
156 assert_eq!(normals[0].tags[0], "h");
157 }
158
159 #[test]
160 fn full_registry_has_expected_tags() {
161 let registry = HelpRegistry::new();
162 assert!(registry.find("h").is_some(), "missing h");
163 assert!(registry.find("dd").is_some(), "missing dd");
164 assert!(registry.find(":w").is_some(), "missing :w");
165 assert!(registry.find(":help").is_some(), "missing :help");
166 assert!(registry.find("SUM").is_some(), "missing SUM");
167 assert!(registry.find("IF").is_some(), "missing IF");
168 assert!(registry.find("Esc").is_some(), "missing Esc");
169 assert!(registry.find("v").is_some(), "missing v");
170 assert!(
171 registry.find(":set delimiter").is_some(),
172 "missing :set delimiter"
173 );
174 }
175}