use log::Level;
use crate::app::command::Command;
use std::cmp::Ordering;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::time::Instant;
pub const COMMAND_PREFIX: char = ':';
pub const SEARCH_PREFIX: char = '/';
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub enum OutputType {
#[default]
None,
Success,
Warning,
Failure,
Action,
}
impl Display for OutputType {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(
f,
"{}",
match self {
Self::Success => "(i) ",
Self::Warning => "(w) ",
Self::Failure => "(e) ",
_ => "",
}
)
}
}
impl From<String> for OutputType {
fn from(s: String) -> Self {
match s.to_lowercase().as_str() {
"success" => Self::Success,
"warning" => Self::Warning,
"failure" => Self::Failure,
"action" => Self::Action,
_ => Self::None,
}
}
}
impl OutputType {
pub fn as_log_level(&self) -> Level {
match self {
OutputType::None => Level::Trace,
OutputType::Success => Level::Info,
OutputType::Warning => Level::Warn,
OutputType::Failure => Level::Error,
OutputType::Action => Level::Info,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Prompt {
pub text: String,
pub output_type: OutputType,
pub clock: Option<Instant>,
pub command: Option<Command>,
pub history: Vec<String>,
pub history_index: usize,
}
impl Prompt {
fn enable(&mut self, prefix: char) {
self.text = if self.text.is_empty() || self.clock.is_some() {
prefix.to_string()
} else {
format!("{}{}", prefix, &self.text[1..self.text.len()])
};
self.output_type = OutputType::None;
self.clock = None;
self.command = None;
self.history_index = 0;
}
pub fn is_enabled(&self) -> bool {
!self.text.is_empty() && self.clock.is_none() && self.command.is_none()
}
pub fn enable_command_input(&mut self) {
self.enable(COMMAND_PREFIX);
}
pub fn is_command_input_enabled(&self) -> bool {
self.text.starts_with(COMMAND_PREFIX)
}
pub fn enable_search(&mut self) {
self.enable(SEARCH_PREFIX);
}
pub fn is_search_enabled(&self) -> bool {
self.text.starts_with(SEARCH_PREFIX)
}
pub fn set_output<S: AsRef<str>>(&mut self, output: (OutputType, S)) {
let (output_type, message) = output;
log::log!(target: "tui", self.output_type.as_log_level(), "{}", message.as_ref());
self.output_type = output_type;
self.text = message.as_ref().to_string();
self.clock = Some(Instant::now());
}
pub fn set_command(&mut self, command: Command) {
self.text = format!("press 'y' to {command}");
self.output_type = OutputType::Action;
self.command = Some(command);
self.clock = Some(Instant::now());
}
pub fn next(&mut self) {
match self.history_index.cmp(&1) {
Ordering::Greater => {
self.history_index -= 1;
self.text = self.history
[self.history.len() - self.history_index]
.to_string();
}
Ordering::Equal => {
self.text = String::from(":");
self.history_index = 0;
}
Ordering::Less => {}
}
}
pub fn previous(&mut self) {
if self.history.len() > self.history_index {
self.text = self.history
[self.history.len() - (self.history_index + 1)]
.to_string();
self.history_index += 1;
}
}
pub fn clear(&mut self) {
self.text.clear();
self.output_type = OutputType::None;
self.clock = None;
self.command = None;
self.history_index = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::{assert_eq, assert_ne};
#[test]
fn test_app_prompt() {
let mut prompt = Prompt::default();
prompt.enable_command_input();
assert!(prompt.is_command_input_enabled());
prompt.enable_search();
assert!(prompt.is_search_enabled());
assert!(prompt.is_enabled());
prompt.set_output((OutputType::from(String::from("success")), "Test"));
assert_eq!(String::from("Test"), prompt.text);
assert_eq!(OutputType::Success, prompt.output_type);
assert_ne!(
0,
prompt
.clock
.expect("could not get clock")
.elapsed()
.as_nanos()
);
assert!(!prompt.is_enabled());
prompt.clear();
assert_eq!(String::new(), prompt.text);
assert_eq!(None, prompt.clock);
prompt.history =
vec![String::from("0"), String::from("1"), String::from("2")];
for i in 0..prompt.history.len() {
prompt.previous();
assert_eq!((prompt.history.len() - i - 1).to_string(), prompt.text);
}
for i in 1..prompt.history.len() {
prompt.next();
assert_eq!(i.to_string(), prompt.text);
}
for output_type in [
OutputType::from(String::from("warning")),
OutputType::from(String::from("failure")),
OutputType::from(String::from("action")),
OutputType::from(String::from("test")),
] {
assert_eq!(
match output_type {
OutputType::Warning => "(w) ",
OutputType::Failure => "(e) ",
_ => "",
},
&output_type.to_string()
);
}
}
}