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,
description: "example confirmation".into(),
},
]);
]).expect("confirmation menu demo failed");
println!("accept: {}, deny: {}", accept.len(), deny.len());
}

View file

@ -517,7 +517,7 @@ fn do_subcmd_trade(
}
} else {
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 {
let result = account.accept_confirmation(conf);
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 regex::Regex;
use std::collections::HashSet;
use std::io::{Read, Write};
use std::io::{stdin, stdout, Read, Write};
use steamguard::Confirmation;
use termion::{
event::{Event, Key},
input::TermRead,
raw::IntoRawMode,
screen::AlternateScreen,
};
use termion::{input::TermRead, raw::IntoRawMode};
lazy_static! {
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.
pub(crate) fn prompt_confirmation_menu(
confirmations: Vec<Confirmation>,
) -> (Vec<Confirmation>, Vec<Confirmation>) {
println!("press a key other than enter to show the menu.");
) -> anyhow::Result<(Vec<Confirmation>, Vec<Confirmation>)> {
let mut to_accept_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());
let stdin = std::io::stdin();
execute!(stdout(), EnterAlternateScreen)?;
crossterm::terminal::enable_raw_mode()?;
let mut selected_idx = 0;
for c in stdin.events() {
match c.expect("could not get events") {
Event::Key(Key::Char('a')) => {
loop {
execute!(
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_deny_idx.remove(&selected_idx);
}
Event::Key(Key::Char('d')) => {
Event::Key(KeyEvent {
code: KeyCode::Char('d'),
..
}) => {
to_accept_idx.remove(&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_deny_idx.remove(&selected_idx);
}
Event::Key(Key::Char('A')) => {
Event::Key(KeyEvent {
code: KeyCode::Char('A'),
..
}) => {
(0..confirmations.len()).for_each(|i| {
to_accept_idx.insert(i);
to_deny_idx.remove(&i);
});
}
Event::Key(Key::Char('D')) => {
Event::Key(KeyEvent {
code: KeyCode::Char('D'),
..
}) => {
(0..confirmations.len()).for_each(|i| {
to_accept_idx.remove(&i);
to_deny_idx.insert(i);
});
}
Event::Key(Key::Char('I')) => {
Event::Key(KeyEvent {
code: KeyCode::Char('I'),
..
}) => {
(0..confirmations.len()).for_each(|i| {
to_accept_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;
}
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;
}
Event::Key(Key::Char('\n')) => {
Event::Key(KeyEvent {
code: KeyCode::Enter,
..
}) => {
break;
}
Event::Key(Key::Esc) | Event::Key(Key::Ctrl('c')) => {
return (vec![], vec![]);
Event::Key(KeyEvent {
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) {
write!(
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();
}
execute!(stdout(), LeaveAlternateScreen)?;
crossterm::terminal::disable_raw_mode()?;
if selected_idx == i {
write!(
screen,
"{}",
termion::color::Fg(termion::color::LightYellow)
)
.unwrap();
}
write!(screen, " {}\n", confirmations[i].description()).unwrap();
}
}
return (
return Ok((
to_accept_idx
.iter()
.map(|i| confirmations[*i].clone())
@ -222,7 +249,7 @@ pub(crate) fn prompt_confirmation_menu(
.iter()
.map(|i| confirmations[*i].clone())
.collect(),
);
));
}
pub(crate) fn pause() {