use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct CommandDef {
pub name: &'static str,
pub aliases: Vec<&'static str>,
pub description: &'static str,
}
pub struct CommandRegistry {
commands: Vec<CommandDef>,
lookup: HashMap<String, usize>,
}
impl CommandRegistry {
pub fn new() -> Self {
let mut registry = Self {
commands: Vec::new(),
lookup: HashMap::new(),
};
registry.register_default_commands();
registry
}
fn register_default_commands(&mut self) {
self.register(CommandDef {
name: "quit",
aliases: vec!["q"],
description: "退出应用",
});
self.register(CommandDef {
name: "project-open",
aliases: vec!["po", "open"],
description: "打开项目",
});
self.register(CommandDef {
name: "project-new",
aliases: vec!["pn", "new"],
description: "创建新项目(全局)",
});
self.register(CommandDef {
name: "project-new-local",
aliases: vec!["pnl", "new-local"],
description: "创建新项目(本地)",
});
self.register(CommandDef {
name: "project-delete",
aliases: vec!["pd", "delete"],
description: "删除项目",
});
self.register(CommandDef {
name: "project-rename",
aliases: vec!["pr", "rename"],
description: "重命名项目",
});
self.register(CommandDef {
name: "task-new",
aliases: vec!["tn", "add"],
description: "创建新任务",
});
self.register(CommandDef {
name: "task-edit",
aliases: vec!["te", "edit"],
description: "编辑任务标题",
});
self.register(CommandDef {
name: "task-delete",
aliases: vec!["td", "del"],
description: "删除任务",
});
self.register(CommandDef {
name: "task-view",
aliases: vec!["tv", "view"],
description: "预览任务(内部)",
});
self.register(CommandDef {
name: "task-view-external",
aliases: vec!["tve", "view-ext"],
description: "预览任务(外部)",
});
self.register(CommandDef {
name: "task-edit-external",
aliases: vec!["tee", "edit-ext"],
description: "用外部编辑器编辑任务",
});
self.register(CommandDef {
name: "split-horizontal",
aliases: vec!["sh", "hsplit"],
description: "水平分屏",
});
self.register(CommandDef {
name: "split-vertical",
aliases: vec!["sv", "vsplit"],
description: "垂直分屏",
});
self.register(CommandDef {
name: "close-pane",
aliases: vec!["cp", "close"],
description: "关闭当前面板",
});
self.register(CommandDef {
name: "focus-next",
aliases: vec!["fn", "next-pane"],
description: "切换到下一个窗口",
});
self.register(CommandDef {
name: "focus-left",
aliases: vec!["fl"],
description: "聚焦左侧面板",
});
self.register(CommandDef {
name: "focus-right",
aliases: vec!["fr"],
description: "聚焦右侧面板",
});
self.register(CommandDef {
name: "focus-up",
aliases: vec!["fu"],
description: "聚焦上方面板",
});
self.register(CommandDef {
name: "focus-down",
aliases: vec!["fd"],
description: "聚焦下方面板",
});
self.register(CommandDef {
name: "reload",
aliases: vec!["r", "refresh"],
description: "重新加载当前项目",
});
self.register(CommandDef {
name: "reload-all",
aliases: vec!["ra", "refresh-all"],
description: "重新加载所有项目",
});
self.register(CommandDef {
name: "help",
aliases: vec!["h", "?"],
description: "显示帮助信息",
});
}
fn register(&mut self, cmd: CommandDef) {
let idx = self.commands.len();
self.lookup.insert(cmd.name.to_string(), idx);
for alias in &cmd.aliases {
self.lookup.insert(alias.to_string(), idx);
}
self.commands.push(cmd);
}
pub fn find_matches(&self, input: &str) -> Vec<&CommandDef> {
if input.is_empty() {
return self.commands.iter().collect();
}
let input_lower = input.to_lowercase();
let mut matches = Vec::new();
for cmd in &self.commands {
if cmd.name.starts_with(&input_lower) {
matches.push(cmd);
continue;
}
let alias_match = cmd.aliases.iter().any(|alias| alias.starts_with(&input_lower));
if alias_match {
matches.push(cmd);
}
}
matches.sort_by_key(|cmd| cmd.name.len());
matches
}
pub fn find_exact(&self, name: &str) -> Option<&CommandDef> {
let idx = self.lookup.get(name)?;
self.commands.get(*idx)
}
pub fn all_commands(&self) -> &[CommandDef] {
&self.commands
}
}
impl Default for CommandRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_find_matches() {
let registry = CommandRegistry::new();
let matches = registry.find_matches("q");
assert!(matches.iter().any(|cmd| cmd.name == "quit"));
let matches = registry.find_matches("po");
assert!(matches.iter().any(|cmd| cmd.name == "project-open"));
let matches = registry.find_matches("");
assert!(!matches.is_empty());
}
#[test]
fn test_find_exact() {
let registry = CommandRegistry::new();
assert!(registry.find_exact("quit").is_some());
assert!(registry.find_exact("q").is_some());
assert_eq!(registry.find_exact("q").unwrap().name, "quit");
assert!(registry.find_exact("nonexistent").is_none());
}
}