1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
//! A simple library for console-based user input and option selection.

use console::{Key, Term};
use std::io::{self, Write};

/// Reads user input from the console.
///
/// This function prompts the user with a message (`before`) and reads a line of input from the
/// console. The input can be empty unless `allow_empty` is set to `false`. If `new_line` is set
/// to `true`, a newline character will be printed after the prompt.
///
/// # Arguments
///
/// * `before` - The text to display before prompting for input.
/// * `allow_empty` - If true, allows the input to be empty.
/// * `new_line` - If true, adds a newline character after the prompt.
///
/// # Returns
///
/// Returns an `Option<String>` containing the user's input or `None` if the input is empty and
/// `allow_empty` is `false`.
///
/// # Example
///
/// ```no_run
/// use console_utils::input;
///     
/// let user_input = input("Enter something: ", false, false);
///
/// match user_input {
///     Some(value) => println!("You entered: {}", value),
///     None => println!("Input is empty."),
/// }
/// ```
pub fn input(before: &str, allow_empty: bool, new_line: bool) -> Option<String> {
    loop {
        print!("{before} {}", if new_line { '\n' } else { '\0' });
        io::stdout().flush().unwrap();

        let mut cli = String::new();
        io::stdin().read_line(&mut cli).unwrap();

        if allow_empty && cli.trim().is_empty() {
            return None;
        } else if !cli.trim().is_empty() {
            return Some(cli.trim().to_owned());
        } else {
            println!("\nWrong Input\n");
        }
    }
}

/// Allows the user to select options from a list using the console.
///
/// This function displays a list of options with checkboxes. The user can navigate through the
/// options using arrow keys or 'w' and 's' keys. Pressing the spacebar toggles the selection of
/// the current option. If the user presses Enter, the function returns a vector of booleans
/// indicating which options were selected.
///
/// # Arguments
///
/// * `before` - The text to display before the list of options.
/// * `options` - A vector of strings representing the available options.
/// * `allow_empty` - If true, allows the user to exit without selecting any option.
/// * `multiple` - If true, allows the user to select multiple options.
///
/// # Returns
///
/// Returns an `Option<Vec<bool>>` containing a vector of booleans indicating which options were
/// selected. Returns `None` if no option was selected and `allow_empty` is `false`.
///
/// # Example
///
/// ```no_run
/// use console_utils::select;
///
/// let options = vec![
///     "Option 1".to_string(),
///     "Option 2".to_string(),
///     "Option 3".to_string(),
/// ];
///
/// let selected_indices = select("Select an option:", &options, false, false);
///
/// match selected_indices {
///     Some(indices) => {
///         println!("Selected indices: {:?}", indices);
///     }
///     None => {
///         println!("No option selected.");
///     }
/// }
/// ```
pub fn select(
    before: &str,
    options: &[String],
    allow_empty: bool,
    multiple: bool,
) -> Option<Vec<bool>> {
    loop {
        let mut matrix: Vec<bool> = vec![];
        let stdout = Term::buffered_stdout();

        println!("{}\n", before,);

        for i in options {
            println!("[ ] {}", i);
            matrix.push(false);
        }

        stdout.move_cursor_up(options.len()).unwrap();
        stdout.flush().unwrap();
        let mut i = 0;

        loop {
            if let Ok(character) = stdout.read_key() {
                match character {
                    Key::ArrowUp | Key::Char('w') => {
                        if i > 0 {
                            stdout.move_cursor_up(1).unwrap();
                            i -= 1;
                        }
                    }
                    Key::ArrowDown | Key::Char('s') => {
                        if i < options.len() - 1 {
                            stdout.move_cursor_down(1).unwrap();
                            i += 1;
                        }
                    }
                    Key::Char(' ') => {
                        stdout.clear_line().unwrap();
                        if matrix[i] {
                            stdout
                                .write_line(&("[ ] ".to_owned() + &options[i]))
                                .unwrap();
                            matrix[i] = false;
                        } else {
                            stdout
                                .write_line(&("[*] ".to_owned() + &options[i]))
                                .unwrap();
                            matrix[i] = true;
                        }
                        stdout.move_cursor_up(1).unwrap();
                        stdout.flush().unwrap();
                    }
                    Key::Enter => {
                        break;
                    }
                    _ => {}
                }
            }
            stdout.flush().unwrap();
        }

        if matrix.iter().filter(|&&selected| selected).count() > 1 && !multiple {
            reset(stdout, "\nPlease Select only one!\n", options.len());
        } else if allow_empty && matrix.iter().all(|&x| !x) {
            reset(stdout, "", options.len());
            return None;
        } else if !matrix.iter().all(|&x| !x) {
            reset(stdout, "", options.len());
            return Some(matrix);
        } else {
            reset(stdout, "\nWrong Input\n", options.len());
        }
    }
}

// Internal function for resetting the console.
fn reset(stdout: Term, mes: &str, len: usize) {
    stdout.move_cursor_down(len).unwrap();
    stdout.flush().unwrap();
    println!("{mes}");
}