use console::Style;
use dialoguer::{theme::ColorfulTheme, Confirm, Input, MultiSelect, Select};
pub fn is_interactive_terminal() -> bool {
atty::is(atty::Stream::Stdin) && atty::is(atty::Stream::Stdout)
}
fn create_enhanced_theme() -> ColorfulTheme {
ColorfulTheme {
active_item_style: Style::new().cyan().bold(),
active_item_prefix: console::style("❯".to_string()).cyan().bold(),
inactive_item_prefix: console::style(" ".to_string()),
..Default::default()
}
}
macro_rules! interact {
($builder:expr, $method:ident, $kind:literal) => {
$builder
.$method()
.map_err(|e| anyhow::anyhow!(concat!("Failed to get user ", $kind, ": {}"), e))
};
}
#[allow(dead_code)]
pub fn confirm(prompt: &str, default: bool) -> anyhow::Result<bool> {
let theme = create_enhanced_theme();
interact!(
Confirm::with_theme(&theme).with_prompt(prompt).default(default),
interact,
"confirmation"
)
}
#[allow(dead_code)]
pub fn select<T: ToString + std::fmt::Display>(prompt: &str, items: &[T]) -> anyhow::Result<usize> {
let theme = create_enhanced_theme();
interact!(
Select::with_theme(&theme).with_prompt(prompt).items(items),
interact,
"selection"
)
}
#[allow(dead_code)]
pub fn input(prompt: &str) -> anyhow::Result<String> {
let theme = create_enhanced_theme();
interact!(
Input::<String>::with_theme(&theme).with_prompt(prompt),
interact_text,
"input"
)
}
pub fn multiselect(prompt: &str, items: &[&str], checked: &[bool]) -> anyhow::Result<Vec<usize>> {
const SELECT_ALL_LABEL: &str = "Select all";
let theme = create_enhanced_theme();
let mut display_items = Vec::with_capacity(items.len() + 1);
display_items.push(SELECT_ALL_LABEL);
display_items.extend_from_slice(items);
let mut display_checked = Vec::with_capacity(items.len() + 1);
display_checked.push(false);
if checked.len() == items.len() {
display_checked.extend_from_slice(checked);
} else {
display_checked.extend(std::iter::repeat(false).take(items.len()));
}
let selections = MultiSelect::with_theme(&theme)
.with_prompt(prompt)
.items(&display_items)
.defaults(&display_checked)
.interact()
.map_err(|e| anyhow::anyhow!("Failed to get user selection: {}", e))?;
Ok(resolve_multiselect(&selections, items.len()))
}
fn resolve_multiselect(raw_selections: &[usize], num_items: usize) -> Vec<usize> {
if raw_selections.contains(&0) {
return (0..num_items).collect();
}
let mut resolved: Vec<usize> = raw_selections.iter().filter_map(|i| i.checked_sub(1)).collect();
resolved.sort_unstable();
resolved
}
#[cfg(test)]
mod multiselect_tests {
use super::*;
#[test]
fn resolve_multiselect_maps_single_real_selection_to_zero_indexed_item() {
assert_eq!(resolve_multiselect(&[2], 3), vec![1]);
}
#[test]
fn resolve_multiselect_returns_multiple_selections_sorted() {
assert_eq!(resolve_multiselect(&[3, 1], 3), vec![0, 2]);
}
#[test]
fn resolve_multiselect_select_all_alone_returns_every_item() {
assert_eq!(resolve_multiselect(&[0], 4), vec![0, 1, 2, 3]);
}
#[test]
fn resolve_multiselect_select_all_plus_others_still_returns_every_item() {
assert_eq!(resolve_multiselect(&[0, 1], 4), vec![0, 1, 2, 3]);
}
#[test]
fn resolve_multiselect_empty_selection_returns_empty() {
assert_eq!(resolve_multiselect(&[], 3), Vec::<usize>::new());
}
}