use super::input::PlaceholderHightlighter;
use crate::{
error::ClackError,
style::{ansi, chars},
};
use crossterm::{cursor, QueueableCommand};
use owo_colors::OwoColorize;
use rustyline::Editor;
use std::{
fmt::Display,
io::{stdout, Write},
str::FromStr,
};
type ValidateFn = dyn Fn(&str) -> Option<&'static str>;
pub struct MultiInput<M: Display> {
message: M,
initial_value: Option<String>,
placeholder: Option<String>,
validate: Option<Box<ValidateFn>>,
cancel: Option<Box<dyn Fn()>>,
min: u16,
max: u16,
}
impl<M: Display> MultiInput<M> {
pub fn new(message: M) -> Self {
MultiInput {
message,
validate: None,
initial_value: None,
placeholder: None,
cancel: None,
min: 1,
max: u16::MAX,
}
}
pub fn initial_value<S: Into<String>>(&mut self, initial_value: S) -> &mut Self {
self.initial_value = Some(initial_value.into());
self
}
pub fn placeholder<S: Into<String>>(&mut self, placeholder: S) -> &mut Self {
self.placeholder = Some(placeholder.into());
self
}
pub fn min(&mut self, min: u16) -> &mut Self {
self.min = min;
self
}
pub fn max(&mut self, max: u16) -> &mut Self {
self.max = max;
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<T: FromStr + Display>(
&self,
enforce_non_empty: bool,
amt: u16,
) -> Result<Option<T>, ClackError>
where
T::Err: Display,
{
let prompt = format!("{} ", *chars::BAR);
let mut editor = Editor::new()?;
let highlighter = PlaceholderHightlighter::new(self.placeholder.as_deref());
editor.set_helper(Some(highlighter));
let mut initial_value = self.initial_value.clone();
loop {
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 enforce_non_empty {
initial_value = None;
if let Some(helper) = editor.helper_mut() {
helper.is_val = true;
}
let text = format!("minimum {}", self.min);
self.w_val(&text, amt);
} else {
break Ok(None);
}
} else if let Some(text) = self.do_validate(&value) {
initial_value = Some(value);
if let Some(helper) = editor.helper_mut() {
helper.is_val = true;
}
self.w_val(text, amt);
} else {
match value.parse::<T>() {
Ok(value) => break Ok(Some(value)),
Err(err) => {
initial_value = Some(value);
self.w_val(&err.to_string(), amt);
}
}
}
} else {
break Err(ClackError::Cancelled);
}
}
}
pub fn parse<T: FromStr + Clone + Display>(&self) -> Result<Vec<T>, ClackError>
where
T::Err: Display,
{
self.w_init();
let mut v = vec![];
loop {
let amt = v.len() as u16;
let enforce_non_empty = amt < self.min;
let once = self.interact_once::<T>(enforce_non_empty, amt);
match once {
Ok(Some(value)) => {
self.w_line(&value, amt);
v.push(value);
if v.len() as u16 == self.max {
println!();
self.w_out(&v);
break;
}
}
Ok(None) => {
self.w_out(&v);
break;
}
Err(ClackError::Cancelled) => {
self.w_cancel(v.len());
if let Some(cancel) = self.cancel.as_deref() {
cancel();
}
return Err(ClackError::Cancelled);
}
Err(err) => return Err(err),
}
}
Ok(v)
}
pub fn interact(&self) -> Result<Vec<String>, ClackError> {
self.w_init();
let mut v = vec![];
loop {
let amt = v.len() as u16;
let enforce_non_empty = amt < self.min;
let once = self.interact_once::<String>(enforce_non_empty, amt);
match once {
Ok(Some(value)) => {
self.w_line(&value, amt);
v.push(value);
if v.len() as u16 == self.max {
println!();
self.w_out(&v);
break;
}
}
Ok(None) => {
self.w_out(&v);
break;
}
Err(ClackError::Cancelled) => {
self.w_cancel(v.len());
if let Some(cancel) = self.cancel.as_deref() {
cancel();
}
return Err(ClackError::Cancelled);
}
Err(err) => return Err(err),
}
}
Ok(v)
}
}
impl<M: Display> MultiInput<M> {
fn w_init(&self) {
let mut stdout = stdout();
println!("{}", *chars::BAR);
println!("{} {}", (*chars::STEP_ACTIVE).cyan(), self.message);
println!("{}", (*chars::BAR).cyan());
print!("{}", (*chars::BAR_END).cyan());
let _ = stdout.queue(cursor::MoveToPreviousLine(1));
let _ = stdout.flush();
print!("{} ", (*chars::BAR).cyan());
let _ = stdout.flush();
}
fn w_line<V: Display>(&self, value: V, amt: u16) {
let mut stdout = stdout();
let _ = stdout.queue(cursor::MoveToPreviousLine(amt + 2));
let _ = stdout.flush();
println!("{} {}", (*chars::STEP_ACTIVE).cyan(), self.message);
for _ in 0..amt {
println!("{}", (*chars::BAR).cyan());
}
println!("{} {}", (*chars::BAR).cyan(), value.dimmed());
println!("{}", (*chars::BAR).cyan());
print!("{}", ansi::CLEAR_LINE);
print!("{}", (*chars::BAR_END).cyan());
let _ = stdout.queue(cursor::MoveToPreviousLine(1));
let _ = stdout.flush();
}
fn w_val(&self, text: &str, amt: u16) {
let mut stdout = stdout();
let _ = stdout.queue(cursor::MoveToPreviousLine(amt + 2));
let _ = stdout.flush();
println!("{} {}", (*chars::STEP_ERROR).yellow(), self.message);
for _ in 0..=amt {
println!("{}", (*chars::BAR).yellow());
}
print!("{}", ansi::CLEAR_LINE);
print!("{} {}", (*chars::BAR_END).yellow(), text.yellow());
let _ = stdout.queue(cursor::MoveToPreviousLine(1));
let _ = stdout.flush();
}
fn w_out<V: Display>(&self, values: &[V]) {
let amt = values.len();
let mut stdout = stdout();
let _ = stdout.queue(cursor::MoveToPreviousLine(amt as u16 + 2));
let _ = stdout.flush();
println!("{} {}", (*chars::STEP_SUBMIT).green(), self.message);
if amt == 0 {
println!("{}", *chars::BAR);
}
for val in values {
println!("{} {}", *chars::BAR, val.dimmed());
}
println!("{}", ansi::CLEAR_LINE);
println!("{}", ansi::CLEAR_LINE);
let _ = stdout.queue(cursor::MoveToPreviousLine(2));
let _ = stdout.flush();
}
fn w_cancel(&self, amt: usize) {
let mut stdout = stdout();
let _ = stdout.queue(cursor::MoveToPreviousLine(1));
let _ = stdout.flush();
print!("{}", ansi::CLEAR_LINE);
println!("{} {}", *chars::BAR, "cancelled".strikethrough().dimmed());
print!("{}", ansi::CLEAR_LINE);
let _ = stdout.queue(cursor::MoveToPreviousLine(amt as u16 + 2));
let _ = stdout.flush();
println!("{} {}", (*chars::STEP_CANCEL).red(), self.message);
for _ in 0..amt {
println!("{}", *chars::BAR);
}
let _ = stdout.queue(cursor::MoveToNextLine(1));
let _ = stdout.flush();
}
}
pub fn multi_input<M: Display>(message: M) -> MultiInput<M> {
MultiInput::new(message)
}