use tokio::io::{self, AsyncWriteExt, BufReader}; use anyhow::Result; use crate::generator::MessageGenerator; pub async fn run_interactive_mode(mut generator: MessageGenerator) -> Result { let types = vec!["feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert"]; generator.set_type(prompt_type(&types).await?); generator.set_scope_opt(prompt_scope().await?); generator.set_description(prompt_description().await?); generator.set_body_opt(prompt_body().await?); let is_breaking = prompt_breaking().await?; generator.set_breaking(is_breaking); if is_breaking { generator.set_breaking_description(prompt_breaking_description().await?); } println!("\nGenerated commit message:"); println!("{}", generator.format_message()); Ok(generator) } async fn prompt_type(types: &[&str]) -> Result { let mut stdout = io::stdout(); println!("Select commit type:"); for (i, t) in types.iter().enumerate() { println!(" {} - {}", i, t); } print!("Enter number (0-{}): ", types.len() - 1); stdout.flush().await?; let mut input = String::new(); let mut stdin = BufReader::new(io::stdin()); stdin.read_line(&mut input).await?; let idx: Result = input.trim().parse(); match idx { Ok(i) if i < types.len() => Ok(types[i].to_string()), Ok(_) => { eprintln!("Invalid selection, using default 'chore'"); Ok("chore".to_string()) } Err(_) => { eprintln!("Invalid input, using default 'chore'"); Ok("chore".to_string()) } } } async fn prompt_scope() -> Result> { let mut stdout = io::stdout(); let mut stdin = BufReader::new(io::stdin()); print!("Scope (optional): "); stdout.flush().await?; let mut input = String::new(); stdin.read_line(&mut input).await?; let trimmed = input.trim().to_string(); if trimmed.is_empty() { Ok(None) } else { Ok(Some(trimmed)) } } async fn prompt_description() -> Result { let mut stdout = io::stdout(); let mut stdin = BufReader::new(io::stdin()); print!("Commit description: "); stdout.flush().await?; let mut input = String::new(); stdin.read_line(&mut input).await?; Ok(input.trim().to_string()) } async fn prompt_body() -> Result> { let mut stdout = io::stdout(); let mut stdin = BufReader::new(io::stdin()); print!("Extended description (optional, leave empty to skip): "); stdout.flush().await?; let mut input = String::new(); stdin.read_line(&mut input).await?; let trimmed = input.trim().to_string(); if trimmed.is_empty() { Ok(None) } else { Ok(Some(trimmed)) } } async fn prompt_breaking() -> Result { let mut stdout = io::stdout(); let mut stdin = BufReader::new(io::stdin()); print!("Is this a breaking change? [y/N]: "); stdout.flush().await?; let mut input = String::new(); stdin.read_line(&mut input).await?; Ok(input.trim().eq_ignore_ascii_case("y")) } async fn prompt_breaking_description() -> Result { let mut stdout = io::stdout(); let mut stdin = BufReader::new(io::stdin()); print!("Describe the breaking change: "); stdout.flush().await?; let mut input = String::new(); stdin.read_line(&mut input).await?; Ok(input.trim().to_string()) }