codeberg_cli/render/ui/
mod.rs

1use anyhow::Context;
2
3/// Given a list of `items`, this function allows the user to interactively select a subset of the
4/// given list.
5///
6/// - To guide the user, you have to provide a `prompt`
7/// - The `is_selected` closure can help to preselect a certain set of items that pass the
8///   predicate
9/// - Some types are not natively displayable. In these cases you can decide what to show to the
10///   user for each item with `f_display`
11pub fn multi_fuzzy_select_with_key<T>(
12    items: &[T],
13    prompt: impl AsRef<str>,
14    is_selected: impl Fn(&T) -> bool,
15    f_display: impl Fn(&T) -> String,
16) -> anyhow::Result<Vec<&T>> {
17    // collect pre-selected items
18    let already_selected = items
19        .iter()
20        .enumerate()
21        .filter(|(_, elem)| is_selected(elem))
22        .map(|(idx, _)| idx)
23        .collect::<Vec<_>>();
24
25    // collect what's shown to the user
26    let displayed_items = items.iter().map(f_display).collect::<Vec<_>>();
27
28    // do the interactive selection
29    let selected_indices = inquire::MultiSelect::new(prompt.as_ref(), displayed_items)
30        .with_default(&already_selected)
31        .raw_prompt()
32        .context("There's nothing to select from")?;
33
34    // get the items for the selected indices
35    let selected_items = selected_indices
36        .into_iter()
37        .map(|raw| raw.index)
38        .filter_map(|idx| items.get(idx))
39        .collect::<Vec<_>>();
40
41    Ok(selected_items)
42}
43
44/// Basically the same as [`fuzzy_select_with_key_with_default`] without a default value
45pub fn fuzzy_select_with_key<T>(
46    items: &[T],
47    prompt: impl AsRef<str>,
48    f_display: impl Fn(&T) -> String,
49) -> anyhow::Result<&T> {
50    fuzzy_select_with_key_with_default(items, prompt, f_display, None)
51}
52
53/// Given a list of `items`, this function allows the user to interactively select a *exactly one*
54/// item of the given list.
55///
56/// - To guide the user, you have to provide a `prompt`
57/// - Some types are not natively displayable. In these cases you can decide what to show to the
58///   user for each item with `f_display`
59/// - The `default_index` optional index value can pre-select one item
60pub fn fuzzy_select_with_key_with_default<T>(
61    items: &[T],
62    prompt: impl AsRef<str>,
63    f_display: impl Fn(&T) -> String,
64    default_index: Option<usize>,
65) -> anyhow::Result<&T> {
66    // return `None` if we have nothing to select from
67    if items.is_empty() {
68        anyhow::bail!("Nothing to select from. Aborting.")
69    }
70
71    let displayed_items = items.iter().map(f_display).collect::<Vec<_>>();
72
73    // build standard dialogue
74    let mut dialogue = inquire::Select::new(prompt.as_ref(), displayed_items);
75
76    // optionally add default selection
77    if let Some(index) = default_index {
78        dialogue = dialogue.with_starting_cursor(index);
79    }
80
81    // select an item by key
82    let selected_index = dialogue.raw_prompt().map_err(anyhow::Error::from)?.index;
83
84    Ok(&items[selected_index])
85}
86
87/// Common confimation prompt (y/n) which maps
88///
89/// - `y` -> `true`
90/// - `n` -> `false`
91pub fn confirm_with_prompt(prompt: &str) -> anyhow::Result<bool> {
92    inquire::Confirm::new(prompt)
93        .with_help_message("(y/n)?")
94        .prompt()
95        .map_err(anyhow::Error::from)
96}