use std::{ io::{BufRead, BufReader, Read, Write}, os::fd::AsRawFd, }; use crossterm::{ event::{read, Event, KeyCode}, style::Print, terminal, tty::IsTty, ExecutableCommand, }; mod alternate_screen; mod raw_mode; use alternate_screen::*; use raw_mode::*; #[derive(thiserror::Error, Debug)] pub enum Error { #[error("The given handler is not a TTY")] NotATTY, #[error("IO Error: {0}")] IO(#[from] std::io::Error), } pub type Result = std::result::Result; pub struct PromptManager { read: BufReader, write: W, } impl PromptManager where R: Read + Sized, W: Write + AsRawFd + Sized, { pub fn new(read_handle: R, write_handle: W) -> Result { if !write_handle.is_tty() { return Err(Error::NotATTY); } Ok(Self { read: BufReader::new(read_handle), write: write_handle, }) } pub fn prompt_input(&mut self, prompt: &str) -> Result { let mut terminal = AlternateScreen::new(&mut self.write)?; terminal .execute(terminal::Clear(terminal::ClearType::All))? .execute(Print(prompt))?; let mut line = String::new(); self.read.read_line(&mut line)?; Ok(line) } // TODO: return secrecy::Secret pub fn prompt_passphrase(&mut self, prompt: &str) -> Result { let mut terminal = AlternateScreen::new(&mut self.write)?; let mut terminal = RawMode::new(&mut terminal)?; terminal .execute(terminal::Clear(terminal::ClearType::All))? .execute(Print(prompt))?; let mut passphrase = String::new(); loop { match read()? { Event::Key(k) => match k.code { KeyCode::Enter => { passphrase.push('\n'); break; } KeyCode::Char(c) => { terminal.execute(Print("*"))?; passphrase.push(c); } _ => (), }, _ => (), } } Ok(passphrase) } pub fn prompt_message(&mut self, prompt: &str) -> Result<()> { let mut terminal = AlternateScreen::new(&mut self.write)?; let mut terminal = RawMode::new(&mut terminal)?; terminal .execute(terminal::Clear(terminal::ClearType::All))? .execute(Print(prompt))?; loop { match read()? { Event::Key(k) => match k.code { KeyCode::Enter | KeyCode::Char(' ') => break, _ => (), }, _ => (), } } Ok(()) } }