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