convert prompt_confirmation_menu to crossterm

This commit is contained in:
Carson McManus 2022-06-25 10:14:34 -04:00
parent 230f85b6ac
commit ebe31b6df7
3 changed files with 105 additions and 78 deletions

View file

@ -33,6 +33,6 @@ pub fn demo_confirmation_menu() {
creator: 09870987, creator: 09870987,
description: "example confirmation".into(), description: "example confirmation".into(),
}, },
]); ]).expect("confirmation menu demo failed");
println!("accept: {}, deny: {}", accept.len(), deny.len()); println!("accept: {}, deny: {}", accept.len(), deny.len());
} }

View file

@ -517,7 +517,7 @@ fn do_subcmd_trade(
} }
} else { } else {
if termion::is_tty(&stdout()) { if termion::is_tty(&stdout()) {
let (accept, deny) = tui::prompt_confirmation_menu(confirmations); let (accept, deny) = tui::prompt_confirmation_menu(confirmations)?;
for conf in &accept { for conf in &accept {
let result = account.accept_confirmation(conf); let result = account.accept_confirmation(conf);
if result.is_err() { if result.is_err() {

View file

@ -1,14 +1,17 @@
use crossterm::{
cursor,
event::{Event, KeyCode, KeyEvent, KeyModifiers},
execute,
style::{Print, PrintStyledContent, Stylize, Color, SetForegroundColor},
terminal::{Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen},
QueueableCommand,
};
use log::*; use log::*;
use regex::Regex; use regex::Regex;
use std::collections::HashSet; use std::collections::HashSet;
use std::io::{Read, Write}; use std::io::{stdin, stdout, Read, Write};
use steamguard::Confirmation; use steamguard::Confirmation;
use termion::{ use termion::{input::TermRead, raw::IntoRawMode};
event::{Event, Key},
input::TermRead,
raw::IntoRawMode,
screen::AlternateScreen,
};
lazy_static! { lazy_static! {
static ref CAPTCHA_VALID_CHARS: Regex = static ref CAPTCHA_VALID_CHARS: Regex =
@ -105,115 +108,139 @@ fn prompt_char_impl(input: &mut impl Read, text: &str, chars: &str) -> char {
/// Returns a tuple of (accepted, denied). Ignored confirmations are not included. /// Returns a tuple of (accepted, denied). Ignored confirmations are not included.
pub(crate) fn prompt_confirmation_menu( pub(crate) fn prompt_confirmation_menu(
confirmations: Vec<Confirmation>, confirmations: Vec<Confirmation>,
) -> (Vec<Confirmation>, Vec<Confirmation>) { ) -> anyhow::Result<(Vec<Confirmation>, Vec<Confirmation>)> {
println!("press a key other than enter to show the menu.");
let mut to_accept_idx: HashSet<usize> = HashSet::new(); let mut to_accept_idx: HashSet<usize> = HashSet::new();
let mut to_deny_idx: HashSet<usize> = HashSet::new(); let mut to_deny_idx: HashSet<usize> = HashSet::new();
let mut screen = AlternateScreen::from(std::io::stdout().into_raw_mode().unwrap()); execute!(stdout(), EnterAlternateScreen)?;
let stdin = std::io::stdin(); crossterm::terminal::enable_raw_mode()?;
let mut selected_idx = 0; let mut selected_idx = 0;
for c in stdin.events() { loop {
match c.expect("could not get events") { execute!(
Event::Key(Key::Char('a')) => { stdout(),
Clear(ClearType::All),
cursor::MoveTo(1, 1),
PrintStyledContent(
"arrow keys to select, [a]ccept, [d]eny, [i]gnore, [enter] confirm choices\n\n"
.white()
),
)?;
for i in 0..confirmations.len() {
stdout().queue(Print("\r"))?;
if selected_idx == i {
stdout().queue(SetForegroundColor(Color::Yellow))?;
stdout().queue(Print(" >"))?;
} else {
stdout().queue(SetForegroundColor(Color::White))?;
stdout().queue(Print(" "))?;
}
if to_accept_idx.contains(&i) {
stdout().queue(SetForegroundColor(Color::Green))?;
stdout().queue(Print("[a]"))?;
} else if to_deny_idx.contains(&i) {
stdout().queue(SetForegroundColor(Color::Red))?;
stdout().queue(Print("[d]"))?;
} else {
stdout().queue(Print("[ ]"))?;
}
if selected_idx == i {
stdout().queue(SetForegroundColor(Color::Yellow))?;
}
stdout().queue(Print(format!(" {}\n", confirmations[i].description())))?;
}
stdout().flush()?;
match crossterm::event::read()? {
Event::Resize(_, _) => continue,
Event::Key(KeyEvent {
code: KeyCode::Char('a'),
..
}) => {
to_accept_idx.insert(selected_idx); to_accept_idx.insert(selected_idx);
to_deny_idx.remove(&selected_idx); to_deny_idx.remove(&selected_idx);
} }
Event::Key(Key::Char('d')) => { Event::Key(KeyEvent {
code: KeyCode::Char('d'),
..
}) => {
to_accept_idx.remove(&selected_idx); to_accept_idx.remove(&selected_idx);
to_deny_idx.insert(selected_idx); to_deny_idx.insert(selected_idx);
} }
Event::Key(Key::Char('i')) => { Event::Key(KeyEvent {
code: KeyCode::Char('i'),
..
}) => {
to_accept_idx.remove(&selected_idx); to_accept_idx.remove(&selected_idx);
to_deny_idx.remove(&selected_idx); to_deny_idx.remove(&selected_idx);
} }
Event::Key(Key::Char('A')) => { Event::Key(KeyEvent {
code: KeyCode::Char('A'),
..
}) => {
(0..confirmations.len()).for_each(|i| { (0..confirmations.len()).for_each(|i| {
to_accept_idx.insert(i); to_accept_idx.insert(i);
to_deny_idx.remove(&i); to_deny_idx.remove(&i);
}); });
} }
Event::Key(Key::Char('D')) => { Event::Key(KeyEvent {
code: KeyCode::Char('D'),
..
}) => {
(0..confirmations.len()).for_each(|i| { (0..confirmations.len()).for_each(|i| {
to_accept_idx.remove(&i); to_accept_idx.remove(&i);
to_deny_idx.insert(i); to_deny_idx.insert(i);
}); });
} }
Event::Key(Key::Char('I')) => { Event::Key(KeyEvent {
code: KeyCode::Char('I'),
..
}) => {
(0..confirmations.len()).for_each(|i| { (0..confirmations.len()).for_each(|i| {
to_accept_idx.remove(&i); to_accept_idx.remove(&i);
to_deny_idx.remove(&i); to_deny_idx.remove(&i);
}); });
} }
Event::Key(Key::Up) if selected_idx > 0 => { Event::Key(KeyEvent {
code: KeyCode::Up, ..
}) if selected_idx > 0 => {
selected_idx -= 1; selected_idx -= 1;
} }
Event::Key(Key::Down) if selected_idx < confirmations.len() - 1 => { Event::Key(KeyEvent {
code: KeyCode::Down,
..
}) if selected_idx < confirmations.len() - 1 => {
selected_idx += 1; selected_idx += 1;
} }
Event::Key(Key::Char('\n')) => { Event::Key(KeyEvent {
code: KeyCode::Enter,
..
}) => {
break; break;
} }
Event::Key(Key::Esc) | Event::Key(Key::Ctrl('c')) => { Event::Key(KeyEvent {
return (vec![], vec![]); code: KeyCode::Esc, ..
})
| Event::Key(KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
}) => {
return Ok((vec![], vec![]));
} }
_ => {} _ => {}
} }
write!(
screen,
"{}{}{}arrow keys to select, [a]ccept, [d]eny, [i]gnore, [enter] confirm choices\n\n",
termion::clear::All,
termion::cursor::Goto(1, 1),
termion::color::Fg(termion::color::White)
)
.unwrap();
for i in 0..confirmations.len() {
if selected_idx == i {
write!(
screen,
"\r{} >",
termion::color::Fg(termion::color::LightYellow)
)
.unwrap();
} else {
write!(screen, "\r{} ", termion::color::Fg(termion::color::White)).unwrap();
} }
if to_accept_idx.contains(&i) { execute!(stdout(), LeaveAlternateScreen)?;
write!( crossterm::terminal::disable_raw_mode()?;
screen,
"{}[a]",
termion::color::Fg(termion::color::LightGreen)
)
.unwrap();
} else if to_deny_idx.contains(&i) {
write!(
screen,
"{}[d]",
termion::color::Fg(termion::color::LightRed)
)
.unwrap();
} else {
write!(screen, "[ ]").unwrap();
}
if selected_idx == i { return Ok((
write!(
screen,
"{}",
termion::color::Fg(termion::color::LightYellow)
)
.unwrap();
}
write!(screen, " {}\n", confirmations[i].description()).unwrap();
}
}
return (
to_accept_idx to_accept_idx
.iter() .iter()
.map(|i| confirmations[*i].clone()) .map(|i| confirmations[*i].clone())
@ -222,7 +249,7 @@ pub(crate) fn prompt_confirmation_menu(
.iter() .iter()
.map(|i| confirmations[*i].clone()) .map(|i| confirmations[*i].clone())
.collect(), .collect(),
); ));
} }
pub(crate) fn pause() { pub(crate) fn pause() {