#![allow(unused_variables)]
use anyhow::Result;
#[cfg(feature = "tui")]
use crossterm::event::{poll, read, Event, KeyCode};
use std::error::Error;
use std::time::Duration;
#[allow(dead_code)]
struct RawModeGuard;
impl RawModeGuard {
#[allow(dead_code)]
fn new() -> Result<Self> {
#[cfg(feature = "tui")]
crossterm::terminal::enable_raw_mode()?;
Ok(Self)
}
}
impl Drop for RawModeGuard {
fn drop(&mut self) {
#[cfg(feature = "tui")]
let _ = crossterm::terminal::disable_raw_mode();
}
}
pub fn prompt(message: &str, wait_secs: u64) -> Result<Option<char>> {
if !message.trim().is_empty() {
println!("{}", message);
}
use std::io::IsTerminal;
if !io::stdin().is_terminal() || !io::stdout().is_terminal() {
return Ok(None);
}
#[cfg(feature = "tui")]
{
let timeout = Duration::from_secs(wait_secs);
drain_events().ok(); let _raw_guard = RawModeGuard::new()?;
let result = if poll(timeout)? {
if let Event::Key(key_event) = read()? {
if let KeyCode::Char(c) = key_event.code {
if c == '\x03' {
return Err(anyhow::anyhow!("Ctrl+C pressed").into()); }
Some(c.to_ascii_lowercase())
} else {
None
}
} else {
None
}
} else {
None
};
Ok(result)
}
#[cfg(not(feature = "tui"))]
{
use std::io::{self, BufRead};
use std::sync::mpsc;
use std::thread;
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let stdin = io::stdin();
let mut line = String::new();
let _ = stdin.lock().read_line(&mut line);
let _ = tx.send(line);
});
match rx.recv_timeout(Duration::from_secs(wait_secs)) {
Ok(line) => Ok(line.trim().chars().next().map(|c| c.to_ascii_lowercase())),
Err(_) => Ok(None),
}
}
}
pub fn prompt_line(message: &str, wait_secs: u64) -> Result<Option<String>, Box<dyn Error>> {
if !message.trim().is_empty() {
println!("{}", message);
}
use std::io::IsTerminal;
if !std::io::stdin().is_terminal() {
return Ok(None);
}
#[cfg(not(feature = "tui"))]
{
use std::io::{self, BufRead};
use std::sync::mpsc;
use std::thread;
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let stdin = io::stdin();
let mut line = String::new();
let _ = stdin.lock().read_line(&mut line);
let _ = tx.send(line);
});
match rx.recv_timeout(Duration::from_secs(wait_secs)) {
Ok(line) => Ok(Some(line.trim().to_string())),
Err(_) => Ok(None),
}
}
#[cfg(feature = "tui")]
{
use crossterm::event::{poll, read, Event, KeyCode};
use std::io::{self, Write};
#[cfg(feature = "tui")]
let _raw_guard = RawModeGuard::new()?;
let mut input = String::new();
let start = std::time::Instant::now();
loop {
let elapsed = start.elapsed().as_secs();
if elapsed >= wait_secs {
println!("Timeout reached; no input received.");
return Ok(None);
}
let remaining = Duration::from_secs(wait_secs - elapsed);
if poll(remaining)? {
if let Event::Key(key_event) = read()? {
if key_event.kind != crossterm::event::KeyEventKind::Press {
continue;
}
match key_event.code {
KeyCode::Enter => break,
KeyCode::Char(c) => {
input.push(c);
print!("{}", c);
use std::io::{self, Write};
io::stdout().flush()?;
}
KeyCode::Backspace => {
input.pop();
print!("\r{} \r", input);
io::stdout().flush()?;
}
_ => {}
}
}
}
}
println!();
Ok(Some(input.trim().to_string()))
}
}
use std::io::{self, BufRead};
use std::sync::mpsc;
use std::thread;
pub fn read_line_with_timeout(wait_secs: u64) -> io::Result<Option<String>> {
let timeout = Duration::from_secs(wait_secs);
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let stdin = io::stdin();
let mut line = String::new();
let _ = stdin.lock().read_line(&mut line);
let _ = tx.send(line);
});
match rx.recv_timeout(timeout) {
Ok(line) => Ok(Some(line.trim().to_string())),
Err(mpsc::RecvTimeoutError::Timeout) => Ok(None),
Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
}
}
pub fn prompt_line_with_poll(wait_secs: u64) -> Result<Option<String>, Box<dyn std::error::Error>> {
#[cfg(feature = "tui")]
{
#[cfg(feature = "tui")]
let _raw_guard = RawModeGuard::new()?;
let timeout = Duration::from_secs(wait_secs);
let start = std::time::Instant::now();
let mut input = String::new();
loop {
let elapsed = start.elapsed();
if elapsed >= timeout {
return Ok(None);
}
let remaining = timeout - elapsed;
if poll(remaining)? {
if let Event::Key(crossterm::event::KeyEvent { code, kind, .. }) = read()? {
if kind != crossterm::event::KeyEventKind::Press {
continue;
}
match code {
KeyCode::Enter => break,
KeyCode::Char(c) => {
input.push(c);
print!("{}", c);
io::Write::flush(&mut io::stdout())?;
}
KeyCode::Backspace => {
input.pop();
print!("\r{}\r", " ".repeat(input.len() + 1));
print!("{}", input);
io::Write::flush(&mut io::stdout())?;
}
_ => {} }
}
}
}
Ok(Some(input))
}
#[cfg(not(feature = "tui"))]
{
return Ok(read_line_with_timeout(wait_secs).unwrap_or_default());
}
}
pub fn prompt_line_with_poll_opts(
wait_secs: u64,
quick_exit: &[char],
allowed_chars: Option<&[char]>,
) -> Result<Option<String>, Box<dyn Error + Send + Sync>> {
#[cfg(feature = "tui")]
{
let timeout = Duration::from_secs(wait_secs);
let _raw_guard = RawModeGuard::new()?;
let start = std::time::Instant::now();
let mut input = String::new();
loop {
let elapsed = start.elapsed();
if elapsed >= timeout {
return Ok(None);
}
let remaining = timeout - elapsed;
if poll(remaining)? {
if let Event::Key(crossterm::event::KeyEvent { code, kind, .. }) = read()? {
if kind != crossterm::event::KeyEventKind::Press {
continue;
}
match code {
KeyCode::Enter => {
drain_events().ok();
break;
} KeyCode::Char(c) => {
if quick_exit.iter().any(|&qe| qe.eq_ignore_ascii_case(&c)) {
input.push(c);
drain_events().ok(); return Ok(Some(input.to_string()));
}
if let Some(allowed) = allowed_chars {
if !allowed.iter().any(|&a| a.eq_ignore_ascii_case(&c)) {
continue;
}
}
input.push(c);
use crossterm::{
cursor::MoveToColumn,
execute,
terminal::{Clear, ClearType},
};
execute!(io::stdout(), Clear(ClearType::CurrentLine), MoveToColumn(0))?;
print!("{}", input);
io::Write::flush(&mut io::stdout())?;
}
KeyCode::Backspace => {
if !input.is_empty() {
input.pop();
print!("\x08 \x08");
io::Write::flush(&mut io::stdout())?;
}
}
_ => {} }
}
}
}
Ok(Some(input.trim().to_string()))
}
#[cfg(not(feature = "tui"))]
{
return Ok(read_line_with_timeout(wait_secs).unwrap_or_default());
}
}
#[allow(dead_code)]
fn drain_events() -> Result<()> {
#[cfg(feature = "tui")]
while poll(Duration::from_millis(0))? {
let _ = read()?;
}
Ok(())
}
pub fn yesno(prompt_message: &str, default: Option<bool>) -> Result<Option<bool>> {
let prompt_with_default = match default {
Some(true) => format!("{} (Y/n)? ", prompt_message),
Some(false) => format!("{} (y/N)? ", prompt_message),
None => format!("{} (y/n)? ", prompt_message),
};
let result = match prompt(&prompt_with_default, 10)? {
Some(c) => match c {
'y' => Some(true),
'n' => Some(false),
_ => {
println!("Invalid input. Please enter 'y' or 'n'.");
return Ok(None); }
},
None => {
match default {
Some(value) => Some(value),
None => None,
}
}
};
Ok(result)
}