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}