1use crate::{
4 error::ClackError,
5 outro,
6 style::{ansi, chars},
7};
8use crossterm::{QueueableCommand, cursor};
9use owo_colors::OwoColorize;
10use rustyline::{
11 Editor, Helper,
12 completion::Completer,
13 highlight::{CmdKind, Highlighter},
14 hint::Hinter,
15 validate::Validator,
16};
17use std::{
18 borrow::{Borrow, Cow},
19 error::Error,
20 fmt::Display,
21 io::{Write, stdout},
22 str::FromStr,
23};
24
25pub(super) struct PlaceholderHighlighter<'a> {
26 placeholder: Option<&'a str>,
27 pub is_val: bool,
28}
29
30impl Completer for PlaceholderHighlighter<'_> {
31 type Candidate = String;
32}
33
34impl Hinter for PlaceholderHighlighter<'_> {
35 type Hint = String;
36}
37
38impl Helper for PlaceholderHighlighter<'_> {}
39impl Validator for PlaceholderHighlighter<'_> {}
40
41impl<'a> PlaceholderHighlighter<'a> {
42 pub fn new(placeholder: Option<&'a str>) -> Self {
43 PlaceholderHighlighter {
44 placeholder,
45 is_val: false,
46 }
47 }
48}
49
50impl Highlighter for PlaceholderHighlighter<'_> {
51 fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
52 if let Some(placeholder) = self.placeholder {
53 if line.is_empty() {
54 Cow::Owned(placeholder.dimmed().to_string())
55 } else {
56 Cow::Borrowed(line)
57 }
58 } else {
59 Cow::Borrowed(line)
60 }
61 }
62
63 fn highlight_char(&self, _line: &str, _pos: usize, _kind: CmdKind) -> bool {
64 true
65 }
66
67 fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
68 &'s self,
69 prompt: &'p str,
70 default: bool,
71 ) -> Cow<'b, str> {
72 if !default {
73 Cow::Borrowed(prompt)
75 } else if self.is_val {
76 Cow::Owned(prompt.yellow().to_string())
77 } else {
78 Cow::Owned(prompt.cyan().to_string())
79 }
80 }
81}
82
83pub(super) type ValidateFn = dyn Fn(&str) -> Result<(), Cow<'static, str>>;
84
85pub struct Input<M: Display> {
109 message: M,
110 initial_value: Option<String>,
111 placeholder: Option<String>,
112 validate: Option<Box<ValidateFn>>,
113 cancel: Option<Box<dyn Fn()>>,
114}
115
116impl<M: Display> Input<M> {
117 pub fn new(message: M) -> Self {
131 Input {
132 message,
133 initial_value: None,
134 placeholder: None,
135 validate: None,
136 cancel: None,
137 }
138 }
139
140 pub fn placeholder<S: ToString>(&mut self, placeholder: S) -> &mut Self {
154 self.placeholder = Some(placeholder.to_string());
155 self
156 }
157
158 pub fn maybe_initial<T: Borrow<Option<S>>, S: ToString>(&mut self, initial: T) -> &mut Self {
172 if let Some(initial) = initial.borrow() {
173 self.initial_value = Some(initial.to_string());
174 }
175
176 self
177 }
178
179 pub fn initial_value<S: ToString>(&mut self, initial_value: S) -> &mut Self {
193 self.initial_value = Some(initial_value.to_string());
194 self
195 }
196
197 pub fn validate<F>(&mut self, validate: F) -> &mut Self
221 where
222 F: Fn(&str) -> Result<(), Cow<'static, str>> + 'static,
223 {
224 let validate = Box::new(validate);
225 self.validate = Some(validate);
226 self
227 }
228
229 fn do_validate(&self, input: &str) -> Result<(), Cow<'static, str>> {
230 if let Some(validate) = self.validate.as_deref() {
231 validate(input)
232 } else {
233 Ok(())
234 }
235 }
236
237 pub fn cancel<F>(&mut self, cancel: F) -> &mut Self
255 where
256 F: Fn() + 'static,
257 {
258 let cancel = Box::new(cancel);
259 self.cancel = Some(cancel);
260 self
261 }
262
263 fn interact_once<T: FromStr>(&self, enforce_non_empty: bool) -> Result<Option<T>, ClackError>
264 where
265 T::Err: Error,
266 {
267 let prompt = format!("{} ", *chars::BAR);
268
269 let mut editor = Editor::new()?;
270 let helper = PlaceholderHighlighter::new(self.placeholder.as_deref());
271 editor.set_helper(Some(helper));
272
273 let mut initial_value = self.initial_value.as_deref().map(Cow::Borrowed);
274 loop {
275 let line = if let Some(ref init) = initial_value {
276 editor.readline_with_initial(&prompt, (init, ""))
277 } else {
278 editor.readline(&prompt)
279 };
280
281 if let Ok(value) = line {
283 if value.is_empty() {
284 if enforce_non_empty {
285 initial_value = None;
286
287 if let Some(helper) = editor.helper_mut() {
288 helper.is_val = true;
289 }
290
291 self.w_val("value is required");
292 } else {
293 break Ok(None);
294 }
295 } else if let Err(text) = self.do_validate(&value) {
296 initial_value = Some(Cow::Owned(value));
297
298 if let Some(helper) = editor.helper_mut() {
299 helper.is_val = true;
300 }
301
302 self.w_val(&text);
303 } else {
304 match value.parse::<T>() {
305 Ok(val) => break Ok(Some(val)),
306 Err(err) => {
307 initial_value = Some(Cow::Owned(value));
308
309 if let Some(helper) = editor.helper_mut() {
310 helper.is_val = true;
311 }
312
313 self.w_val(&err.to_string());
314 }
315 }
316 }
317 } else {
318 break Err(ClackError::Cancelled);
319 }
320 }
321 }
322
323 pub fn parse<T: FromStr + Display>(&self) -> Result<T, ClackError>
339 where
340 T::Err: Error,
341 {
342 self.w_init();
343
344 let interact = self.interact_once::<T>(true);
345 match interact {
346 Ok(Some(value)) => {
347 self.w_out(&value);
348 Ok(value)
349 }
350 Ok(None) => unreachable!(),
351 Err(ClackError::Cancelled) => {
352 self.w_cancel();
353 if let Some(cancel) = self.cancel.as_deref() {
354 cancel();
355 } else {
356 outro!();
357 }
358
359 Err(ClackError::Cancelled)
360 }
361 Err(err) => Err(err),
362 }
363 }
364
365 pub fn maybe_parse<T: FromStr + Display>(&self) -> Result<Option<T>, ClackError>
378 where
379 T::Err: Error,
380 {
381 self.w_init();
382
383 let interact = self.interact_once::<T>(false);
384 match interact {
385 Ok(val) => {
386 if let Some(val) = &val {
387 self.w_out(val);
388 } else {
389 self.w_out("");
390 }
391
392 Ok(val)
393 }
394 Err(ClackError::Cancelled) => {
395 self.w_cancel();
396 if let Some(cancel) = self.cancel.as_deref() {
397 cancel();
398 } else {
399 outro!();
400 }
401
402 Err(ClackError::Cancelled)
403 }
404 Err(err) => Err(err),
405 }
406 }
407
408 pub fn required(&self) -> Result<String, ClackError> {
422 self.w_init();
423
424 let interact = self.interact_once::<String>(true);
425 match interact {
426 Ok(Some(value)) => {
427 self.w_out(&value);
428 Ok(value)
429 }
430 Ok(None) => unreachable!(),
431 Err(ClackError::Cancelled) => {
432 self.w_cancel();
433 if let Some(cancel) = self.cancel.as_deref() {
434 cancel();
435 } else {
436 outro!();
437 }
438
439 Err(ClackError::Cancelled)
440 }
441 Err(err) => Err(err),
442 }
443 }
444
445 pub fn interact(&self) -> Result<Option<String>, ClackError> {
469 self.w_init();
470
471 let interact = self.interact_once(false);
472 match interact {
473 Ok(val) => {
474 let v = val.as_deref().unwrap_or("");
475 self.w_out(v);
476 Ok(val)
477 }
478 Err(ClackError::Cancelled) => {
479 self.w_cancel();
480 if let Some(cancel) = self.cancel.as_deref() {
481 cancel();
482 } else {
483 outro!();
484 }
485
486 Err(ClackError::Cancelled)
487 }
488 Err(err) => Err(err),
489 }
490 }
491}
492
493impl<M: Display> Input<M> {
494 fn w_init(&self) {
495 let mut stdout = stdout();
496
497 println!("{}", *chars::BAR);
498 println!("{} {}", (*chars::STEP_ACTIVE).cyan(), self.message);
499 println!("{}", (*chars::BAR).cyan());
500 print!("{}", (*chars::BAR_END).cyan());
501
502 let _ = stdout.queue(cursor::MoveToPreviousLine(1));
503 let _ = stdout.flush();
504
505 print!("{} ", (*chars::BAR).cyan());
506 let _ = stdout.flush();
507 }
508
509 fn w_val(&self, text: &str) {
510 let mut stdout = stdout();
511 let _ = stdout.queue(cursor::MoveToPreviousLine(2));
512 let _ = stdout.flush();
513
514 println!("{} {}", (*chars::STEP_ERROR).yellow(), self.message);
515 println!("{}", (*chars::BAR).yellow());
516
517 print!("{}", ansi::CLEAR_LINE);
518 print!("{} {}", (*chars::BAR_END).yellow(), text.yellow());
519
520 let _ = stdout.queue(cursor::MoveToPreviousLine(1));
521 let _ = stdout.flush();
522 }
523
524 fn w_out<D: Display>(&self, value: D) {
525 let mut stdout = stdout();
526 let _ = stdout.queue(cursor::MoveToPreviousLine(2));
527 let _ = stdout.flush();
528
529 println!("{} {}", (*chars::STEP_SUBMIT).green(), self.message);
530 print!("{}", ansi::CLEAR_LINE);
531 println!("{} {}", *chars::BAR, value.dimmed());
532
533 print!("{}", ansi::CLEAR_LINE);
534 }
535
536 fn w_cancel(&self) {
537 let mut stdout = stdout();
538 let _ = stdout.queue(cursor::MoveToPreviousLine(2));
539 let _ = stdout.flush();
540
541 println!("{} {}", (*chars::STEP_CANCEL).red(), self.message);
542
543 print!("{}", ansi::CLEAR_LINE);
544 println!("{} {}", *chars::BAR, "cancelled".strikethrough().dimmed());
545
546 print!("{}", ansi::CLEAR_LINE);
547 }
548}
549
550pub fn input<M: Display>(message: M) -> Input<M> {
552 Input::new(message)
553}