keyfork-prompt: incorporate AlternateScreen and RawMode into a generic guard type

This commit is contained in:
Ryan Heywood 2024-01-10 23:16:58 -05:00
parent f6b41fce5f
commit aba62fc4bf
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
3 changed files with 98 additions and 182 deletions

View File

@ -1,80 +0,0 @@
use std::{
io::Write,
os::fd::AsRawFd,
};
use keyfork_crossterm::{
cursor::MoveTo,
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
ExecutableCommand,
};
use crate::Result;
pub(crate) struct AlternateScreen<'a, W>
where
W: Write + AsRawFd + Sized,
{
write: &'a mut W,
}
impl<'a, W> AlternateScreen<'a, W>
where
W: Write + AsRawFd + Sized,
{
pub(crate) fn new(write_handle: &'a mut W) -> Result<Self> {
write_handle.execute(EnterAlternateScreen)?.execute(MoveTo(0, 0))?;
Ok(Self {
write: write_handle,
})
}
}
impl<W> Write for AlternateScreen<'_, W>
where
W: Write + AsRawFd + Sized,
{
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.write.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.write.flush()
}
}
impl<W> AsRawFd for AlternateScreen<'_, W>
where
W: Write + AsRawFd + Sized,
{
fn as_raw_fd(&self) -> std::os::fd::RawFd {
self.write.as_raw_fd()
}
}
/*
impl<W> ExecutableCommand for AlternateScreen<W>
where
W: Write + AsRawFd + Sized,
{
fn execute(&mut self, command: impl crossterm::Command) -> std::io::Result<&mut Self> {
let mut write = self.write.lock().unwrap();
match write.execute(command) {
Ok(_) => {
drop(write);
Ok(self)
}
Err(e) => Err(e),
}
}
}
*/
impl<W> Drop for AlternateScreen<'_, W>
where
W: Write + AsRawFd + Sized,
{
fn drop(&mut self) {
self.write.execute(LeaveAlternateScreen).unwrap();
}
}

View File

