use crate::bus::ChatMessage; use super::channel::CliChannel; pub enum InputEvent { Message(ChatMessage), Command(InputCommand), } #[derive(Debug, Clone, PartialEq, Eq)] pub enum InputCommand { Exit, Clear, New(Option), Sessions, Use(String), Rename(String), Archive, Delete, } pub struct InputHandler { channel: CliChannel, } impl InputHandler { pub fn new() -> Self { Self { channel: CliChannel::new(), } } pub async fn read_input(&mut self, prompt: &str) -> Result, InputError> { match self.channel.read_line(prompt).await { Ok(Some(line)) => { if line.trim().is_empty() { return Ok(None); } if let Some(cmd) = self.handle_special_commands(&line) { return Ok(Some(InputEvent::Command(cmd))); } Ok(Some(InputEvent::Message(ChatMessage::user(line)))) } Ok(None) => Ok(None), Err(e) => Err(InputError::IoError(e)), } } pub async fn write_output(&mut self, content: &str) -> Result<(), InputError> { self.channel.write_line(content).await.map_err(InputError::IoError) } pub async fn write_response(&mut self, content: &str) -> Result<(), InputError> { self.channel.write_response(content).await.map_err(InputError::IoError) } fn handle_special_commands(&self, line: &str) -> Option { let trimmed = line.trim(); let mut parts = trimmed.splitn(2, char::is_whitespace); let command = parts.next()?; let arg = parts.next().map(str::trim).filter(|value| !value.is_empty()); match command { "/quit" | "/exit" | "/q" => Some(InputCommand::Exit), "/clear" => Some(InputCommand::Clear), "/new" => Some(InputCommand::New(arg.map(ToOwned::to_owned))), "/sessions" => Some(InputCommand::Sessions), "/use" => arg.map(|value| InputCommand::Use(value.to_string())), "/rename" => arg.map(|value| InputCommand::Rename(value.to_string())), "/archive" => Some(InputCommand::Archive), "/delete" => Some(InputCommand::Delete), _ => None, } } } impl Default for InputHandler { fn default() -> Self { Self::new() } } #[derive(Debug)] pub enum InputError { IoError(std::io::Error), } impl std::fmt::Display for InputError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { InputError::IoError(e) => write!(f, "IO error: {}", e), } } } impl std::error::Error for InputError {} #[cfg(test)] mod tests { use super::*; #[test] fn test_special_command_parsing() { let handler = InputHandler::new(); assert_eq!(handler.handle_special_commands("/quit"), Some(InputCommand::Exit)); assert_eq!(handler.handle_special_commands("/clear"), Some(InputCommand::Clear)); assert_eq!(handler.handle_special_commands("/new"), Some(InputCommand::New(None))); assert_eq!( handler.handle_special_commands("/new planning"), Some(InputCommand::New(Some("planning".to_string()))) ); assert_eq!(handler.handle_special_commands("/sessions"), Some(InputCommand::Sessions)); assert_eq!( handler.handle_special_commands("/use abc123"), Some(InputCommand::Use("abc123".to_string())) ); assert_eq!( handler.handle_special_commands("/rename project alpha"), Some(InputCommand::Rename("project alpha".to_string())) ); assert_eq!(handler.handle_special_commands("/archive"), Some(InputCommand::Archive)); assert_eq!(handler.handle_special_commands("/delete"), Some(InputCommand::Delete)); assert_eq!(handler.handle_special_commands("/unknown"), None); assert_eq!(handler.handle_special_commands("/use"), None); } }