quiz/
lib.rs

1use std::io::{stdin, stdout, Write};
2use termion::event::Key;
3use termion::input::TermRead;
4use termion::raw::IntoRawMode;
5
6pub fn select<'a, T: std::fmt::Display>(prompt: &str, list: &'a [T]) -> Option<&'a T> {
7    let stdin = stdin();
8    let mut stdout = stdout().into_raw_mode().unwrap();
9
10    let last_item_id = list.len() - 1;
11
12    write!(
13        stdout,
14        "{} ? {}{}{}{}\n\r",
15        termion::color::Fg(termion::color::Green),
16        termion::style::Reset,
17        termion::style::Bold,
18        prompt,
19        termion::style::Reset
20    )
21    .unwrap();
22
23    let mut curr = 0;
24    let mut input = stdin.keys();
25
26    loop {
27        for (i, s) in list.iter().enumerate() {
28            write!(stdout, "{}", termion::clear::CurrentLine).unwrap();
29
30            if curr == i {
31                write!(
32                    stdout,
33                    "{}{} {} {}{}",
34                    termion::style::Bold,
35                    termion::color::Fg(termion::color::Cyan),
36                    figures_rs::POINTER,
37                    s,
38                    termion::style::Reset
39                )
40                .unwrap();
41            } else {
42                write!(stdout, "   {}", s).unwrap();
43            }
44
45            write!(stdout, "\n\r").unwrap();
46        }
47        stdout.flush().unwrap();
48
49        let next = input.next().unwrap();
50
51        match next.unwrap() {
52            Key::Char('\n') | Key::Char('l') => {
53                write!(stdout, "\n\r{}", termion::cursor::Show).unwrap();
54                return list.get(curr);
55            }
56            Key::Up | Key::Char('k') => {
57                if curr == 0 {
58                    curr = last_item_id;
59                } else {
60                    curr -= 1;
61                }
62            }
63            Key::Down | Key::Char('j') => {
64                if curr == last_item_id {
65                    curr = 0;
66                } else {
67                    curr += 1;
68                }
69            }
70            Key::Ctrl('c') | Key::Char('q') => {
71                write!(stdout, "\n\r{}", termion::cursor::Show).unwrap();
72                return None;
73            }
74            _ => {}
75        };
76
77        print!("{}", termion::cursor::Up(list.len() as u16));
78    }
79}
80
81pub fn checkbox<'a, T: std::fmt::Display>(
82    prompt: &'a str,
83    list: &'a [T],
84) -> Option<Vec<(&'a T, bool)>> {
85    let mut list: Vec<(&T, bool)> = list.iter().map(|item| (item, false)).collect();
86
87    let stdin = stdin();
88    let mut stdout = stdout().into_raw_mode().unwrap();
89
90    let last_item_id = list.len() - 1;
91
92    write!(
93        stdout,
94        "{} ? {}{}{}{}\n\r",
95        termion::color::Fg(termion::color::Green),
96        termion::style::Reset,
97        termion::style::Bold,
98        prompt,
99        termion::style::Reset
100    )
101    .unwrap();
102
103    let mut curr = 0;
104    let mut input = stdin.keys();
105
106    fn print_check<'a, T>(s: &(&T, bool)) -> &'a str {
107        if s.1 {
108            figures_rs::CIRCLE_FILLED
109        } else {
110            figures_rs::CIRCLE
111        }
112    }
113
114    loop {
115        for (i, s) in list.iter_mut().enumerate() {
116            write!(stdout, "{}", termion::clear::CurrentLine).unwrap();
117
118            if curr == i {
119                write!(
120                    stdout,
121                    "{}{} {} {} {}{}",
122                    termion::style::Bold,
123                    termion::color::Fg(termion::color::Cyan),
124                    figures_rs::POINTER,
125                    print_check(s),
126                    s.0,
127                    termion::style::Reset
128                )
129                .unwrap();
130            } else {
131                write!(stdout, "   {} {}", print_check(s), s.0).unwrap();
132            }
133
134            write!(stdout, "\n\r").unwrap();
135        }
136        stdout.flush().unwrap();
137
138        let next = input.next().unwrap();
139
140        match next.unwrap() {
141            Key::Char('\n') | Key::Char('l') => {
142                write!(stdout, "\n\r{}", termion::cursor::Show).unwrap();
143                return Some(list);
144            }
145            Key::Char(' ') => {
146                list[curr].1 = !list[curr].1;
147            }
148            Key::Up | Key::Char('k') => {
149                if curr == 0 {
150                    curr = last_item_id;
151                } else {
152                    curr -= 1;
153                }
154            }
155            Key::Down | Key::Char('j') => {
156                if curr == last_item_id {
157                    curr = 0;
158                } else {
159                    curr += 1;
160                }
161            }
162            Key::Ctrl('c') | Key::Char('q') => {
163                write!(stdout, "\n\r{}", termion::cursor::Show).unwrap();
164                return None;
165            }
166            _ => {}
167        };
168
169        print!("{}", termion::cursor::Up(list.len() as u16));
170    }
171}
172
173pub fn confirm(prompt: &str) -> bool {
174    let stdin = stdin();
175    let mut stdout = stdout().into_raw_mode().unwrap();
176
177    write!(
178        stdout,
179        "{} ? {}{}{}{} (y/n) ",
180        termion::color::Fg(termion::color::Green),
181        termion::style::Reset,
182        termion::style::Bold,
183        prompt,
184        termion::style::Reset
185    )
186    .unwrap();
187
188    let mut curr = None;
189    let mut input = stdin.keys();
190
191    fn curr_to_letter(curr: &Option<bool>) -> &str {
192        if let Some(curr) = curr {
193            if *curr {
194                "Y"
195            } else {
196                "N"
197            }
198        } else {
199            " "
200        }
201    }
202
203    write!(stdout, "{}", termion::cursor::Hide).unwrap();
204    loop {
205        write!(
206            stdout,
207            "{}{}",
208            termion::clear::AfterCursor,
209            curr_to_letter(&curr)
210        )
211        .unwrap();
212        print!("{}", termion::cursor::Left(1));
213        stdout.flush().unwrap();
214
215        let next = input.next().unwrap();
216
217        match next.unwrap() {
218            Key::Char('\n') | Key::Char('l') => {
219                write!(stdout, "\n\r{}", termion::cursor::Show).unwrap();
220                return if let Some(true) = curr { true } else { false };
221            }
222            Key::Up | Key::Char('k') => {
223                if let Some(c) = curr {
224                    curr = Some(!c);
225                } else {
226                    curr = Some(false);
227                }
228            }
229            Key::Down | Key::Char('j') => {
230                if let Some(c) = curr {
231                    curr = Some(!c);
232                } else {
233                    curr = Some(true);
234                }
235            }
236            Key::Char('y') | Key::Char('Y') => curr = Some(true),
237            Key::Char('n') | Key::Char('N') => curr = Some(false),
238            Key::Backspace => curr = None,
239            Key::Ctrl('c') | Key::Char('q') => {
240                write!(stdout, "\n\r{}", termion::cursor::Show).unwrap();
241                return false;
242            }
243            _ => {}
244        };
245    }
246}