1use std::io::{self, Read, Write};
4
5#[derive(Debug, Default)]
7pub struct InputHandler {
8 buffer: String,
9}
10
11impl InputHandler {
12 pub fn new() -> Self {
14 Self::default()
15 }
16
17 pub fn read_line(&mut self) -> io::Result<String> {
19 self.buffer.clear();
20 io::stdin().read_line(&mut self.buffer)?;
21 Ok(self.buffer.trim().to_string())
22 }
23
24 pub fn read_char(&mut self) -> io::Result<char> {
26 let mut buffer = [0; 1];
27 io::stdin().read_exact(&mut buffer)?;
28 Ok(buffer[0] as char)
29 }
30
31 pub fn prompt(&mut self, message: &str) -> io::Result<String> {
33 print!("{}", message);
34 io::stdout().flush()?;
35 self.read_line()
36 }
37
38 pub fn read_number<T>(&mut self, prompt: &str) -> io::Result<T>
40 where
41 T: std::str::FromStr,
42 T::Err: std::fmt::Display,
43 {
44 loop {
45 let input = self.prompt(prompt)?;
46 match input.parse::<T>() {
47 Ok(value) => return Ok(value),
48 Err(e) => {
49 println!("Invalid input: {}. Please try again.", e);
50 }
51 }
52 }
53 }
54
55 pub fn read_yes_no(&mut self, prompt: &str) -> io::Result<bool> {
57 loop {
58 let input = self.prompt(&format!("{} (y/n): ", prompt))?;
59 match input.to_lowercase().as_str() {
60 "y" | "yes" | "true" | "1" => return Ok(true),
61 "n" | "no" | "false" | "0" => return Ok(false),
62 _ => println!("Please enter 'y' for yes or 'n' for no."),
63 }
64 }
65 }
66
67 pub fn read_validated<F>(&mut self, prompt: &str, validator: F) -> io::Result<String>
69 where
70 F: Fn(&str) -> Result<(), String>,
71 {
72 loop {
73 let input = self.prompt(prompt)?;
74 match validator(&input) {
75 Ok(()) => return Ok(input),
76 Err(error) => {
77 println!("Invalid input: {}. Please try again.", error);
78 }
79 }
80 }
81 }
82
83 pub fn read_choice(&mut self, prompt: &str, choices: &[&str]) -> io::Result<usize> {
85 loop {
86 println!("{}", prompt);
87 for (i, choice) in choices.iter().enumerate() {
88 println!("{}. {}", i + 1, choice);
89 }
90
91 let input = self.prompt("Enter your choice (number): ")?;
92 match input.parse::<usize>() {
93 Ok(choice) if choice > 0 && choice <= choices.len() => {
94 return Ok(choice - 1);
95 }
96 _ => {
97 println!(
98 "Please enter a number between 1 and {}.",
99 choices.len()
100 );
101 }
102 }
103 }
104 }
105
106 pub fn clear_buffer(&mut self) {
108 self.buffer.clear();
109 }
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum KeyCode {
115 Char(char),
116 Enter,
117 Escape,
118 Space,
119 Backspace,
120 Tab,
121 Up,
122 Down,
123 Left,
124 Right,
125 Home,
126 End,
127 PageUp,
128 PageDown,
129 Delete,
130 Insert,
131 F1,
132 F2,
133 F3,
134 F4,
135 F5,
136 F6,
137 F7,
138 F8,
139 F9,
140 F10,
141 F11,
142 F12,
143}
144
145#[derive(Debug, Clone, PartialEq, Eq)]
147pub enum InputEvent {
148 Key(KeyCode),
149 Resize(u16, u16), Mouse(u16, u16), }
152
153#[derive(Debug)]
155pub struct NonBlockingInput {
156 _private: (),
157}
158
159impl NonBlockingInput {
160 pub fn new() -> io::Result<Self> {
162 Ok(Self { _private: () })
163 }
164
165 pub fn poll_event(&mut self) -> io::Result<Option<InputEvent>> {
167 Ok(None)
171 }
172
173 pub fn has_input(&self) -> bool {
175 false
177 }
178}
179
180impl Default for NonBlockingInput {
181 fn default() -> Self {
182 Self::new().unwrap()
183 }
184}
185
186#[derive(Debug)]
188pub struct Menu {
189 title: String,
190 items: Vec<String>,
191 selected_index: usize,
192}
193
194impl Menu {
195 pub fn new(title: String, items: Vec<String>) -> Self {
197 Self {
198 title,
199 items,
200 selected_index: 0,
201 }
202 }
203
204 pub fn show(&mut self, input_handler: &mut InputHandler) -> io::Result<usize> {
206 loop {
207 print!("\x1B[2J\x1B[H");
209
210 println!("{}", self.title);
212 println!("{}", "=".repeat(self.title.len()));
213 println!();
214
215 for (i, item) in self.items.iter().enumerate() {
217 if i == self.selected_index {
218 println!("> {}", item);
219 } else {
220 println!(" {}", item);
221 }
222 }
223
224 println!();
225 println!("Use numbers to select, or 'q' to quit:");
226
227 let input = input_handler.read_line()?;
228
229 if input == "q" || input == "quit" {
230 return Err(io::Error::new(io::ErrorKind::Interrupted, "User quit"));
231 }
232
233 match input.parse::<usize>() {
234 Ok(choice) if choice > 0 && choice <= self.items.len() => {
235 return Ok(choice - 1);
236 }
237 _ => {
238 println!("Please enter a number between 1 and {} or 'q' to quit.", self.items.len());
239 println!("Press Enter to continue...");
240 input_handler.read_line()?;
241 }
242 }
243 }
244 }
245
246 pub fn set_selected(&mut self, index: usize) {
248 if index < self.items.len() {
249 self.selected_index = index;
250 }
251 }
252
253 pub fn get_selected(&self) -> usize {
255 self.selected_index
256 }
257
258 pub fn add_item(&mut self, item: String) {
260 self.items.push(item);
261 }
262
263 pub fn remove_item(&mut self, index: usize) -> Option<String> {
265 if index < self.items.len() {
266 Some(self.items.remove(index))
267 } else {
268 None
269 }
270 }
271
272 pub fn len(&self) -> usize {
274 self.items.len()
275 }
276
277 pub fn is_empty(&self) -> bool {
279 self.items.is_empty()
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn test_input_handler_creation() {
289 let handler = InputHandler::new();
290 assert_eq!(handler.buffer.len(), 0);
291 }
292
293 #[test]
294 fn test_non_blocking_input_creation() {
295 let input = NonBlockingInput::new();
296 assert!(input.is_ok());
297 }
298
299 #[test]
300 fn test_menu_creation() {
301 let menu = Menu::new(
302 "Test Menu".to_string(),
303 vec!["Option 1".to_string(), "Option 2".to_string()],
304 );
305 assert_eq!(menu.len(), 2);
306 assert_eq!(menu.get_selected(), 0);
307 }
308
309 #[test]
310 fn test_menu_selection() {
311 let mut menu = Menu::new(
312 "Test Menu".to_string(),
313 vec!["Option 1".to_string(), "Option 2".to_string()],
314 );
315 menu.set_selected(1);
316 assert_eq!(menu.get_selected(), 1);
317 }
318
319 #[test]
320 fn test_menu_add_remove() {
321 let mut menu = Menu::new("Test".to_string(), vec![]);
322 menu.add_item("Item 1".to_string());
323 assert_eq!(menu.len(), 1);
324
325 let removed = menu.remove_item(0);
326 assert_eq!(removed, Some("Item 1".to_string()));
327 assert_eq!(menu.len(), 0);
328 }
329}