1use log::Level;
2
3use crate::app::command::Command;
4use std::cmp::Ordering;
5use std::fmt::{Display, Formatter, Result as FmtResult};
6use std::time::Instant;
7
8pub const COMMAND_PREFIX: char = ':';
10pub const SEARCH_PREFIX: char = '/';
12
13#[derive(Clone, Debug, PartialEq, Eq, Default)]
15pub enum OutputType {
16 #[default]
18 None,
19 Success,
21 Warning,
23 Failure,
25 Action,
27}
28
29impl Display for OutputType {
30 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
31 write!(
32 f,
33 "{}",
34 match self {
35 Self::Success => "(i) ",
36 Self::Warning => "(w) ",
37 Self::Failure => "(e) ",
38 _ => "",
39 }
40 )
41 }
42}
43
44impl From<String> for OutputType {
45 fn from(s: String) -> Self {
46 match s.to_lowercase().as_str() {
47 "success" => Self::Success,
48 "warning" => Self::Warning,
49 "failure" => Self::Failure,
50 "action" => Self::Action,
51 _ => Self::None,
52 }
53 }
54}
55
56impl OutputType {
57 pub fn as_log_level(&self) -> Level {
59 match self {
60 OutputType::None => Level::Trace,
61 OutputType::Success => Level::Info,
62 OutputType::Warning => Level::Warn,
63 OutputType::Failure => Level::Error,
64 OutputType::Action => Level::Info,
65 }
66 }
67}
68
69#[derive(Clone, Debug, Default)]
76pub struct Prompt {
77 pub text: String,
79 pub output_type: OutputType,
81 pub clock: Option<Instant>,
83 pub command: Option<Command>,
85 pub history: Vec<String>,
87 pub history_index: usize,
89}
90
91impl Prompt {
92 fn enable(&mut self, prefix: char) {
98 self.text = if self.text.is_empty() || self.clock.is_some() {
99 prefix.to_string()
100 } else {
101 format!("{}{}", prefix, &self.text[1..self.text.len()])
102 };
103 self.output_type = OutputType::None;
104 self.clock = None;
105 self.command = None;
106 self.history_index = 0;
107 }
108
109 pub fn is_enabled(&self) -> bool {
111 !self.text.is_empty() && self.clock.is_none() && self.command.is_none()
112 }
113
114 pub fn enable_command_input(&mut self) {
116 self.enable(COMMAND_PREFIX);
117 }
118
119 pub fn is_command_input_enabled(&self) -> bool {
121 self.text.starts_with(COMMAND_PREFIX)
122 }
123
124 pub fn enable_search(&mut self) {
126 self.enable(SEARCH_PREFIX);
127 }
128
129 pub fn is_search_enabled(&self) -> bool {
131 self.text.starts_with(SEARCH_PREFIX)
132 }
133
134 pub fn set_output<S: AsRef<str>>(&mut self, output: (OutputType, S)) {
136 let (output_type, message) = output;
137 log::log!(target: "tui", self.output_type.as_log_level(), "{}", message.as_ref());
138 self.output_type = output_type;
139 self.text = message.as_ref().to_string();
140 self.clock = Some(Instant::now());
141 }
142
143 pub fn set_command(&mut self, command: Command) {
145 self.text = format!("press 'y' to {command}");
146 self.output_type = OutputType::Action;
147 self.command = Some(command);
148 self.clock = Some(Instant::now());
149 }
150
151 pub fn next(&mut self) {
153 match self.history_index.cmp(&1) {
154 Ordering::Greater => {
155 self.history_index -= 1;
156 self.text = self.history
157 [self.history.len() - self.history_index]
158 .to_string();
159 }
160 Ordering::Equal => {
161 self.text = String::from(":");
162 self.history_index = 0;
163 }
164 Ordering::Less => {}
165 }
166 }
167
168 pub fn previous(&mut self) {
170 if self.history.len() > self.history_index {
171 self.text = self.history
172 [self.history.len() - (self.history_index + 1)]
173 .to_string();
174 self.history_index += 1;
175 }
176 }
177
178 pub fn clear(&mut self) {
180 self.text.clear();
181 self.output_type = OutputType::None;
182 self.clock = None;
183 self.command = None;
184 self.history_index = 0;
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use pretty_assertions::{assert_eq, assert_ne};
192 #[test]
193 fn test_app_prompt() {
194 let mut prompt = Prompt::default();
195 prompt.enable_command_input();
196 assert!(prompt.is_command_input_enabled());
197 prompt.enable_search();
198 assert!(prompt.is_search_enabled());
199 assert!(prompt.is_enabled());
200 prompt.set_output((OutputType::from(String::from("success")), "Test"));
201 assert_eq!(String::from("Test"), prompt.text);
202 assert_eq!(OutputType::Success, prompt.output_type);
203 assert_ne!(
204 0,
205 prompt
206 .clock
207 .expect("could not get clock")
208 .elapsed()
209 .as_nanos()
210 );
211 assert!(!prompt.is_enabled());
212 prompt.clear();
213 assert_eq!(String::new(), prompt.text);
214 assert_eq!(None, prompt.clock);
215 prompt.history =
216 vec![String::from("0"), String::from("1"), String::from("2")];
217 for i in 0..prompt.history.len() {
218 prompt.previous();
219 assert_eq!((prompt.history.len() - i - 1).to_string(), prompt.text);
220 }
221 for i in 1..prompt.history.len() {
222 prompt.next();
223 assert_eq!(i.to_string(), prompt.text);
224 }
225 for output_type in [
226 OutputType::from(String::from("warning")),
227 OutputType::from(String::from("failure")),
228 OutputType::from(String::from("action")),
229 OutputType::from(String::from("test")),
230 ] {
231 assert_eq!(
232 match output_type {
233 OutputType::Warning => "(w) ",
234 OutputType::Failure => "(e) ",
235 _ => "",
236 },
237 &output_type.to_string()
238 );
239 }
240 }
241}