Skip to main content

stynx_code_commands/application/
command_registry.rs

1use 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}