@ -10,16 +10,11 @@ use keyfork_crossterm::{
cursor,
event::{read, DisableBracketedPaste, EnableBracketedPaste, Event, KeyCode, KeyModifiers},
style::{Print, PrintStyledContent, Stylize},
terminal::{self, TerminalIoctl, FdTerminal},
terminal::{self, TerminalIoctl, FdTerminal, EnterAlternateScreen, LeaveAlternateScreen},
tty::IsTty,
QueueableCommand,
QueueableCommand, ExecutableCommand
};
mod alternate_screen;
mod raw_mode;
use alternate_screen::AlternateScreen;
use raw_mode::RawMode;
pub mod validators;
#[cfg(feature = "qrencode")]
@ -44,6 +39,89 @@ pub enum Message {
Data(String),
}
struct TerminalGuard<'a, R, W> where W: Write + AsRawFd {
read: &'a mut BufReader<R>,
write: &'a mut W,
terminal: &'a mut FdTerminal,
}
impl<'a, R, W> TerminalGuard<'a, R, W> where W: Write + AsRawFd, R: Read {
fn new(read: &'a mut BufReader<R>, write: &'a mut W, terminal: &'a mut FdTerminal) -> Self {
Self {
read,
write,
terminal
}
}
fn alternate_screen(mut self) -> std::io::Result<Self> {
self.execute(EnterAlternateScreen)?;
Ok(self)
}
fn raw_mode(self) -> std::io::Result<Self> {
self.terminal.enable_raw_mode()?;
Ok(self)
}
fn bracketed_paste(mut self) -> std::io::Result<Self> {
self.execute(EnableBracketedPaste)?;
Ok(self)
}
}
impl<R, W> TerminalIoctl for TerminalGuard<'_, R, W> where R: Read, W: Write + AsRawFd {
fn enable_raw_mode(&mut self) -> std::io::Result<()> {
self.terminal.enable_raw_mode()
}
fn disable_raw_mode(&mut self) -> std::io::Result<()> {
self.terminal.disable_raw_mode()
}
fn size(&self) -> std::io::Result<(u16, u16)> {
self.terminal.size()
}
fn window_size(&self) -> std::io::Result<terminal::WindowSize> {
self.terminal.window_size()
}
}
impl<R, W> Read for TerminalGuard<'_, R, W> where R: Read, W: Write + AsRawFd {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.read.read(buf)
}
}
impl<R, W> BufRead for TerminalGuard<'_, R, W> where R: Read, W: Write + AsRawFd {
fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
self.read.fill_buf()
}
fn consume(&mut self, amt: usize) {
self.read.consume(amt)
}
}
impl<R, W> Write for TerminalGuard<'_, R, W> where W: Write + AsRawFd {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.write.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.write.flush()
}
}
impl<R, W> Drop for TerminalGuard<'_, R, W> where W: Write + AsRawFd {
fn drop(&mut self) {
self.write.execute(DisableBracketedPaste).unwrap();
self.write.execute(LeaveAlternateScreen).unwrap();
self.terminal.disable_raw_mode().unwrap();
}
}
pub struct Terminal<R, W> {
read: BufReader<R>,
write: W,
@ -66,8 +144,12 @@ where
})
}
fn lock(&mut self) -> TerminalGuard<'_, R, W> {
TerminalGuard::new(&mut self.read, &mut self.write, &mut self.terminal)
}
pub fn prompt_input(&mut self, prompt: &str) -> Result<String> {
let mut terminal = AlternateScreen::new(&mut self.write)?;
let mut terminal = self.lock().alternate_screen()?;
terminal
.queue(terminal::Clear(terminal::ClearType::All))?
.queue(cursor::MoveTo(0, 0))?;
@ -83,7 +165,7 @@ where
terminal.flush()?;
let mut line = String::new();
self.read.read_line(&mut line)?;
terminal.read.read_line(&mut line)?;
Ok(line)
}
@ -118,17 +200,14 @@ where
))
}
// TODO: create a wrapper for bracketed paste similar to RawMode
#[cfg(feature = "mnemonic")]
#[allow(clippy::too_many_lines)]
pub fn prompt_wordlist(&mut self, prompt: &str, wordlist: &Wordlist) -> Result<String> {
let mut terminal = AlternateScreen::new(&mut self.write)?;
let mut terminal = RawMode::new(&mut terminal)?;
let mut terminal = self.lock().alternate_screen()?.raw_mode()?.bracketed_paste()?;
terminal
.queue(terminal::Clear(terminal::ClearType::All))?
.queue(cursor::MoveTo(0, 0))?
.queue(EnableBracketedPaste)?;
.queue(cursor::MoveTo(0, 0))?;
let mut lines = prompt.lines().peekable();
let mut prefix_length = 0;
while let Some(line) = lines.next() {
@ -142,7 +221,7 @@ where
}
terminal.flush()?;
let (mut cols, mut _rows) = self.terminal.size()?;
let (mut cols, mut _rows) = terminal.size()?;
let mut input = String::new();
loop {
@ -236,8 +315,6 @@ where
terminal.flush()?;
}
terminal.queue(DisableBracketedPaste)?.flush()?;
Ok(input)
}
@ -273,8 +350,7 @@ where
// TODO: return secrecy::Secret<String>
pub fn prompt_passphrase(&mut self, prompt: &str) -> Result<String> {
let mut terminal = AlternateScreen::new(&mut self.write)?;
let mut terminal = RawMode::new(&mut terminal)?;
let mut terminal = self.lock().alternate_screen()?.raw_mode()?;
terminal
.queue(terminal::Clear(terminal::ClearType::All))?
@ -292,7 +368,7 @@ where
}
terminal.flush()?;
let (mut cols, mut _rows) = self.terminal.size()?;
let (mut cols, mut _rows) = terminal.size()?;
let mut passphrase = String::new();
loop {
@ -332,11 +408,10 @@ where
}
pub fn prompt_message(&mut self, prompt: &Message) -> Result<()> {
let mut terminal = AlternateScreen::new(&mut self.write)?;
let mut terminal = RawMode::new(&mut terminal)?;
let mut terminal = self.lock().alternate_screen()?.raw_mode()?;
loop {
let (cols, rows) = self.terminal.size()?;
let (cols, rows) = terminal.size()?;
terminal
.queue(terminal::Clear(terminal::ClearType::All))?

View File

@ -1,79 +0,0 @@
use std::{
io::Write,
os::fd::AsRawFd,
};
use keyfork_crossterm::terminal::{FdTerminal, TerminalIoctl};
use crate::Result;
pub(crate) struct RawMode<'a, W>
where
W: Write + AsRawFd + Sized,
{
write: &'a mut W,
terminal: FdTerminal,
}
impl<'a, W> RawMode<'a, W>
where
W: Write + AsRawFd + Sized,
{
pub(crate) fn new(write_handle: &'a mut W) -> Result<Self> {
let mut terminal = FdTerminal::from(write_handle.as_raw_fd());
terminal.enable_raw_mode()?;
Ok(Self {
terminal,
write: write_handle,
})
}
}
impl<W> Write for RawMode<'_, W>
where
W: Write + AsRawFd + Sized,
{
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.write.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.write.flush()
}
}
impl<W> AsRawFd for RawMode<'_, W>
where
W: Write + AsRawFd + Sized,
{
fn as_raw_fd(&self) -> std::os::fd::RawFd {
self.write.as_raw_fd()
}
}
/*
impl<W> ExecutableCommand for RawMode<W>
where
W: Write + AsRawFd + Sized,
{
fn execute(&mut self, command: impl crossterm::Command) -> std::io::Result<&mut Self> {
let mut write = self.write.lock().unwrap();
match write.execute(command) {
Ok(_) => {
drop(write);
Ok(self)
}
Err(e) => Err(e),
}
}
}
*/
impl<W> Drop for RawMode<'_, W>
where
W: Write + AsRawFd + Sized,
{
fn drop(&mut self) {
self.terminal.disable_raw_mode().unwrap();
}
}