1use super::input::{PlaceholderHighlighter, ValidateFn};
4use crate::{
5 error::ClackError,
6 style::{ansi, chars},
7};
8use crossterm::{cursor, QueueableCommand};
9use owo_colors::OwoColorize;
10use rustyline::Editor;
11use std::{
12 borrow::Cow,
13 error::Error,
14 fmt::Display,
15 io::{stdout, Write},
16 str::FromStr,
17};
18
19pub struct MultiInput<M: Display> {
48 message: M,
49 initial_value: Option<String>,
50 placeholder: Option<String>,
51 validate: Option<Box<ValidateFn>>,
52 cancel: Option<Box<dyn Fn()>>,
53 min: u16,
54 max: u16,
55}
56
57impl<M: Display> MultiInput<M> {
58 pub fn new(message: M) -> Self {
72 MultiInput {
73 message,
74 validate: None,
75 initial_value: None,
76 placeholder: None,
77 cancel: None,
78 min: 1,
79 max: u16::MAX,
80 }
81 }
82
83 pub fn initial_value<S: ToString>(&mut self, initial_value: S) -> &mut Self {
99 self.initial_value = Some(initial_value.to_string());
100 self
101 }
102
103 pub fn placeholder<S: ToString>(&mut self, placeholder: S) -> &mut Self {
119 self.placeholder = Some(placeholder.to_string());
120 self
121 }
122
123 pub fn min(&mut self, min: u16) -> &mut Self {
135 self.min = min;
136 self
137 }
138
139 pub fn max(&mut self, max: u16) -> &mut Self {
154 self.max = max;
155 self
156 }
157
158 pub fn validate<F>(&mut self, validate: F) -> &mut Self
184 where
185 F: Fn(&str) -> Result<(), Cow<'static, str>> + 'static,
186 {
187 let validate = Box::new(validate);
188 self.validate = Some(validate);
189 self
190 }
191
192 fn do_validate(&self, input: &str) -> Result<(), Cow<'static, str>> {
193 if let Some(validate) = self.validate.as_deref() {
194 validate(input)
195 } else {
196 Ok(())
197 }
198 }
199
200 pub fn cancel<F>(&mut self, cancel: F) -> &mut Self
218 where
219 F: Fn() + 'static,
220 {
221 let cancel = Box::new(cancel);
222 self.cancel = Some(cancel);
223 self
224 }
225
226 fn interact_once<T: FromStr>(
227 &self,
228 enforce_non_empty: bool,
229 amt: u16,
230 ) -> Result<Option<T>, ClackError>
231 where
232 T::Err: Error,
233 {
234 let prompt = format!("{} ", *chars::BAR);
235 let mut editor = Editor::new()?;
236
237 let highlighter = PlaceholderHighlighter::new(self.placeholder.as_deref());
238 editor.set_helper(Some(highlighter));
239
240 let mut initial_value = self.initial_value.as_deref().map(Cow::Borrowed);
241 loop {
242 let line = if let Some(ref init) = initial_value {
243 editor.readline_with_initial(&prompt, (init, ""))
244 } else {
245 editor.readline(&prompt)
246 };
247
248 if let Ok(value) = line {
250 if value.is_empty() {
251 if enforce_non_empty {
252 initial_value = None;
253
254 if let Some(helper) = editor.helper_mut() {
255 helper.is_val = true;
256 }
257
258 let text = format!("minimum {}", self.min);
259 self.w_val(&text, amt);
260 } else {
261 break Ok(None);
262 }
263 } else if let Err(text) = self.do_validate(&value) {
264 initial_value = Some(Cow::Owned(value));
265
266 if let Some(helper) = editor.helper_mut() {
267 helper.is_val = true;
268 }
269
270 self.w_val(&text, amt);
271 } else {
272 match value.parse::<T>() {
273 Ok(value) => break Ok(Some(value)),
274 Err(err) => {
275 initial_value = Some(Cow::Owned(value));
276
277 if let Some(helper) = editor.helper_mut() {
278 helper.is_val = true;
279 }
280
281 self.w_val(&err.to_string(), amt);
282 }
283 }
284 }
286 } else {
287 break Err(ClackError::Cancelled);
288 }
289 }
290 }
291
292 pub fn parse<T: FromStr + Display>(&self) -> Result<Vec<T>, ClackError>
307 where
308 T::Err: Error,
309 {
310 self.w_init();
311
312 let mut v = vec![];
313 loop {
314 let amt = v.len() as u16;
315
316 let enforce_non_empty = amt < self.min;
317 let once = self.interact_once::<T>(enforce_non_empty, amt);
318
319 match once {
320 Ok(Some(value)) => {
321 self.w_line(&value, amt);
322 v.push(value);
323
324 if v.len() as u16 == self.max {
325 println!();
326 self.w_out(&v);
327 break;
328 }
329 }
330 Ok(None) => {
331 self.w_out(&v);
332 break;
333 }
334 Err(ClackError::Cancelled) => {
335 self.w_cancel(v.len());
336 if let Some(cancel) = self.cancel.as_deref() {
337 cancel();
338 }
339
340 return Err(ClackError::Cancelled);
341 }
342 Err(err) => return Err(err),
343 }
344 }
345
346 Ok(v)
347 }
348
349 pub fn interact(&self) -> Result<Vec<String>, ClackError> {
380 self.w_init();
381
382 let mut v = vec![];
383 loop {
384 let amt = v.len() as u16;
385
386 let enforce_non_empty = amt < self.min;
387 let once = self.interact_once::<String>(enforce_non_empty, amt);
388
389 match once {
390 Ok(Some(value)) => {
391 self.w_line(&value, amt);
392 v.push(value);
393
394 if v.len() as u16 == self.max {
395 println!();
396 self.w_out(&v);
397 break;
398 }
399 }
400 Ok(None) => {
401 self.w_out(&v);
402 break;
403 }
404 Err(ClackError::Cancelled) => {
405 self.w_cancel(v.len());
406 if let Some(cancel) = self.cancel.as_deref() {
407 cancel();
408 }
409
410 return Err(ClackError::Cancelled);
411 }
412 Err(err) => return Err(err),
413 }
414 }
415
416 Ok(v)
417 }
418}
419
420impl<M: Display> MultiInput<M> {
421 fn w_init(&self) {
422 let mut stdout = stdout();
423
424 println!("{}", *chars::BAR);
425 println!("{} {}", (*chars::STEP_ACTIVE).cyan(), self.message);
426 println!("{}", (*chars::BAR).cyan());
427 print!("{}", (*chars::BAR_END).cyan());
428
429 let _ = stdout.queue(cursor::MoveToPreviousLine(1));
430 let _ = stdout.flush();
431
432 print!("{} ", (*chars::BAR).cyan());
433 let _ = stdout.flush();
434 }
435
436 fn w_line<V: Display>(&self, value: V, amt: u16) {
437 let mut stdout = stdout();
438 let _ = stdout.queue(cursor::MoveToPreviousLine(amt + 2));
439 let _ = stdout.flush();
440
441 println!("{} {}", (*chars::STEP_ACTIVE).cyan(), self.message);
442
443 for _ in 0..amt {
444 println!("{}", (*chars::BAR).cyan());
445 }
446
447 println!("{} {}", (*chars::BAR).cyan(), value.dimmed());
448 println!("{}", (*chars::BAR).cyan());
449
450 print!("{}", ansi::CLEAR_LINE);
451 print!("{}", (*chars::BAR_END).cyan());
452
453 let _ = stdout.queue(cursor::MoveToPreviousLine(1));
454 let _ = stdout.flush();
455 }
456
457 fn w_val(&self, text: &str, amt: u16) {
458 let mut stdout = stdout();
459 let _ = stdout.queue(cursor::MoveToPreviousLine(amt + 2));
460 let _ = stdout.flush();
461
462 println!("{} {}", (*chars::STEP_ERROR).yellow(), self.message);
463
464 for _ in 0..=amt {
465 println!("{}", (*chars::BAR).yellow());
466 }
467
468 print!("{}", ansi::CLEAR_LINE);
469 print!("{} {}", (*chars::BAR_END).yellow(), text.yellow());
470
471 let _ = stdout.queue(cursor::MoveToPreviousLine(1));
472 let _ = stdout.flush();
473 }
474
475 fn w_out<V: Display>(&self, values: &[V]) {
476 let amt = values.len();
477
478 let mut stdout = stdout();
479 let _ = stdout.queue(cursor::MoveToPreviousLine(amt as u16 + 2));
480 let _ = stdout.flush();
481
482 println!("{} {}", (*chars::STEP_SUBMIT).green(), self.message);
483
484 if amt == 0 {
485 println!("{}", *chars::BAR);
486 }
487
488 for val in values {
489 println!("{} {}", *chars::BAR, val.dimmed());
490 }
491
492 println!("{}", ansi::CLEAR_LINE);
493 println!("{}", ansi::CLEAR_LINE);
494
495 let _ = stdout.queue(cursor::MoveToPreviousLine(2));
496 let _ = stdout.flush();
497 }
498
499 fn w_cancel(&self, amt: usize) {
500 let mut stdout = stdout();
501 let _ = stdout.queue(cursor::MoveToPreviousLine(1));
502 let _ = stdout.flush();
503
504 print!("{}", ansi::CLEAR_LINE);
505 println!("{} {}", *chars::BAR, "cancelled".strikethrough().dimmed());
506
507 print!("{}", ansi::CLEAR_LINE);
508
509 let _ = stdout.queue(cursor::MoveToPreviousLine(amt as u16 + 2));
510 let _ = stdout.flush();
511
512 println!("{} {}", (*chars::STEP_CANCEL).red(), self.message);
513
514 for _ in 0..amt {
515 println!("{}", *chars::BAR);
516 }
517
518 let _ = stdout.queue(cursor::MoveToNextLine(1));
519 let _ = stdout.flush();
520 }
521}
522
523pub fn multi_input<M: Display>(message: M) -> MultiInput<M> {
525 MultiInput::new(message)
526}