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