use std::io;
use console::{Key, Term};
use crate::{
theme::{render::TermThemeRenderer, SimpleTheme, Theme},
Result,
};
#[derive(Clone)]
pub struct Confirm<'a> {
prompt: String,
report: bool,
default: Option<bool>,
show_default: bool,
wait_for_newline: bool,
theme: &'a dyn Theme,
}
impl Default for Confirm<'static> {
fn default() -> Self {
Self::new()
}
}
impl Confirm<'static> {
pub fn new() -> Self {
Self::with_theme(&SimpleTheme)
}
}
impl Confirm<'_> {
pub fn with_prompt<S: Into<String>>(mut self, prompt: S) -> Self {
self.prompt = prompt.into();
self
}
pub fn report(mut self, val: bool) -> Self {
self.report = val;
self
}
pub fn wait_for_newline(mut self, wait: bool) -> Self {
self.wait_for_newline = wait;
self
}
pub fn default(mut self, val: bool) -> Self {
self.default = Some(val);
self
}
pub fn show_default(mut self, val: bool) -> Self {
self.show_default = val;
self
}
#[inline]
pub fn interact(self) -> Result<bool> {
self.interact_on(&Term::stderr())
}
#[inline]
pub fn interact_opt(self) -> Result<Option<bool>> {
self.interact_on_opt(&Term::stderr())
}
#[inline]
pub fn interact_on(self, term: &Term) -> Result<bool> {
Ok(self
._interact_on(term, false)?
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Quit not allowed in this case"))?)
}
#[inline]
pub fn interact_on_opt(self, term: &Term) -> Result<Option<bool>> {
self._interact_on(term, true)
}
fn _interact_on(self, term: &Term, allow_quit: bool) -> Result<Option<bool>> {
if !term.is_term() {
return Err(io::Error::new(io::ErrorKind::NotConnected, "not a terminal").into());
}
let mut render = TermThemeRenderer::new(term, self.theme);
let default_if_show = if self.show_default {
self.default
} else {
None
};
render.confirm_prompt(&self.prompt, default_if_show)?;
term.hide_cursor()?;
term.flush()?;
let rv;
if self.wait_for_newline {
let mut value = default_if_show;
loop {
let input = term.read_key()?;
match input {
Key::Char('y') | Key::Char('Y') => {
value = Some(true);
}
Key::Char('n') | Key::Char('N') => {
value = Some(false);
}
Key::Enter => {
if !allow_quit {
value = value.or(self.default);
}
if value.is_some() || allow_quit {
rv = value;
break;
}
continue;
}
Key::Escape | Key::Char('q') if allow_quit => {
value = None;
}
_ => {
continue;
}
};
term.clear_line()?;
render.confirm_prompt(&self.prompt, value)?;
}
} else {
loop {
let input = term.read_key()?;
let value = match input {
Key::Char('y') | Key::Char('Y') => Some(true),
Key::Char('n') | Key::Char('N') => Some(false),
Key::Enter if self.default.is_some() => Some(self.default.unwrap()),
Key::Escape | Key::Char('q') if allow_quit => None,
_ => {
continue;
}
};
rv = value;
break;
}
}
term.clear_line()?;
if self.report {
render.confirm_prompt_selection(&self.prompt, rv)?;
}
term.show_cursor()?;
term.flush()?;
Ok(rv)
}
}
impl<'a> Confirm<'a> {
pub fn with_theme(theme: &'a dyn Theme) -> Self {
Self {
prompt: "".into(),
report: true,
default: None,
show_default: true,
wait_for_newline: false,
theme,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_clone() {
let confirm = Confirm::new().with_prompt("Do you want to continue?");
let _ = confirm.clone();
}
}