use crate::validation::Validator;
use libc::{tcgetattr, tcsetattr, ECHO, ICANON, TCSANOW};
use std::fmt::Debug;
use std::io::{self, stdin, Read, Write};
use std::os::unix::io::AsRawFd;
use std::str::FromStr;
pub fn read_input<T>(prompt: &str, validator: Option<&Validator>) -> Result<T, String>
where
T: FromStr,
T::Err: Debug,
{
use std::io::{self, Write};
loop {
print!("{} ", prompt);
io::stdout()
.flush()
.map_err(|e| format!("Failed to flush stdout: {:?}", e))?;
let mut input = String::new();
stdin()
.read_line(&mut input)
.map_err(|e| format!("Failed to read line: {:?}", e))?;
let input = input.trim();
if let Some(validator) = validator {
if let Err(err) = validator.validate(input) {
eprintln!("{}", err);
continue;
}
}
return input.parse::<T>().map_err(|err| format!("{:?}", err));
}
}
pub fn read_select<T>(prompt: &str, options: &[(T, String)]) -> Result<T, String>
where
T: Clone + PartialEq + Debug + FromStr,
T::Err: Debug,
{
let mut selected = 0;
loop {
clear_screen();
println!("{}:", prompt);
for (i, (_, value)) in options.iter().enumerate() {
if i == selected {
println!("> {}", value);
} else {
println!(" {}", value);
}
}
io::stdout()
.flush()
.map_err(|e| format!("Failed to flush stdout: {:?}", e))?;
match read_key_raw()? {
Key::Up => {
if selected > 0 {
selected -= 1;
}
}
Key::Down => {
if selected < options.len() - 1 {
selected += 1;
}
}
Key::Enter => {
clear_screen();
return Ok(options[selected].0.clone());
}
_ => {}
}
}
}
pub fn read_multiselect<T>(
prompt: &str,
options: &[(T, String)],
limit: Option<usize>,
) -> Result<Vec<T>, String>
where
T: Clone + PartialEq + Debug + FromStr,
T::Err: Debug,
{
let mut selected = 0;
let mut selected_options = vec![false; options.len()];
loop {
clear_screen();
println!("{}:", prompt);
println!("Use Space to select/deselect, Enter to confirm");
for (i, (_, value)) in options.iter().enumerate() {
let marker = if selected_options[i] { "*" } else { " " };
if i == selected {
println!("> [{}] {}", marker, value);
} else {
println!(" [{}] {}", marker, value);
}
}
io::stdout()
.flush()
.map_err(|e| format!("Failed to flush stdout: {:?}", e))?;
match read_key_raw()? {
Key::Up => {
if selected > 0 {
selected -= 1;
}
}
Key::Down => {
if selected < options.len() - 1 {
selected += 1;
}
}
Key::Space => {
if selected_options[selected] {
selected_options[selected] = false;
} else if limit.is_none()
|| selected_options.iter().filter(|&&x| x).count() < limit.unwrap()
{
selected_options[selected] = true;
}
}
Key::Enter => {
let selected_keys: Vec<T> = options
.iter()
.enumerate()
.filter(|(i, _)| selected_options[*i])
.map(|(_, (key, _))| key.clone())
.collect();
if !selected_keys.is_empty() {
clear_screen();
return Ok(selected_keys);
}
}
_ => {}
}
}
}
pub enum Key {
Up,
Down,
Enter,
Space,
Other,
}
pub fn clear_screen() {
print!("\x1B[2J\x1B[1;1H");
if io::stdout().flush().is_err() {
eprintln!("Failed to flush stdout");
}
}
pub fn read_key_raw() -> Result<Key, String> {
let stdin_fd = stdin().as_raw_fd();
let mut termios = unsafe { std::mem::zeroed() };
if unsafe { tcgetattr(stdin_fd, &mut termios) } < 0 {
return Err("Failed to get terminal attributes".to_string());
}
termios.c_lflag &= !(ICANON | ECHO);
if unsafe { tcsetattr(stdin_fd, TCSANOW, &termios) } < 0 {
return Err("Failed to set terminal attributes".to_string());
}
let mut buffer = [0; 1];
if stdin().read_exact(&mut buffer).is_err() {
return Err("Failed to read from stdin".to_string());
}
termios.c_lflag |= ICANON | ECHO;
if unsafe { tcsetattr(stdin_fd, TCSANOW, &termios) } < 0 {
return Err("Failed to reset terminal attributes".to_string());
}
match buffer[0] {
65 => Ok(Key::Up),
66 => Ok(Key::Down),
10 => Ok(Key::Enter),
32 => Ok(Key::Space),
_ => Ok(Key::Other),
}
}