stynx_code_commands/application/
command_registry.rs1use std::collections::HashMap;
2
3use stynx_code_errors::AppResult;
4
5use crate::domain::{CommandDefinition, CommandHandler, CommandOutput, CommandType};
6use crate::application::parse_command::parse_command;
7use crate::application::execute_command::execute_command;
8use crate::domain::command::CommandResult;
9
10pub struct CommandRegistry {
11 commands: Vec<CommandDefinition>,
12
13 index: HashMap<String, usize>,
14}
15
16impl CommandRegistry {
17 pub fn new() -> Self {
18 Self {
19 commands: Vec::new(),
20 index: HashMap::new(),
21 }
22 }
23
24 pub fn register(&mut self, def: CommandDefinition) {
25 let idx = self.commands.len();
26 self.index.insert(def.name.clone(), idx);
27 for alias in &def.aliases {
28 self.index.insert(alias.clone(), idx);
29 }
30 self.commands.push(def);
31 }
32
33 pub fn get(&self, name: &str) -> Option<&CommandDefinition> {
34 self.index.get(name).map(|&i| &self.commands[i])
35 }
36
37 pub fn completions(&self, prefix: &str) -> Vec<String> {
38 let mut results: Vec<String> = self.commands.iter()
39 .filter(|cmd| !cmd.is_hidden)
40 .flat_map(|cmd| {
41 let mut names = vec![cmd.name.clone()];
42 names.extend(cmd.aliases.iter().cloned());
43 names
44 })
45 .filter(|name| name.starts_with(prefix))
46 .collect();
47 results.sort();
48 results.dedup();
49 results
50 }
51
52 pub fn list_commands(&self) -> Vec<(&str, &str, CommandType)> {
53 self.commands.iter()
54 .filter(|c| !c.is_hidden)
55 .map(|c| (c.name.as_str(), c.description.as_str(), c.command_type))
56 .collect()
57 }
58
59 pub async fn dispatch(&self, input: &str) -> Option<AppResult<CommandOutput>> {
60 let trimmed = input.trim();
61
62 let cmd_name = if let Some(space_idx) = trimmed.find(' ') {
63 &trimmed[..space_idx]
64 } else {
65 trimmed
66 };
67
68 let args = trimmed.strip_prefix(cmd_name).unwrap_or("").trim();
69
70 if let Some(def) = self.get(cmd_name) {
71 return Some((def.handler)(args).await);
72 }
73
74 if let Some(cmd) = parse_command(trimmed) {
75 let result = execute_command(cmd).await;
76 return Some(Ok(match result {
77 CommandResult::Output(text) => CommandOutput::Text(text),
78 CommandResult::ReplaceConversation(_) => CommandOutput::None,
79 CommandResult::Quit => CommandOutput::Quit,
80 }));
81 }
82
83 None
84 }
85
86 pub fn len(&self) -> usize {
87 self.commands.len()
88 }
89
90 pub fn is_empty(&self) -> bool {
91 self.commands.is_empty()
92 }
93}
94
95impl Default for CommandRegistry {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101pub fn local_handler<F>(f: F) -> CommandHandler
102where
103 F: Fn(&str) -> AppResult<CommandOutput> + Send + Sync + 'static,
104{
105 std::sync::Arc::new(move |args: &str| {
106 let result = f(args);
107 Box::pin(async move { result })
108 })
109}
110
111pub fn prompt_handler<F>(f: F) -> CommandHandler
112where
113 F: Fn(&str) -> String + Send + Sync + 'static,
114{
115 std::sync::Arc::new(move |args: &str| {
116 let prompt = f(args);
117 Box::pin(async move { Ok(CommandOutput::Prompt(prompt)) })
118 })
119}