use std::io::{self, Read, Write};
#[derive(Debug, Default)]
pub struct InputHandler {
buffer: String,
}
impl InputHandler {
pub fn new() -> Self {
Self::default()
}
pub fn read_line(&mut self) -> io::Result<String> {
self.buffer.clear();
io::stdin().read_line(&mut self.buffer)?;
Ok(self.buffer.trim().to_string())
}
pub fn read_char(&mut self) -> io::Result<char> {
let mut buffer = [0; 1];
io::stdin().read_exact(&mut buffer)?;
Ok(buffer[0] as char)
}
pub fn prompt(&mut self, message: &str) -> io::Result<String> {
print!("{}", message);
io::stdout().flush()?;
self.read_line()
}
pub fn read_number<T>(&mut self, prompt: &str) -> io::Result<T>
where
T: std::str::FromStr,
T::Err: std::fmt::Display,
{
loop {
let input = self.prompt(prompt)?;
match input.parse::<T>() {
Ok(value) => return Ok(value),
Err(e) => {
println!("Invalid input: {}. Please try again.", e);
}
}
}
}
pub fn read_yes_no(&mut self, prompt: &str) -> io::Result<bool> {
loop {
let input = self.prompt(&format!("{} (y/n): ", prompt))?;
match input.to_lowercase().as_str() {
"y" | "yes" | "true" | "1" => return Ok(true),
"n" | "no" | "false" | "0" => return Ok(false),
_ => println!("Please enter 'y' for yes or 'n' for no."),
}
}
}
pub fn read_validated<F>(&mut self, prompt: &str, validator: F) -> io::Result<String>
where
F: Fn(&str) -> Result<(), String>,
{
loop {
let input = self.prompt(prompt)?;
match validator(&input) {
Ok(()) => return Ok(input),
Err(error) => {
println!("Invalid input: {}. Please try again.", error);
}
}
}
}
pub fn read_choice(&mut self, prompt: &str, choices: &[&str]) -> io::Result<usize> {
loop {
println!("{}", prompt);
for (i, choice) in choices.iter().enumerate() {
println!("{}. {}", i + 1, choice);
}
let input = self.prompt("Enter your choice (number): ")?;
match input.parse::<usize>() {
Ok(choice) if choice > 0 && choice <= choices.len() => {
return Ok(choice - 1);
}
_ => {
println!(
"Please enter a number between 1 and {}.",
choices.len()
);
}
}
}
}
pub fn clear_buffer(&mut self) {
self.buffer.clear();
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyCode {
Char(char),
Enter,
Escape,
Space,
Backspace,
Tab,
Up,
Down,
Left,
Right,
Home,
End,
PageUp,
PageDown,
Delete,
Insert,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InputEvent {
Key(KeyCode),
Resize(u16, u16), Mouse(u16, u16), }
#[derive(Debug)]
pub struct NonBlockingInput {
_private: (),
}
impl NonBlockingInput {
pub fn new() -> io::Result<Self> {
Ok(Self { _private: () })
}
pub fn poll_event(&mut self) -> io::Result<Option<InputEvent>> {
Ok(None)
}
pub fn has_input(&self) -> bool {
false
}
}
impl Default for NonBlockingInput {
fn default() -> Self {
Self::new().unwrap()
}
}
#[derive(Debug)]
pub struct Menu {
title: String,
items: Vec<String>,
selected_index: usize,
}
impl Menu {
pub fn new(title: String, items: Vec<String>) -> Self {
Self {
title,
items,
selected_index: 0,
}
}
pub fn show(&mut self, input_handler: &mut InputHandler) -> io::Result<usize> {
loop {
print!("\x1B[2J\x1B[H");
println!("{}", self.title);
println!("{}", "=".repeat(self.title.len()));
println!();
for (i, item) in self.items.iter().enumerate() {
if i == self.selected_index {
println!("> {}", item);
} else {
println!(" {}", item);
}
}
println!();
println!("Use numbers to select, or 'q' to quit:");
let input = input_handler.read_line()?;
if input == "q" || input == "quit" {
return Err(io::Error::new(io::ErrorKind::Interrupted, "User quit"));
}
match input.parse::<usize>() {
Ok(choice) if choice > 0 && choice <= self.items.len() => {
return Ok(choice - 1);
}
_ => {
println!("Please enter a number between 1 and {} or 'q' to quit.", self.items.len());
println!("Press Enter to continue...");
input_handler.read_line()?;
}
}
}
}
pub fn set_selected(&mut self, index: usize) {
if index < self.items.len() {
self.selected_index = index;
}
}
pub fn get_selected(&self) -> usize {
self.selected_index
}
pub fn add_item(&mut self, item: String) {
self.items.push(item);
}
pub fn remove_item(&mut self, index: usize) -> Option<String> {
if index < self.items.len() {
Some(self.items.remove(index))
} else {
None
}
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_input_handler_creation() {
let handler = InputHandler::new();
assert_eq!(handler.buffer.len(), 0);
}
#[test]
fn test_non_blocking_input_creation() {
let input = NonBlockingInput::new();
assert!(input.is_ok());
}
#[test]
fn test_menu_creation() {
let menu = Menu::new(
"Test Menu".to_string(),
vec!["Option 1".to_string(), "Option 2".to_string()],
);
assert_eq!(menu.len(), 2);
assert_eq!(menu.get_selected(), 0);
}
#[test]
fn test_menu_selection() {
let mut menu = Menu::new(
"Test Menu".to_string(),
vec!["Option 1".to_string(), "Option 2".to_string()],
);
menu.set_selected(1);
assert_eq!(menu.get_selected(), 1);
}
#[test]
fn test_menu_add_remove() {
let mut menu = Menu::new("Test".to_string(), vec![]);
menu.add_item("Item 1".to_string());
assert_eq!(menu.len(), 1);
let removed = menu.remove_item(0);
assert_eq!(removed, Some("Item 1".to_string()));
assert_eq!(menu.len(), 0);
}
}