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}