1use crate::{
4 error::ClackError,
5 style::{ansi, chars},
6};
7use crossterm::{QueueableCommand, cursor};
8use owo_colors::OwoColorize;
9use rustyline::{
10 Completer, Editor, Helper, Hinter, Validator,
11 highlight::{CmdKind, Highlighter},
12};
13use std::{
14 borrow::{Borrow, Cow},
15 error::Error,
16 fmt::Display,
17 io::{Write, stdout},
18 str::FromStr,
19};
20
21#[derive(Completer, Helper, Hinter, Validator)]
22pub(super) struct PlaceholderHighlighter<'a> {
23 placeholder: Option<&'a str>,
24 pub is_val: bool,
25}
26
27impl<'a> PlaceholderHighlighter<'a> {
28 pub fn new(placeholder: Option<&'a str>) -> Self {
29 PlaceholderHighlighter {
30 placeholder,
31 is_val: false,
32 }
33 }
34}
35
36impl Highlighter for PlaceholderHighlighter<'_> {
37 fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
38 if let Some(placeholder) = self.placeholder {
39 if line.is_empty() {
40 Cow::Owned(placeholder.dimmed().to_string())
41 } else {
42 Cow::Borrowed(line)
43 }
44 } else {
45 Cow::Borrowed(line)
46 }
47 }
48
49 fn highlight_char(&self, _line: &str, _pos: usize, _kind: CmdKind) -> bool {
50 true
51 }
52
53 fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
54 &'s self,
55 prompt: &'p str,
56 default: bool,
57 ) -> Cow<'b, str> {
58 if !default {
59 Cow::Borrowed(prompt)
61 } else if self.is_val {
62 Cow::Owned(prompt.yellow().to_string())
63 } else {
64 Cow::Owned(prompt.cyan().to_string())
65 }
66 }
67}
68
69pub(super) type ValidateFn = dyn Fn(&str) -> Result<(), Cow<'static, str>>;
70
71pub struct Input<M: Display> {
101 message: M,
102 initial_value: Option<String>,
103 placeholder: Option<String>,
104 validate: Option<Box<ValidateFn>>,
105 cancel: Option<Box<dyn Fn()>>,
106}
107
108impl<M: Display> Input<M> {
109 pub fn new(message: M) -> Self {
123 Input {
124 message,
125 initial_value: None,
126 placeholder: None,
127 validate: None,
128 cancel: None,
129 }
130 }
131
132 pub fn placeholder<S: ToString>(&mut self, placeholder: S) -> &mut Self {
146 self.placeholder = Some(placeholder.to_string());
147 self
148 }
149
150 pub fn maybe_initial<T: Borrow<Option<S>>, S: ToString>(&mut self, initial: T) -> &mut Self {
164 if let Some(initial) = initial.borrow() {
165 self.initial_value = Some(initial.to_string());
166 }
167
168 self
169 }
170
171 pub fn initial_value<S: ToString>(&mut self, initial_value: S) -> &mut Self {
185 self.initial_value = Some(initial_value.to_string());
186 self
187 }
188
189 pub fn validate<F>(&mut self, validate: F) -> &mut Self
213 where
214 F: Fn(&str) -> Result<(), Cow<'static, str>> + 'static,
215 {
216 let validate = Box::new(validate);
217 self.validate = Some(validate);
218 self
219 }
220
221 fn do_validate(&self, input: &str) -> Result<(), Cow<'static, str>> {
222 if let Some(validate) = self.validate.as_deref() {
223 validate(input)
224 } else {
225 Ok(())
226 }
227 }
228
229 pub fn cancel<F>(&mut self, cancel: F) -> &mut Self
247 where
248 F: Fn() + 'static,
249 {
250 let cancel = Box::new(cancel);
251 self.cancel = Some(cancel);
252 self
253 }
254
255 fn interact_once<T: FromStr>(&self, enforce_non_empty: bool) -> Result<Option<T>, ClackError>
256 where
257 T::Err: Error,
258 {
259 let prompt = format!("{} ", *chars::BAR);
260
261 let mut editor = Editor::new()?;
262 let helper = PlaceholderHighlighter::new(self.placeholder.as_deref());
263 editor.set_helper(Some(helper));
264
265 let mut initial_value = self.initial_value.as_deref().map(Cow::Borrowed);
266 loop {
267 let line = if let Some(ref init) = initial_value {
268 editor.readline_with_initial(&prompt, (init, ""))
269 } else {
270 editor.readline(&prompt)
271 };
272
273 if let Ok(value) = line {
275 if value.is_empty() {
276 if enforce_non_empty {
277 initial_value = None;
278
279 if let Some(helper) = editor.helper_mut() {
280 helper.is_val = true;
281 }
282
283 self.w_val("value is required");
284 } else {
285 break Ok(None);
286 }
287 } else if let Err(text) = self.do_validate(&value) {
288 initial_value = Some(Cow::Owned(value));
289
290 if let Some(helper) = editor.helper_mut() {
291 helper.is_val = true;
292 }
293
294 self.w_val(&text);
295 } else {
296 match value.parse::<T>() {
297 Ok(val) => break Ok(Some(val)),
298 Err(err) => {
299 initial_value = Some(Cow::Owned(value));
300
301 if let Some(helper) = editor.helper_mut() {
302 helper.is_val = true;
303 }
304
305 self.w_val(&err.to_string());
306 }
307 }
308 }
309 } else {
310 break Err(ClackError::Cancelled);
311 }
312 }
313 }
314
315 pub fn parse<T: FromStr + Display>(&self) -> Result<T, ClackError>
331 where
332 T::Err: Error,
333 {
334 self.w_init();
335
336 let interact = self.interact_once::<T>(true);
337 match interact {
338 Ok(Some(value)) => {
339 self.w_out(&value);
340 Ok(value)
341 }
342 Ok(None) => unreachable!(),
343 Err(ClackError::Cancelled) => {
344 self.w_cancel();
345 if let Some(cancel) = self.cancel.as_deref() {
346 cancel();
347 }
348
349 Err(ClackError::Cancelled)
350 }
351 Err(err) => Err(err),
352 }
353 }
354
355 pub fn maybe_parse<T: FromStr + Display>(&self) -> Result<Option<T>, ClackError>
368 where
369 T::Err: Error,
370 {
371 self.w_init();
372
373 let interact = self.interact_once::<T>(false);
374 match interact {
375 Ok(val) => {
376 if let Some(val) = &val {
377 self.w_out(val);
378 } else {
379 self.w_out("");
380 }
381
382 Ok(val)
383 }
384 Err(ClackError::Cancelled) => {
385 self.w_cancel();
386 if let Some(cancel) = self.cancel.as_deref() {
387 cancel();
388 }
389
390 Err(ClackError::Cancelled)
391 }
392 Err(err) => Err(err),
393 }
394 }
395
396 pub fn required(&self) -> Result<String, ClackError> {
410 self.w_init();
411
412 let interact = self.interact_once::<String>(true);
413 match interact {
414 Ok(Some(value)) => {
415 self.w_out(&value);
416 Ok(value)
417 }
418 Ok(None) => unreachable!(),
419 Err(ClackError::Cancelled) => {
420 self.w_cancel();
421 if let Some(cancel) = self.cancel.as_deref() {
422 cancel();
423 }
424
425 Err(ClackError::Cancelled)
426 }
427 Err(err) => Err(err),
428 }
429 }
430
431 pub fn interact(&self) -> Result<Option<String>, ClackError> {
461 self.w_init();
462
463 let interact = self.interact_once(false);
464 match interact {
465 Ok(val) => {
466 let v = val.as_deref().unwrap_or("");
467 self.w_out(v);
468 Ok(val)
469 }
470 Err(ClackError::Cancelled) => {
471 self.w_cancel();
472 if let Some(cancel) = self.cancel.as_deref() {
473 cancel();
474 }
475
476 Err(ClackError::Cancelled)
477 }
478 Err(err) => Err(err),
479 }
480 }
481}
482
483impl<M: Display> Input<M> {
484 fn w_init(&self) {
485 let mut stdout = stdout();
486
487 println!("{}", *chars::BAR);
488 println!("{} {}", (*chars::STEP_ACTIVE).cyan(), self.message);
489 println!("{}", (*chars::BAR).cyan());
490 print!("{}", (*chars::BAR_END).cyan());
491
492 let _ = stdout.queue(cursor::MoveToPreviousLine(1));
493 let _ = stdout.flush();
494
495 print!("{} ", (*chars::BAR).cyan());
496 let _ = stdout.flush();
497 }
498
499 fn w_val(&self, text: &str) {
500 let mut stdout = stdout();
501 let _ = stdout.queue(cursor::MoveToPreviousLine(2));
502 let _ = stdout.flush();
503
504 println!("{} {}", (*chars::STEP_ERROR).yellow(), self.message);
505 println!("{}", (*chars::BAR).yellow());
506
507 print!("{}", ansi::CLEAR_LINE);
508 print!("{} {}", (*chars::BAR_END).yellow(), text.yellow());
509
510 let _ = stdout.queue(cursor::MoveToPreviousLine(1));
511 let _ = stdout.flush();
512 }
513
514 fn w_out<D: Display>(&self, value: D) {
515 let mut stdout = stdout();
516 let _ = stdout.queue(cursor::MoveToPreviousLine(2));
517 let _ = stdout.flush();
518
519 println!("{} {}", (*chars::STEP_SUBMIT).green(), self.message);
520 print!("{}", ansi::CLEAR_LINE);
521 println!("{} {}", *chars::BAR, value.dimmed());
522
523 print!("{}", ansi::CLEAR_LINE);
524 }
525
526 fn w_cancel(&self) {
527 let mut stdout = stdout();
528 let _ = stdout.queue(cursor::MoveToPreviousLine(2));
529 let _ = stdout.flush();
530
531 println!("{} {}", (*chars::STEP_CANCEL).red(), self.message);
532
533 print!("{}", ansi::CLEAR_LINE);
534 println!("{} {}", *chars::BAR, "cancelled".strikethrough().dimmed());
535
536 print!("{}", ansi::CLEAR_LINE);
537 }
538}
539
540pub fn input<M: Display>(message: M) -> Input<M> {
542 Input::new(message)
543}