console_utils/
input.rs

1//! Input Utilities
2//!
3//! This module provides functions for handling user input in console applications, including reading user input,
4//! selecting options from a list, displaying spinners, and gradually revealing strings.
5use std::{io, str::FromStr, thread, time::Duration};
6
7use crate::{
8    control::*,
9    read::{read_key, Key},
10};
11
12/// A Wrapper for empty inputs returning a None
13#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, Default)]
14pub enum Empty<T> {
15    Some(T),
16    #[default]
17    None,
18}
19
20impl<T> FromStr for Empty<T>
21where
22    T: FromStr,
23    T::Err: std::fmt::Debug,
24{
25    type Err = T::Err;
26
27    fn from_str(s: &str) -> Result<Self, Self::Err> {
28        if s.trim().is_empty() {
29            Ok(Empty::None)
30        } else {
31            s.trim().parse::<T>().map(Empty::Some)
32        }
33    }
34}
35
36/// Reads user input from the console.
37///
38/// This function prompts the user with a message (`before`) and reads a line of input from the
39/// console. The input can be empty.
40///
41/// # Arguments
42///
43/// * `before` - The text to display before prompting for input. Add here `\n` for a new line.
44///
45/// # Returns
46///
47/// Returns an `T` containing the user's input converted to the specified type.
48pub fn input<T>(before: &str) -> T
49where
50    T: std::str::FromStr,
51    T::Err: std::fmt::Debug,
52{
53    loop {
54        print!("\x1b[31m?\x1b[0m {before} \x1b[90m›\x1b[0m ");
55        flush();
56
57        let mut cli = String::new();
58        io::stdin().read_line(&mut cli).unwrap();
59
60        match cli.parse() {
61            Ok(value) => return value,
62            Err(_) => println!("\n\x1b[31mX\x1b[0m Invalid Input Type\n"),
63        }
64    }
65}
66
67/// Allows the user to select one option from a list using the console.
68///
69/// This function displays a list of options. The user can navigate through the
70/// options using arrow keys or 'w' and 's' keys. If the user presses Enter, the
71/// function returns the selected option.
72///
73/// # Arguments
74///
75/// * `before` - The text to display before the list of options.
76/// * `options` - A vector of strings representing the available options.
77///
78/// # Returns
79///
80/// Returns an `usize` as an index of the inputted array `options`
81pub fn select<'a>(before: &'a str, options: &'a [&'a str]) -> usize {
82    let mut i = 0;
83
84    // print everything
85    println!("\x1b[31m?\x1b[0m {before} \x1b[90m›\x1b[0m ");
86
87    populate(options, None, 0);
88
89    // hide cursor
90    let vis = Visibility::new();
91    vis.hide_cursor();
92
93    loop {
94        if let Ok(character) = read_key() {
95            match character {
96                Key::ArrowUp | Key::Char('w') | Key::Char('W') => {
97                    if i > 0 {
98                        i -= 1;
99                        populate(options, None, i);
100                    }
101                }
102                Key::ArrowDown | Key::Char('s') | Key::Char('S') => {
103                    if i < options.len() - 1 {
104                        i += 1;
105                        populate(options, None, i);
106                    }
107                }
108                Key::Enter => {
109                    break;
110                }
111                _ => {}
112            }
113        }
114    }
115
116    // reset cursor
117    move_cursor_down(options.len());
118
119    i
120}
121
122/// Allows the user to select multiple options from a list using the console.
123///
124/// This function displays a list of options with checkboxes. The user can navigate through the
125/// options using arrow keys or 'w' and 's' keys. Pressing the spacebar toggles the selection of
126/// the current option. If the user presses Enter, the function returns a vector of booleans
127/// indicating which options were selected.
128///
129/// # Arguments
130///
131/// * `before` - The text to display before the list of options.
132/// * `options` - A vector of strings representing the available options.
133///
134/// # Returns
135///
136/// Returns an `Vec<bool>` containing a vector of booleans indicating which options were
137/// selected.
138pub fn multiselect(before: &str, options: &[&str]) -> Vec<bool> {
139    let mut matrix: Vec<bool> = vec![false; options.len()];
140    let mut i = 0;
141
142    // print everything
143    println!("\x1b[31m?\x1b[0m {before} \x1b[90m›\x1b[0m ");
144
145    populate(options, Some(&matrix), 0);
146
147    // hide cursor
148    let vis = Visibility::new();
149    vis.hide_cursor();
150
151    loop {
152        if let Ok(character) = read_key() {
153            match character {
154                Key::ArrowUp | Key::Char('w') | Key::Char('W') => {
155                    if i > 0 {
156                        i -= 1;
157                        populate(options, Some(&matrix), i);
158                    }
159                }
160                Key::ArrowDown | Key::Char('s') | Key::Char('S') => {
161                    if i < options.len() - 1 {
162                        i += 1;
163                        populate(options, Some(&matrix), i);
164                    }
165                }
166                Key::Char(' ') => {
167                    move_cursor_down(i);
168                    clear_line();
169                    matrix[i] = !matrix[i];
170                    flush();
171                    move_cursor_up(i);
172                    populate(options, Some(&matrix), i);
173                }
174                Key::Enter => {
175                    break;
176                }
177                _ => {}
178            }
179        }
180    }
181
182    // reset cursor
183    move_cursor_down(options.len());
184
185    matrix
186}
187
188/// Populate function for multiselect
189fn populate(options: &[&str], matrix: Option<&[bool]>, cursor: usize) {
190    for (i, option) in options.iter().enumerate() {
191        clear_line();
192        if i == cursor {
193            println!(
194                "\x1b[36m ›\x1b[0m\x1b[3{}m {}\x1b[0m",
195                if matrix.is_some() && matrix.unwrap()[i] {
196                    "2"
197                } else {
198                    "6"
199                },
200                option
201            );
202        } else if matrix.is_some() && matrix.unwrap()[i] {
203            println!("\x1b[32m   {}\x1b[0m", option);
204        } else {
205            println!("   {}", option);
206        }
207    }
208    move_cursor_up(options.len());
209}
210
211/// Enumeration representing different types of spinners.
212#[derive(Debug, Clone)]
213pub enum SpinnerType {
214    Standard,
215    Dots,
216    Box,
217    Flip,
218    Custom(Vec<&'static str>),
219}
220
221impl SpinnerType {
222    /// Converts the spinner type to a vector of frames, gives back the following variants:
223    ///  - `SpinnerType::Standard`: Standard spinner with characters / - \ |.
224    ///  - `SpinnerType::Dots`: Spinner with dots . .. ... .....
225    ///  - `SpinnerType::Box`: Spinner with box characters ▌ ▀ ▐ ▄.
226    ///  - `SpinnerType::Flip`: Spinner with flip characters _ _ _ - \ ' ´ - _ _ _.
227    ///  - `SpinnerType::Custom(frames)`: Custom spinner with user-defined frames.
228    pub fn to_frames(&self) -> Vec<&'static str> {
229        match self {
230            SpinnerType::Standard => vec!["/", "-", "\\", "|"],
231            SpinnerType::Dots => vec![".", "..", "...", "....", "...", ".."],
232            SpinnerType::Box => vec!["▌", "▀", "▐", "▄"],
233            SpinnerType::Flip => vec!["_", "_", "_", "-", "`", "`", "'", "´", "-", "_", "_", "_"],
234            SpinnerType::Custom(frames) => frames.to_owned(),
235        }
236    }
237}
238
239/// Displays a console-based spinner animation.
240///
241/// A spinner is a visual indicator of a long-running process. It consists of a set of frames
242/// that are displayed sequentially to create the appearance of motion.
243///
244/// # Parameters
245///
246/// - `time`: A floating-point number representing the duration of the spinner animation in seconds.
247/// - `spinner_type`: The type of spinner to display.
248pub fn spinner(mut time: f64, spinner_type: SpinnerType) {
249    let frames = spinner_type.to_frames();
250    let mut i = 0;
251
252    while time > 0.0 {
253        clear_line();
254        print!("{}", frames[i]);
255        flush();
256        thread::sleep(Duration::from_secs_f64(0.075));
257        time -= 0.075;
258        if i < frames.len() - 1 {
259            i += 1
260        } else {
261            i = 0
262        }
263    }
264
265    clear_line();
266}
267
268/// Reveals a string gradually, printing one character at a time with a specified time interval.
269///
270/// This function is useful for creating a typing effect or slowly displaying information to the user.
271///
272/// # Arguments
273///
274/// * `str` - The string to reveal gradually. Add here `\n` for a new line.
275/// * `time_between` - The time interval (in seconds) between each revealed character.
276pub fn reveal(str: &str, time_between: f64) {
277    for i in 0..str.len() {
278        print!("{}", str.chars().nth(i).unwrap_or(' '));
279        flush();
280        thread::sleep(Duration::from_secs_f64(time_between));
281    }
282}