repline/
prebaked.rs

1//! Here's a menu I prepared earlier!
2//!
3//! Constructs a [Repline] and repeatedly runs the provided closure on the input strings,
4//! obeying the closure's [Response].
5
6use std::error::Error;
7
8use crate::{error::Error as RlError, repline::Repline};
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
11/// Control codes for the [prebaked menu](read_and)
12pub enum Response {
13    /// Accept the line, and save it to history
14    Accept,
15    /// Reject the line, and clear the buffer
16    Deny,
17    /// End the loop
18    Break,
19    /// Gather more input and try again
20    Continue,
21}
22
23/// Implements a basic menu loop using an embedded [Repline].
24///
25/// Repeatedly runs the provided closure on the input strings,
26/// obeying the closure's [Response].
27///
28/// Captures and displays all user [Error]s.
29///
30/// # Keybinds
31/// - `Ctrl+C` exits the loop
32/// - `Ctrl+D` clears the input, but *runs the closure* with the old input
33pub fn read_and<F>(color: &str, begin: &str, again: &str, mut f: F) -> Result<(), RlError>
34where F: FnMut(&str) -> Result<Response, Box<dyn Error>> {
35    let mut rl = Repline::new(color, begin, again);
36    loop {
37        let line = match rl.read() {
38            Err(RlError::CtrlC(_)) => break,
39            Err(RlError::CtrlD(line)) => {
40                rl.deny();
41                line
42            }
43            Ok(line) => line,
44            Err(e) => Err(e)?,
45        };
46        print!("\x1b[G\x1b[J");
47        match f(&line) {
48            Ok(Response::Accept) => rl.accept(),
49            Ok(Response::Deny) => rl.deny(),
50            Ok(Response::Break) => break,
51            Ok(Response::Continue) => continue,
52            Err(e) => rl.print_inline(format_args!("\x1b[40G\x1b[91m{e}\x1b[0m"))?,
53        }
54    }
55    Ok(())
56}