use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct HelpTopic {
pub id: String,
pub title: String,
pub content: String,
pub keybinds: Vec<String>,
pub tags: Vec<String>,
}
pub struct HelpSystem {
topics: HashMap<String, HelpTopic>,
index: HashMap<String, Vec<String>>,
}
impl HelpSystem {
pub fn new() -> Self {
Self {
topics: HashMap::new(),
index: HashMap::new(),
}
}
pub fn register_topic(&mut self, topic: HelpTopic) {
let mut keywords = Vec::new();
keywords.push(topic.id.to_lowercase());
keywords.push(topic.title.to_lowercase());
for tag in &topic.tags {
keywords.push(tag.to_lowercase());
}
for word in topic.content.split_whitespace() {
if word.len() > 3 {
keywords.push(word.to_lowercase());
}
}
for keyword in keywords {
self.index
.entry(keyword)
.or_insert_with(Vec::new)
.push(topic.id.clone());
}
self.topics.insert(topic.id.clone(), topic);
}
pub fn get_topic(&self, id: &str) -> Option<&HelpTopic> {
self.topics.get(id)
}
pub fn search(&self, query: &str) -> Vec<&HelpTopic> {
let query = query.to_lowercase();
let mut results = Vec::new();
let mut seen = std::collections::HashSet::new();
if let Some(ids) = self.index.get(&query) {
for id in ids {
if seen.insert(id) {
if let Some(topic) = self.topics.get(id) {
results.push(topic);
}
}
}
}
for (keyword, ids) in &self.index {
if keyword.contains(&query) {
for id in ids {
if seen.insert(id) {
if let Some(topic) = self.topics.get(id) {
results.push(topic);
}
}
}
}
}
results
}
pub fn with_defaults() -> Self {
let mut system = Self::new();
system.register_topic(HelpTopic {
id: "basics".to_string(),
title: "Basic Navigation".to_string(),
content: "Use Tab to switch panels. Use Arrow keys to navigate lists.".to_string(),
keybinds: vec!["Tab".to_string(), "Arrows".to_string()],
tags: vec!["navigation".to_string(), "movement".to_string()],
});
system.register_topic(HelpTopic {
id: "staging".to_string(),
title: "Staging Files".to_string(),
content: "Press 's' to stage a file in the Status panel. Press 'u' to unstage.".to_string(),
keybinds: vec!["s".to_string(), "u".to_string()],
tags: vec!["stage".to_string(), "unstage".to_string(), "index".to_string()],
});
system.register_topic(HelpTopic {
id: "commit".to_string(),
title: "Committing Changes".to_string(),
content: "Briefly describe your changes. Press Ctrl+S to save the commit message.".to_string(),
keybinds: vec!["c".to_string(), "Ctrl+S".to_string()],
tags: vec!["commit".to_string(), "save".to_string()],
});
system
}
}
impl Default for HelpSystem {
fn default() -> Self {
Self::with_defaults()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_help_search() {
let system = HelpSystem::default();
let results = system.search("nav");
assert_eq!(results.len(), 1);
assert_eq!(results[0].id, "basics");
let results = system.search("stage");
assert_eq!(results.len(), 1);
assert_eq!(results[0].id, "staging");
}
#[test]
fn test_help_content() {
let system = HelpSystem::default();
let topic = system.get_topic("commit").unwrap();
assert!(topic.content.contains("Ctrl+S"));
}
}