Add interactive.rs module
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-01-31 11:40:48 +00:00
parent 25d3c72abc
commit 45aaf9a67f

69
src/interactive.rs Normal file
View File

@@ -0,0 +1,69 @@
use dialoguer::{Confirm, MultiSelect, Input, Select};
use crate::convention::CommitType;
use crate::error::Result;
pub struct InteractivePrompter;
impl InteractivePrompter {
pub fn new() -> Self { Self }
pub fn select_commit_type(&self, detected_type: &CommitType, all_types: &[CommitType]) -> Result<CommitType> {
println!("Detected commit type: {}", detected_type);
let type_names: Vec<String> = all_types.iter().map(|t| t.to_string()).collect();
let items: Vec<&str> = type_names.iter().map(|s| s.as_str()).collect();
println!("\nSelect commit type:");
let selected = Select::new().with_prompt("Choose type").items(&items).default(0).interact()?;
Ok(all_types[selected].clone())
}
pub fn confirm_scope(&self, suggested_scope: &str, available_scopes: &[String]) -> Result<Option<String>> {
println!("\nSuggested scope: {}", suggested_scope);
let mut options = vec!["Use suggested scope", "Enter custom scope", "No scope"];
options.extend(available_scopes.iter().map(|s| s.as_str()));
let selected = Select::new().with_prompt("Choose scope").items(&options).default(0).interact()?;
match selected {
0 => Ok(Some(suggested_scope.to_string())),
1 => {
let input: String = Input::new().with_prompt("Enter custom scope").interact_text()?;
Ok(if input.trim().is_empty() { None } else { Some(input) })
}
2 => Ok(None),
_ => {
let idx = selected - 3;
Ok(if idx < available_scopes.len() { Some(available_scopes[idx].clone()) } else { None })
}
}
}
pub fn confirm_description(&self, suggested: &str) -> Result<String> {
let input: String = Input::new().with_prompt("Commit description").with_initial_text(suggested).interact_text()?;
Ok(input)
}
pub fn confirm_commit(&self, commit_message: &str) -> Result<bool> {
println!("\nGenerated commit message:\n {}", commit_message);
Confirm::new().with_prompt("Use this commit message?").default(true).interact()
.map_err(|e| crate::error::Error::Other(e.to_string()))
}
pub fn select_type_interactively(&self, suggestions: &[(CommitType, f64)]) -> Result<CommitType> {
let items: Vec<String> = suggestions.iter().map(|(t, conf)| format!("{} ({:.0}%)", t, conf * 100.0)).collect();
let selected = MultiSelect::new().with_prompt("Select commit types").items(&items.iter().map(|s| s.as_str()).collect::<Vec<&str>>()).interact()?;
if selected.is_empty() { return Ok(CommitType::Chore); }
Ok(suggestions[selected[0]].0.clone())
}
pub fn show_alternatives(&self, alternatives: &[String]) -> Result<Option<usize>> {
if alternatives.is_empty() { return Ok(None); }
let selected = Select::new().with_prompt("Multiple possibilities detected. Choose one:").items(&alternatives.iter().map(|s| s.as_str()).collect::<Vec<&str>>()).default(0).interact()?;
Ok(Some(selected))
}
}
#[cfg(test)] mod tests {
use super::*;
#[test] fn test_interactive_prompter_new() {
let prompter = InteractivePrompter::new();
assert!(prompter.select_commit_type(&CommitType::Feat, &[CommitType::Feat, CommitType::Fix]).is_ok());
}
}