use std::io;
use std::path::Path;
use crossterm::event::{Event, KeyCode, KeyEvent};
use crossterm::{
cursor, execute,
terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType},
};
use regex::Regex;
use crate::color::ENABLE_COLOR;
use crate::menu::handle_key_input;
use crate::style::{Color, Print, SetForegroundColor};
use crate::terminal::console_render::raw_mode_wrapper;
pub struct Requirements {
regex: Option<Regex>,
path: bool,
allow_creating: bool,
true_note: Option<String>,
false_note: Option<String>,
}
impl Default for Requirements {
fn default() -> Self {
Requirements::path()
}
}
impl Requirements {
pub fn regex(regex: Regex) -> Self {
Requirements {
regex: Some(regex),
path: false,
allow_creating: false,
true_note: None,
false_note: None,
}
}
pub fn path() -> Self {
Requirements {
regex: None,
path: true,
allow_creating: false,
true_note: None,
false_note: Some("Please enter a valid path!".to_string()),
}
}
pub fn set_regex(mut self, regex: Regex) -> Self {
self.regex = Some(regex);
self
}
pub fn set_note(mut self, valid: &str, invalid: &str) -> Self {
self.true_note = Some(valid.to_string());
self.false_note = Some(invalid.to_string());
self
}
pub fn allow_creation(mut self) {
self.allow_creating = true;
}
}
pub struct Input {
title: String,
requirements: Vec<Requirements>,
default: Option<String>,
allow_force: bool,
}
impl Input {
pub fn new(title: &str, req: Requirements) -> Self {
let reqs = vec![req];
Input {
title: title.to_string(),
requirements: reqs,
default: None,
allow_force: false,
}
}
pub fn start(&self) -> Box<String> {
let mut force: bool = false;
let mut buffer = String::new();
let mut validation_status = Vec::new();
let mut notes = Vec::new();
loop {
raw_mode_wrapper!(self.render_input_prompt(
&buffer,
validation_status.iter().all(|&status| status),
¬es,
));
let result = handle_key_input(&mut buffer, &mut force);
validation_status.clear();
notes.clear();
for req in &self.requirements {
let mut path_valid = true;
if req.path {
path_valid = Self::validate_path(&buffer);
}
let regex_valid = req
.regex
.as_ref()
.map_or(true, |regex| Self::validate_regex(&buffer, regex));
validation_status.push(path_valid && regex_valid);
notes.push(if validation_status.last() == Some(&true) {
req.true_note.clone()
} else {
req.false_note.clone()
});
}
if result {
if validation_status.iter().all(|&status| status) {
break;
} else if self.default.is_some() && buffer.is_empty() {
buffer = self.default.clone().unwrap();
break;
}
}
if force && self.allow_force {
break;
}
}
execute!(
io::stdout(),
cursor::MoveTo(0, 4),
Clear(ClearType::FromCursorDown),
cursor::Show,
)
.unwrap();
Box::new(buffer)
}
pub fn add_requirement(mut self, requirement: Requirements) -> Self {
self.requirements.push(requirement);
self
}
pub fn allow_force(mut self) -> Self {
self.allow_force = true;
self
}
pub fn set_default(mut self, default: &str) -> Self {
self.default = Some(default.to_string());
self
}
#[inline]
fn validate_path(path: &str) -> bool {
Path::new(path).exists()
}
#[inline]
fn validate_regex(buffer: &str, regex: &Regex) -> bool {
if regex.is_match(buffer) {
true
} else {
execute!(
io::stdout(),
cursor::MoveTo(0, 5),
Clear(ClearType::CurrentLine)
)
.unwrap();
false
}
}
fn render_input_prompt(&self, buffer: &str, valid: bool, notes: &[Option<String>]) {
execute!(
io::stdout(),
cursor::MoveTo(0, 4),
Clear(ClearType::CurrentLine),
cursor::Hide,
)
.unwrap();
let (text_color, content) = if !buffer.is_empty() || self.default.is_none() {
let text_color = if *ENABLE_COLOR {
if !valid {
Color::DarkRed
} else {
Color::Green
}
} else {
Color::Reset
};
(text_color, buffer.to_string())
} else {
let text_color = if *ENABLE_COLOR {
Color::Grey
} else {
Color::Reset
};
(text_color, self.default.clone().unwrap_or_default())
};
execute!(
io::stdout(),
Print(&self.title),
cursor::MoveToNextLine(1),
Clear(ClearType::CurrentLine),
SetForegroundColor(text_color),
Print(content),
)
.unwrap();
if self.default.is_some() && buffer.is_empty() {
execute!(io::stdout(), Print(" (Default)")).unwrap();
}
execute!(
io::stdout(),
SetForegroundColor(Color::Reset),
cursor::SavePosition,
cursor::MoveToNextLine(2)
)
.unwrap();
if *ENABLE_COLOR {
execute!(io::stdout(), SetForegroundColor(Color::DarkGrey)).unwrap();
}
for note in notes.iter() {
match note {
Some(note_str) => {
execute!(
io::stdout(),
cursor::MoveToNextLine(1),
Print("- "),
Print(note_str)
)
.unwrap();
}
None => {
execute!(
io::stdout(),
cursor::MoveToNextLine(1),
Clear(ClearType::CurrentLine),
cursor::MoveToPreviousLine(1),
)
.unwrap();
}
}
}
if self.allow_force && !buffer.is_empty() && !valid {
execute!(
io::stdout(),
cursor::MoveToNextLine(2),
Print("Press SHIFT + Enter to force input"),
cursor::MoveToPreviousLine(1),
)
.unwrap();
} else {
execute!(
io::stdout(),
cursor::MoveToNextLine(2),
Clear(ClearType::CurrentLine),
cursor::MoveToPreviousLine(2),
)
.unwrap();
}
if self.default.is_some() && buffer.is_empty() {
execute!(
io::stdout(),
cursor::MoveToNextLine(2),
Print("Press Enter to accept default"),
cursor::MoveToPreviousLine(1),
)
.unwrap();
} else {
execute!(
io::stdout(),
cursor::MoveToNextLine(2),
Clear(ClearType::CurrentLine),
cursor::MoveToPreviousLine(2),
)
.unwrap();
}
execute!(
io::stdout(),
cursor::RestorePosition,
SetForegroundColor(Color::Reset),
cursor::Show,
)
.unwrap();
}
}
pub struct Confirm {
title: String,
default: bool,
}
impl Default for Confirm {
fn default() -> Self {
Confirm {
title: "Do you want to continue?".to_string(),
default: true,
}
}
}
impl Confirm {
pub fn new(title: &str, default: bool) -> Self {
Confirm {
title: title.to_string(),
default,
}
}
pub fn start(&self) -> bool {
enable_raw_mode().unwrap();
execute!(io::stdout(), Print(&self.title)).unwrap();
let default_text = if self.default { " (Y/n)" } else { " (y/N)" };
execute!(io::stdout(), Print(default_text), cursor::Hide).unwrap();
loop {
if let Event::Key(key_event) = crossterm::event::read().unwrap() {
let KeyEvent { code, .. } = key_event;
let result = match code {
KeyCode::Char('y') | KeyCode::Char('Y') => {
execute!(io::stdout(), Print("y"), cursor::Show).unwrap();
true
}
KeyCode::Char('n') | KeyCode::Char('N') => {
execute!(io::stdout(), Print("n"), cursor::Show).unwrap();
false
}
_ => {
execute!(io::stdout(), cursor::Show).unwrap();
self.default
}
};
disable_raw_mode().unwrap();
return result;
}
}
}
}