use std::{ io::{BufRead, BufReader, Read, Write}, os::fd::AsRawFd, sync::{Arc, Mutex}, }; 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: Arc>>, write: Arc>, } 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: Arc::new(Mutex::new(BufReader::new(read_handle))), write: Arc::new(Mutex::new(write_handle)), }) } fn alt_screen(&self) -> Result> { AlternateScreen::new(self.write.clone()) } pub fn prompt_input(&mut self, prompt: &str) -> Result { let mut alt_screen = self.alt_screen()?; alt_screen .execute(terminal::Clear(terminal::ClearType::All))? .execute(Print(prompt))?; let mut line = String::new(); self.read.lock().unwrap().read_line(&mut line)?; Ok(line) } // TODO: return secrecy::Secret // TODO: write a guard drop system for raw mode pub fn prompt_passphrase(&mut self, prompt: &str) -> Result { let write = AlternateScreen::new(self.write.clone())?; let mut write = RawMode::new(write.arc_mutex())?; write .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) => { write.execute(Print("*"))?; passphrase.push(c); } _ => (), }, _ => (), } } Ok(passphrase) } }