1#![allow(unused_variables)]
2use anyhow::Result;
3#[cfg(feature = "tui")]
4use crossterm::event::{poll, read, Event, KeyCode};
5use std::error::Error;
6use std::time::Duration;
7
8#[allow(dead_code)]
10struct RawModeGuard;
11
12impl RawModeGuard {
13 #[allow(dead_code)]
14 fn new() -> Result<Self> {
15 #[cfg(feature = "tui")]
16 crossterm::terminal::enable_raw_mode()?;
17 Ok(Self)
18 }
19}
20
21impl Drop for RawModeGuard {
22 fn drop(&mut self) {
23 #[cfg(feature = "tui")]
24 let _ = crossterm::terminal::disable_raw_mode();
25 }
26}
27
28pub fn prompt(message: &str, wait_secs: u64) -> Result<Option<char>> {
32 if !message.trim().is_empty() {
33 println!("{}", message);
34 }
35 use std::io::IsTerminal;
36 if !io::stdin().is_terminal() || !io::stdout().is_terminal() {
37 return Ok(None);
39 }
40
41 #[cfg(feature = "tui")]
43 {
44 let timeout = Duration::from_secs(wait_secs);
45 drain_events().ok(); let _raw_guard = RawModeGuard::new()?;
48 let result = if poll(timeout)? {
49 if let Event::Key(key_event) = read()? {
50 if let KeyCode::Char(c) = key_event.code {
51 if c == '\x03' {
53 return Err(anyhow::anyhow!("Ctrl+C pressed").into()); }
56 Some(c.to_ascii_lowercase())
57 } else {
58 None
59 }
60 } else {
61 None
62 }
63 } else {
64 None
65 };
66 Ok(result)
67 }
68
69 #[cfg(not(feature = "tui"))]
71 {
72 use std::io::{self, BufRead};
73 use std::sync::mpsc;
74 use std::thread;
75
76 let (tx, rx) = mpsc::channel();
77 thread::spawn(move || {
78 let stdin = io::stdin();
79 let mut line = String::new();
80 let _ = stdin.lock().read_line(&mut line);
82 let _ = tx.send(line);
83 });
84
85 match rx.recv_timeout(Duration::from_secs(wait_secs)) {
86 Ok(line) => Ok(line.trim().chars().next().map(|c| c.to_ascii_lowercase())),
87 Err(_) => Ok(None),
88 }
89 }
90}
91
92pub fn prompt_line(message: &str, wait_secs: u64) -> Result<Option<String>, Box<dyn Error>> {
95 if !message.trim().is_empty() {
96 println!("{}", message);
97 }
98 use std::io::IsTerminal;
99 if !std::io::stdin().is_terminal() {
100 return Ok(None);
102 }
103
104 #[cfg(not(feature = "tui"))]
105 {
106 use std::io::{self, BufRead};
107 use std::sync::mpsc;
108 use std::thread;
109
110 let (tx, rx) = mpsc::channel();
111 thread::spawn(move || {
112 let stdin = io::stdin();
113 let mut line = String::new();
114 let _ = stdin.lock().read_line(&mut line);
116 let _ = tx.send(line);
117 });
118
119 match rx.recv_timeout(Duration::from_secs(wait_secs)) {
120 Ok(line) => Ok(Some(line.trim().to_string())),
121 Err(_) => Ok(None),
122 }
123 }
124
125 #[cfg(feature = "tui")]
126 {
127 use crossterm::event::{poll, read, Event, KeyCode};
129 use std::io::{self, Write};
130 #[cfg(feature = "tui")]
132 let _raw_guard = RawModeGuard::new()?;
133 let mut input = String::new();
134 let start = std::time::Instant::now();
135 loop {
136 let elapsed = start.elapsed().as_secs();
137 if elapsed >= wait_secs {
138 println!("Timeout reached; no input received.");
139 return Ok(None);
140 }
141 let remaining = Duration::from_secs(wait_secs - elapsed);
142 if poll(remaining)? {
143 if let Event::Key(key_event) = read()? {
144 if key_event.kind != crossterm::event::KeyEventKind::Press {
146 continue;
147 }
148 match key_event.code {
149 KeyCode::Enter => break,
150 KeyCode::Char(c) => {
151 input.push(c);
152 print!("{}", c);
153 use std::io::{self, Write};
154 io::stdout().flush()?;
155 }
156 KeyCode::Backspace => {
157 input.pop();
158 print!("\r{} \r", input);
159 io::stdout().flush()?;
160 }
161 _ => {}
162 }
163 }
164 }
165 }
166 println!();
167 Ok(Some(input.trim().to_string()))
168 }
169}
170
171use std::io::{self, BufRead};
172use std::sync::mpsc;
173use std::thread;
174
175pub fn read_line_with_timeout(wait_secs: u64) -> io::Result<Option<String>> {
179 let timeout = Duration::from_secs(wait_secs);
180 let (tx, rx) = mpsc::channel();
181 thread::spawn(move || {
182 let stdin = io::stdin();
183 let mut line = String::new();
184 let _ = stdin.lock().read_line(&mut line);
185 let _ = tx.send(line);
186 });
187 match rx.recv_timeout(timeout) {
188 Ok(line) => Ok(Some(line.trim().to_string())),
189 Err(mpsc::RecvTimeoutError::Timeout) => Ok(None),
190 Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
191 }
192}
193
194pub fn prompt_line_with_poll(wait_secs: u64) -> Result<Option<String>, Box<dyn std::error::Error>> {
197 #[cfg(feature = "tui")]
198 {
199 #[cfg(feature = "tui")]
201 let _raw_guard = RawModeGuard::new()?;
202 let timeout = Duration::from_secs(wait_secs);
203 let start = std::time::Instant::now();
204 let mut input = String::new();
205 loop {
206 let elapsed = start.elapsed();
207 if elapsed >= timeout {
208 return Ok(None);
209 }
210 let remaining = timeout - elapsed;
212 if poll(remaining)? {
213 if let Event::Key(crossterm::event::KeyEvent { code, kind, .. }) = read()? {
214 if kind != crossterm::event::KeyEventKind::Press {
216 continue;
217 }
218 match code {
219 KeyCode::Enter => break,
220 KeyCode::Char(c) => {
221 input.push(c);
222 print!("{}", c);
224 io::Write::flush(&mut io::stdout())?;
225 }
226 KeyCode::Backspace => {
227 input.pop();
228 print!("\r{}\r", " ".repeat(input.len() + 1));
230 print!("{}", input);
231 io::Write::flush(&mut io::stdout())?;
232 }
233 _ => {} }
235 }
236 }
237 }
238 Ok(Some(input))
239 }
240 #[cfg(not(feature = "tui"))]
241 {
242 return Ok(read_line_with_timeout(wait_secs).unwrap_or_default());
243 }
244}
245
246pub fn prompt_line_with_poll_opts(
254 wait_secs: u64,
255 quick_exit: &[char],
256 allowed_chars: Option<&[char]>,
257) -> Result<Option<String>, Box<dyn Error + Send + Sync>> {
258 #[cfg(feature = "tui")]
259 {
260 let timeout = Duration::from_secs(wait_secs);
261 let _raw_guard = RawModeGuard::new()?;
262 let start = std::time::Instant::now();
263 let mut input = String::new();
264
265 loop {
266 let elapsed = start.elapsed();
267 if elapsed >= timeout {
268 return Ok(None);
269 }
270 let remaining = timeout - elapsed;
271 if poll(remaining)? {
272 if let Event::Key(crossterm::event::KeyEvent { code, kind, .. }) = read()? {
273 if kind != crossterm::event::KeyEventKind::Press {
275 continue;
276 }
277 match code {
278 KeyCode::Enter => {
279 drain_events().ok();
280 break;
281 } KeyCode::Char(c) => {
283 if quick_exit.iter().any(|&qe| qe.eq_ignore_ascii_case(&c)) {
285 input.push(c);
286 drain_events().ok(); return Ok(Some(input.to_string()));
288 }
289 if let Some(allowed) = allowed_chars {
291 if !allowed.iter().any(|&a| a.eq_ignore_ascii_case(&c)) {
292 continue;
294 }
295 }
296 input.push(c);
297 use crossterm::{
298 cursor::MoveToColumn,
299 execute,
300 terminal::{Clear, ClearType},
301 };
302 execute!(io::stdout(), Clear(ClearType::CurrentLine), MoveToColumn(0))?;
303 print!("{}", input);
304 io::Write::flush(&mut io::stdout())?;
305 }
306 KeyCode::Backspace => {
307 if !input.is_empty() {
308 input.pop();
309 print!("\x08 \x08");
311 io::Write::flush(&mut io::stdout())?;
312 }
313 }
314 _ => {} }
316 }
317 }
318 }
319 Ok(Some(input.trim().to_string()))
321 }
322
323 #[cfg(not(feature = "tui"))]
324 {
325 return Ok(read_line_with_timeout(wait_secs).unwrap_or_default());
326 }
327}
328
329#[allow(dead_code)]
331fn drain_events() -> Result<()> {
332 #[cfg(feature = "tui")]
334 while poll(Duration::from_millis(0))? {
335 let _ = read()?;
336 }
337 Ok(())
338}
339
340pub fn yesno(prompt_message: &str, default: Option<bool>) -> Result<Option<bool>> {
343 let prompt_with_default = match default {
344 Some(true) => format!("{} (Y/n)? ", prompt_message),
345 Some(false) => format!("{} (y/N)? ", prompt_message),
346 None => format!("{} (y/n)? ", prompt_message),
347 };
348
349 let result = match prompt(&prompt_with_default, 10)? {
350 Some(c) => match c {
351 'y' => Some(true),
352 'n' => Some(false),
353 _ => {
354 println!("Invalid input. Please enter 'y' or 'n'.");
356 return Ok(None); }
358 },
359 None => {
360 match default {
362 Some(value) => Some(value),
363 None => None,
364 }
365 }
366 };
367 Ok(result)
368}