use crate::{
error::ClackError,
style::{ansi, chars},
};
use console::style;
use crossterm::{cursor, QueueableCommand};
use rustyline::DefaultEditor;
use std::{
fmt::Display,
io::{stdout, Write},
};
type ValidateFn = dyn Fn(&str) -> Option<&'static str>;
pub struct Input<M: Display> {
message: M,
default_value: Option<String>,
initial_value: Option<String>,
validate: Option<Box<ValidateFn>>,
cancel: Option<Box<dyn Fn()>>,
}
impl<M: Display> Input<M> {
pub fn new(message: M) -> Self {
Input {
message,
default_value: None,
initial_value: None,
validate: None,
cancel: None,
}
}
pub fn default_value<S: Into<String>>(&mut self, def: S) -> &mut Self {
self.default_value = Some(def.into());
self
}
pub fn placeholder(&mut self) -> &mut Self {
todo!();
}
pub fn initial_value<S: Into<String>>(&mut self, init: S) -> &mut Self {
self.initial_value = Some(init.into());
self
}
pub fn validate<F>(&mut self, validate: F) -> &mut Self
where
F: Fn(&str) -> Option<&'static str> + 'static,
{
let validate = Box::new(validate);
self.validate = Some(validate);
self
}
fn do_validate(&self, input: &str) -> Option<&'static str> {
if let Some(validate) = self.validate.as_deref() {
validate(input)
} else {
None
}
}
pub fn cancel<F>(&mut self, cancel: F) -> &mut Self
where
F: Fn() + 'static,
{
let cancel = Box::new(cancel);
self.cancel = Some(cancel);
self
}
fn interact_once(&self, enforce_non_empty: bool) -> Result<Option<String>, ClackError> {
let default_prompt = format!("{} ", style(*chars::BAR).cyan());
let val_prompt = format!("{} ", style(*chars::BAR).yellow());
let mut editor = DefaultEditor::new()?;
let mut initial_value = self.initial_value.clone();
let mut is_val = false;
loop {
let prompt = if is_val { &val_prompt } else { &default_prompt };
let line = if let Some(ref init) = initial_value {
editor.readline_with_initial(prompt, (init, ""))
} else {
editor.readline(prompt)
};
if let Ok(value) = line {
if value.is_empty() {
if let Some(default_value) = self.default_value.clone() {
break Ok(Some(default_value));
} else if enforce_non_empty {
initial_value = None;
is_val = true;
self.w_val("value is required");
} else {
break Ok(None);
}
} else if let Some(text) = self.do_validate(&value) {
initial_value = Some(value.clone());
is_val = true;
self.w_val(text);
} else {
break Ok(Some(value));
}
} else {
break Err(ClackError::Cancelled);
}
}
}
pub fn required(&self) -> Result<String, ClackError> {
self.w_init();
let interact = self.interact_once(true);
match interact {
Ok(Some(value)) => {
self.w_out(&value);
Ok(value)
}
Ok(None) => unreachable!(),
Err(ClackError::Cancelled) => {
self.w_cancel();
if let Some(cancel) = self.cancel.as_deref() {
cancel();
}
Err(ClackError::Cancelled)
}
Err(err) => Err(err),
}
}
pub fn interact(&self) -> Result<Option<String>, ClackError> {
self.w_init();
let interact = self.interact_once(false);
match interact {
Ok(val) => {
let v = val.clone().unwrap_or(String::new());
self.w_out(&v);
Ok(val)
}
Err(ClackError::Cancelled) => {
self.w_cancel();
if let Some(cancel) = self.cancel.as_deref() {
cancel();
}
Err(ClackError::Cancelled)
}
Err(err) => Err(err),
}
}
}
impl<M: Display> Input<M> {
fn w_init(&self) {
let mut stdout = stdout();
println!("{}", *chars::BAR);
println!("{} {}", style(*chars::STEP_ACTIVE).cyan(), self.message);
println!("{}", style(*chars::BAR).cyan());
print!("{}", style(*chars::BAR_END).cyan());
let _ = stdout.queue(cursor::MoveToPreviousLine(1));
let _ = stdout.flush();
print!("{} ", style(*chars::BAR).cyan());
let _ = stdout.flush();
}
fn w_val(&self, text: &str) {
let mut stdout = stdout();
let _ = stdout.queue(cursor::MoveToPreviousLine(2));
let _ = stdout.flush();
println!("{} {}", style(*chars::STEP_ERROR).yellow(), self.message);
println!("{}", style(*chars::BAR).yellow());
print!("{}", ansi::CLEAR_LINE);
print!(
"{} {}",
style(*chars::BAR_END).yellow(),
style(text).yellow()
);
let _ = stdout.queue(cursor::MoveToPreviousLine(1));
let _ = stdout.flush();
}
fn w_out(&self, value: &str) {
let mut stdout = stdout();
let _ = stdout.queue(cursor::MoveToPreviousLine(2));
let _ = stdout.flush();
println!("{} {}", style(*chars::STEP_SUBMIT).green(), self.message);
println!("{} {}", *chars::BAR, style(value).dim());
println!("{}", style(ansi::CLEAR_LINE));
let _ = stdout.queue(cursor::MoveToPreviousLine(1));
let _ = stdout.flush();
}
fn w_cancel(&self) {
let mut stdout = stdout();
let _ = stdout.queue(cursor::MoveToPreviousLine(2));
let _ = stdout.flush();
println!("{} {}", style(*chars::STEP_CANCEL).red(), self.message);
print!("{}", style(ansi::CLEAR_LINE));
println!(
"{} {}",
*chars::BAR,
style("cancelled").strikethrough().dim()
);
println!("{}", style(ansi::CLEAR_LINE));
let _ = stdout.queue(cursor::MoveToPreviousLine(1));
let _ = stdout.flush();
}
}
pub fn input<M: Display>(message: M) -> Input<M> {
Input::new(message)
}