1use inquire::autocompletion::{Autocomplete, Replacement};
2
3const SLASH_COMMANDS: &[(&str, &str)] = &[
5 ("/config", "Show current configuration"),
6 ("/help", "Show available commands"),
7 ("/quit", "Exit chat mode"),
8];
9
10#[derive(Clone, Default)]
12pub struct SlashCommandCompleter;
13
14impl Autocomplete for SlashCommandCompleter {
15 fn get_suggestions(&mut self, input: &str) -> Result<Vec<String>, inquire::CustomUserError> {
16 if !input.starts_with('/') {
17 return Ok(vec![]);
18 }
19
20 let suggestions: Vec<String> = SLASH_COMMANDS
21 .iter()
22 .filter(|(cmd, _)| cmd.starts_with(input))
23 .map(|(cmd, desc)| format!("{cmd} {desc}"))
24 .collect();
25
26 Ok(suggestions)
27 }
28
29 fn get_completion(
30 &mut self,
31 _input: &str,
32 highlighted_suggestion: Option<String>,
33 ) -> Result<Replacement, inquire::CustomUserError> {
34 let replacement =
35 highlighted_suggestion.map(|s| s.split_whitespace().next().unwrap_or("").to_string());
36 Ok(replacement)
37 }
38}
39
40#[derive(Debug, Clone)]
42pub enum SlashCommand {
43 Config,
44 Help,
45 Quit,
46 Unknown(String),
47}
48
49#[derive(Debug)]
51pub enum Input {
52 Text(String),
53 Command(SlashCommand),
54 Empty,
55}
56
57pub fn parse_input(input: &str) -> Input {
58 let input = input.trim();
59
60 if input.is_empty() {
61 return Input::Empty;
62 }
63
64 input
65 .strip_prefix('/')
66 .map_or_else(|| Input::Text(input.to_string()), parse_slash_command)
67}
68
69fn parse_slash_command(cmd: &str) -> Input {
70 let parts: Vec<&str> = cmd.split_whitespace().collect();
71
72 match parts.first().copied() {
73 Some("config") => Input::Command(SlashCommand::Config),
74 Some("help") => Input::Command(SlashCommand::Help),
75 Some("quit" | "exit" | "q") => Input::Command(SlashCommand::Quit),
76 _ => Input::Command(SlashCommand::Unknown(parts.join(" "))),
77 }
78}
79
80#[cfg(test)]
81#[allow(clippy::unwrap_used)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn test_parse_empty_input() {
87 assert!(matches!(parse_input(""), Input::Empty));
88 assert!(matches!(parse_input(" "), Input::Empty));
89 }
90
91 #[test]
92 fn test_parse_text_input() {
93 match parse_input("Hello, world!") {
94 Input::Text(text) => assert_eq!(text, "Hello, world!"),
95 _ => panic!("Expected Input::Text"),
96 }
97 }
98
99 #[test]
100 fn test_parse_config_command() {
101 assert!(matches!(
102 parse_input("/config"),
103 Input::Command(SlashCommand::Config)
104 ));
105 }
106
107 #[test]
108 fn test_parse_help_command() {
109 assert!(matches!(
110 parse_input("/help"),
111 Input::Command(SlashCommand::Help)
112 ));
113 }
114
115 #[test]
116 fn test_parse_quit_commands() {
117 assert!(matches!(
118 parse_input("/quit"),
119 Input::Command(SlashCommand::Quit)
120 ));
121 assert!(matches!(
122 parse_input("/exit"),
123 Input::Command(SlashCommand::Quit)
124 ));
125 assert!(matches!(
126 parse_input("/q"),
127 Input::Command(SlashCommand::Quit)
128 ));
129 }
130
131 #[test]
132 fn test_parse_unknown_command() {
133 match parse_input("/unknown") {
134 Input::Command(SlashCommand::Unknown(cmd)) => assert_eq!(cmd, "unknown"),
135 _ => panic!("Expected Input::Command(SlashCommand::Unknown)"),
136 }
137 }
138
139 #[test]
142 fn test_completer_no_suggestions_for_regular_text() {
143 let mut completer = SlashCommandCompleter;
144 let suggestions = completer.get_suggestions("hello").unwrap();
145 assert!(suggestions.is_empty());
146 }
147
148 #[test]
149 fn test_completer_suggestions_for_slash() {
150 let mut completer = SlashCommandCompleter;
151 let suggestions = completer.get_suggestions("/").unwrap();
152 assert_eq!(suggestions.len(), 3); }
154
155 #[test]
156 fn test_completer_suggestions_filter_by_prefix() {
157 let mut completer = SlashCommandCompleter;
158
159 let suggestions = completer.get_suggestions("/c").unwrap();
160 assert_eq!(suggestions.len(), 1);
161 assert!(suggestions[0].starts_with("/config"));
162
163 let suggestions = completer.get_suggestions("/q").unwrap();
164 assert_eq!(suggestions.len(), 1);
165 assert!(suggestions[0].starts_with("/quit"));
166 }
167
168 #[test]
169 fn test_completer_completion() {
170 let mut completer = SlashCommandCompleter;
171 let suggestion = "/config Show current configuration".to_string();
172 let completion = completer.get_completion("/c", Some(suggestion)).unwrap();
173 assert_eq!(completion, Some("/config".to_string()));
174 }
175
176 #[test]
177 fn test_completer_completion_none() {
178 let mut completer = SlashCommandCompleter;
179 let completion = completer.get_completion("/x", None).unwrap();
180 assert!(completion.is_none());
181 }
182}