Skip to main content

xbp_cli/cli/
interactive.rs

1//! Shared interactive pickers (type-to-filter FuzzySelect + ANSI theme).
2
3use dialoguer::theme::ColorfulTheme;
4use dialoguer::{FuzzySelect, MultiSelect};
5use std::fmt::Display;
6
7/// Rich dialoguer theme used across XBP interactive prompts.
8pub fn xbp_theme() -> ColorfulTheme {
9    ColorfulTheme::default()
10}
11
12/// Searchable single-select (type to filter). Returns `None` if cancelled.
13pub fn searchable_select<T: Display>(
14    prompt: &str,
15    items: &[T],
16    default: usize,
17) -> Result<Option<usize>, String> {
18    if items.is_empty() {
19        return Ok(None);
20    }
21    let default = default.min(items.len().saturating_sub(1));
22    FuzzySelect::with_theme(&xbp_theme())
23        .with_prompt(format!("{prompt} (type to search)"))
24        .items(items)
25        .default(default)
26        .interact_opt()
27        .map_err(|error| format!("Prompt failed: {error}"))
28}
29
30/// Searchable multi-select. Returns selected indices (may be empty).
31pub fn searchable_multi_select<T: Display>(
32    prompt: &str,
33    items: &[T],
34    defaults: &[bool],
35) -> Result<Vec<usize>, String> {
36    if items.is_empty() {
37        return Ok(Vec::new());
38    }
39    let defaults = if defaults.len() == items.len() {
40        defaults.to_vec()
41    } else {
42        vec![false; items.len()]
43    };
44    MultiSelect::with_theme(&xbp_theme())
45        .with_prompt(format!("{prompt} (space to toggle, type to filter if supported)"))
46        .items(items)
47        .defaults(&defaults)
48        .interact()
49        .map_err(|error| format!("Prompt failed: {error}"))
50}