Skip to main content

may_clack/
multi_select.rs

1//! Select multiple options
2
3use crate::{
4	error::ClackError,
5	outro,
6	style::{IS_UNICODE, ansi, chars},
7};
8use crossterm::{
9	cursor,
10	event::{self, Event, KeyCode, KeyEventKind, KeyModifiers},
11	execute, terminal,
12};
13use owo_colors::OwoColorize;
14use std::{
15	fmt::Display,
16	io::{Write, stdout},
17};
18use unicode_truncate::UnicodeTruncateStr;
19
20/// `MultiSelect` `Opt` struct
21#[derive(Debug, Clone)]
22pub struct Opt<T: Clone, O: Display + Clone> {
23	value: T,
24	label: O,
25	hint: Option<String>,
26	active: bool,
27}
28
29impl<T: Clone, O: Display + Clone> Opt<T, O> {
30	/// Creates a new `Opt` struct.
31	///
32	/// # Examples
33	///
34	/// ```
35	/// use may_clack::multi_select::Opt;
36	///
37	/// let option = Opt::new("value", "lavel", Some("hint"));
38	/// ```
39	pub fn new<S: ToString>(value: T, label: O, hint: Option<S>) -> Self {
40		Opt {
41			value,
42			label,
43			hint: hint.map(|hint| hint.to_string()),
44			active: false,
45		}
46	}
47
48	/// Creates a new `Opt` struct without a hint
49	///
50	/// # Examples
51	///
52	/// ```
53	/// use may_clack::multi_select::Opt;
54	///
55	/// let option = Opt::simple("value", "label");
56	/// ```
57	pub fn simple(value: T, label: O) -> Self {
58		Opt::new(value, label, None::<String>)
59	}
60
61	/// Creates a new `Opt` struct with a hint.
62	///
63	/// # Examples
64	///
65	/// ```
66	/// use may_clack::multi_select::Opt;
67	///
68	/// let option = Opt::hint("value", "label", "hint");
69	/// ```
70	pub fn hint<S: ToString>(value: T, label: O, hint: S) -> Self {
71		Opt::new(value, label, Some(hint))
72	}
73
74	fn toggle(&mut self) {
75		self.active = !self.active;
76	}
77
78	fn trunc(&self, hint: usize) -> String {
79		let size = crossterm::terminal::size();
80		let label = format!("{}", self.label);
81
82		let one_three = if *IS_UNICODE { 1 } else { 3 };
83
84		match size {
85			Ok((width, _height)) => label
86				.unicode_truncate(width as usize - 4 - one_three - hint)
87				.0
88				.to_owned(),
89			Err(_) => label,
90		}
91	}
92
93	fn focus(&self) -> String {
94		let hint_len = self.hint.as_deref().map_or(0, |hint| hint.len() + 3);
95		let label = self.trunc(hint_len);
96
97		let fmt = if self.active {
98			format!("{} {}", (*chars::CHECKBOX_SELECTED).green(), label)
99		} else {
100			format!("{} {}", (*chars::CHECKBOX_ACTIVE).cyan(), label)
101		};
102
103		if let Some(hint) = &self.hint {
104			let hint = format!("({hint})");
105			format!("{} {}", fmt, hint.dimmed())
106		} else {
107			fmt
108		}
109	}
110
111	fn unfocus(&self) -> String {
112		let label = self.trunc(0);
113
114		if self.active {
115			format!("{} {}", (*chars::CHECKBOX_SELECTED).green(), label.dimmed())
116		} else {
117			format!(
118				"{} {}",
119				(*chars::CHECKBOX_INACTIVE).dimmed(),
120				label.dimmed()
121			)
122		}
123	}
124}
125
126/// `MultiSelect` struct
127///
128/// # Examples
129///
130/// ```no_run
131/// use may_clack::multi_select;
132///
133/// # fn main() -> Result<(), may_clack::error::ClackError> {
134/// let answer = multi_select("select")
135///     .option("val1", "value 1")
136///     .option("val2", "value 2")
137///     .option_hint("val 3", "value 3", "hint")
138///     .interact()?;
139/// println!("answer {:?}", answer);
140/// # Ok(())
141/// # }
142/// ```
143pub struct MultiSelect<M: Display, T: Clone, O: Display + Clone> {
144	message: M,
145	less: bool,
146	less_amt: Option<u16>,
147	less_max: Option<u16>,
148	cancel: Option<Box<dyn Fn()>>,
149	options: Vec<Opt<T, O>>,
150}
151
152impl<M: Display, T: Clone, O: Display + Clone> MultiSelect<M, T, O> {
153	/// Creates a new `MultiSelect` struct.
154	///
155	/// Has a shorthand version in [`multi_select()`]
156	///
157	/// # Examples
158	///
159	/// ```no_run
160	/// use may_clack::{multi_select, multi_select::MultiSelect};
161	///
162	/// // these two are equivalent
163	/// let mut question = MultiSelect::new("message");
164	/// question.option("value", "hint");
165	///
166	/// let mut question = multi_select("message");
167	/// question.option("value", "hint");
168	/// ```
169	pub fn new(message: M) -> Self {
170		MultiSelect {
171			message,
172			less: false,
173			less_amt: None,
174			less_max: None,
175			cancel: None,
176			options: vec![],
177		}
178	}
179
180	/// Add an option without a hint.
181	///
182	/// # Examples
183	///
184	/// ```no_run
185	/// use may_clack::multi_select;
186	///
187	/// # fn main() -> Result<(), may_clack::error::ClackError> {
188	/// let answer = multi_select("message")
189	///     .option("val1", "label 1")
190	///     .option("val2", "label 2")
191	///     .interact()?;
192	/// println!("answer {:?}", answer);
193	/// # Ok(())
194	/// # }
195	/// ```
196	pub fn option(&mut self, val: T, label: O) -> &mut Self {
197		let opt = Opt::new(val, label, None::<String>);
198		self.options.push(opt);
199		self
200	}
201
202	/// Add an option with a hint.
203	///
204	/// # Examples
205	///
206	/// ```no_run
207	/// use may_clack::multi_select;
208	///
209	/// # fn main() -> Result<(), may_clack::error::ClackError> {
210	/// let answer = multi_select("message")
211	///     .option("val1", "label 1")
212	///     .option_hint("val2", "label 2", "hint")
213	///     .option("val3", "label 3")
214	///     .interact()?;
215	/// println!("answer {:?}", answer);
216	/// # Ok(())
217	/// # }
218	/// ```
219	pub fn option_hint<S: ToString>(&mut self, val: T, label: O, hint: S) -> &mut Self {
220		let opt = Opt::new(val, label, Some(hint));
221		self.options.push(opt);
222		self
223	}
224
225	/// Add multiple options.
226	///
227	/// # Examples
228	///
229	/// ```no_run
230	/// use may_clack::{multi_select, multi_select::Opt};
231	///
232	/// # fn main() -> Result<(), may_clack::error::ClackError> {
233	/// let opts = vec![
234	///     Opt::simple("val1", "label 1"),
235	///     Opt::hint("val2", "label 2", "hint"),
236	///     Opt::simple("val3", "label 3"),
237	/// ];
238	///
239	/// let answer = multi_select("message").options(opts).interact()?;
240	/// println!("answer {:?}", answer);
241	/// # Ok(())
242	/// # }
243	/// ```
244	pub fn options(&mut self, options: Vec<Opt<T, O>>) -> &mut Self {
245		self.options = options;
246		self
247	}
248
249	/// Enable paging with the amount of terminal rows.
250	///
251	/// # Examples
252	///
253	/// ```no_run
254	/// use may_clack::multi_select;
255	///
256	/// # fn main() -> Result<(), may_clack::error::ClackError> {
257	/// let answer = multi_select("message")
258	///     .option("val 1", "value 1")
259	///     .option("val 2", "value 2")
260	///     .option_hint("val 3", "value 3", "hint")
261	///     .option("val 4", "value 4")
262	///     .option("val 5", "value 5")
263	///     .less()
264	///     .interact()?;
265	/// println!("answer {:?}", answer);
266	/// # Ok(())
267	/// # }
268	/// ```
269	pub fn less(&mut self) -> &mut Self {
270		self.less = true;
271		self
272	}
273
274	/// Enable paging with the amount of terminal rows, additionally setting a maximum amount.
275	///
276	/// # Panics
277	///
278	/// Panics when the given value is 0.  
279	/// Panics when called after [`MultiSelect::less_amt`] has already been called.
280	///
281	/// # Examples
282	///
283	/// ```no_run
284	/// use may_clack::multi_select;
285	///
286	/// # fn main() -> Result<(), may_clack::error::ClackError> {
287	/// let answer = multi_select("message")
288	///     .option("val 1", "value 1")
289	///     .option("val 2", "value 2")
290	///     .option_hint("val 3", "value 3", "hint")
291	///     .option("val 4", "value 4")
292	///     .option("val 5", "value 5")
293	///     .less_max(3)
294	///     .interact()?;
295	/// println!("answer {:?}", answer);
296	/// # Ok(())
297	/// # }
298	/// ```
299	pub fn less_max(&mut self, max: u16) -> &mut Self {
300		assert!(max > 0, "less max value has to be greater than zero");
301		assert!(
302			self.less_amt.is_none(),
303			"cannot set both less_amt and less_max"
304		);
305		self.less = true;
306		self.less_max = Some(max);
307		self
308	}
309
310	/// Enable paging with the specified amount of lines.
311	///
312	/// # Panics
313	///
314	/// Panics when the given value is 0.  
315	/// Panics when called after [`MultiSelect::less_max`] has already been called.
316	///
317	/// # Examples
318	///
319	/// ```no_run
320	/// use may_clack::multi_select;
321	///
322	/// # fn main() -> Result<(), may_clack::error::ClackError> {
323	/// let answer = multi_select("message")
324	///     .option("val 1", "value 1")
325	///     .option("val 2", "value 2")
326	///     .option_hint("val 3", "value 3", "hint")
327	///     .option("val 4", "value 4")
328	///     .option("val 5", "value 5")
329	///     .less_amt(3)
330	///     .interact()?;
331	/// println!("answer {:?}", answer);
332	/// # Ok(())
333	/// # }
334	/// ```
335	pub fn less_amt(&mut self, less: u16) -> &mut Self {
336		assert!(less > 0, "less value has to be greater than zero");
337		assert!(
338			self.less_amt.is_none(),
339			"cannot set both less_amt and less_max"
340		);
341		self.less = true;
342		self.less_amt = Some(less);
343		self
344	}
345
346	/// Specify function to call on cancel.
347	///
348	/// # Examples
349	///
350	/// ```no_run
351	/// use may_clack::{multi_select, cancel};
352	///
353	/// # fn main() -> Result<(), may_clack::error::ClackError> {
354	/// let answer = multi_select("select")
355	///     .option("val1", "value 1")
356	///     .option("val2", "value 2")
357	///     .option_hint("val 3", "value 3", "hint")
358	///     .cancel(do_cancel)
359	///     .interact()?;
360	/// println!("answer {:?}", answer);
361	/// # Ok(())
362	/// # }
363	///
364	/// fn do_cancel() {
365	///     cancel!("operation cancelled");
366	///     panic!("operation cancelled");
367	/// }
368	pub fn cancel<F>(&mut self, cancel: F) -> &mut Self
369	where
370		F: Fn() + 'static,
371	{
372		let cancel = Box::new(cancel);
373		self.cancel = Some(cancel);
374
375		self
376	}
377
378	fn mk_less(&self) -> Option<u16> {
379		if !self.less {
380			return None;
381		}
382
383		if let Some(less) = self.less_amt {
384			let is_less = self.options.len() > less as usize;
385			is_less.then_some(less)
386		} else if let Ok((_, rows)) = crossterm::terminal::size() {
387			let len = self.options.len();
388			let rows = rows.saturating_sub(4);
389			let rows = self.less_max.map_or(rows, |max| u16::min(rows, max));
390
391			let is_less = rows > 0 && len > rows as usize;
392			is_less.then_some(rows)
393		} else {
394			None
395		}
396	}
397
398	/// Wait for the user to submit the selected options.
399	///
400	/// # Examples
401	///
402	/// ```no_run
403	/// use may_clack::multi_select;
404	///
405	/// # fn main() -> Result<(), may_clack::error::ClackError> {
406	/// let answer = multi_select("select")
407	///     .option("val1", "value 1")
408	///     .option("val2", "value 2")
409	///     .option_hint("val 3", "value 3", "hint")
410	///     .interact()?;
411	/// println!("answer {:?}", answer);
412	/// # Ok(())
413	/// # }
414	/// ```
415	pub fn interact(&self) -> Result<Vec<T>, ClackError> {
416		if self.options.is_empty() {
417			return Err(ClackError::NoOptions);
418		}
419
420		let mut options = self.options.clone();
421
422		let max = self.options.len();
423		let is_less = self.mk_less();
424
425		let mut idx = 0;
426		let mut less_idx: u16 = 0;
427
428		if let Some(less) = is_less {
429			self.w_init_less(less);
430		} else {
431			self.w_init();
432		}
433
434		terminal::enable_raw_mode()?;
435
436		loop {
437			if let Event::Key(key) = event::read()?
438				&& key.kind == KeyEventKind::Press
439			{
440				match (key.code, key.modifiers) {
441					(KeyCode::Up | KeyCode::Left, _) => {
442						if let Some(less) = is_less {
443							let prev_less = less_idx;
444
445							if idx > 0 {
446								idx -= 1;
447								less_idx = less_idx.saturating_sub(1);
448							} else {
449								idx = max - 1;
450								less_idx = less - 1;
451							}
452
453							self.draw_less(&options, less, idx, less_idx, prev_less);
454						} else {
455							self.draw_unfocus(&options, idx);
456							let mut stdout = stdout();
457
458							if idx > 0 {
459								idx -= 1;
460								let _ = execute!(stdout, cursor::MoveUp(1));
461							} else if max > 1 {
462								idx = max - 1;
463								let _ = execute!(stdout, cursor::MoveDown(max as u16 - 1));
464							}
465
466							self.draw_focus(&options, idx);
467						}
468					}
469					(KeyCode::Down | KeyCode::Right, _) => {
470						if let Some(less) = is_less {
471							let prev_less = less_idx;
472
473							if idx < max - 1 {
474								idx += 1;
475								if less_idx < less - 1 {
476									less_idx += 1;
477								}
478							} else {
479								idx = 0;
480								less_idx = 0;
481							}
482
483							self.draw_less(&options, less, idx, less_idx, prev_less);
484						} else {
485							self.draw_unfocus(&options, idx);
486							let mut stdout = stdout();
487
488							if idx < max - 1 {
489								idx += 1;
490								let _ = execute!(stdout, cursor::MoveDown(1));
491							} else if idx > 0 {
492								idx = 0;
493								let _ = execute!(stdout, cursor::MoveUp(max as u16 - 1));
494							}
495
496							self.draw_focus(&options, idx);
497						}
498					}
499					(KeyCode::PageDown, _) => {
500						if let Some(less) = is_less {
501							let prev_less = less_idx;
502
503							if idx + less as usize >= max - 1 {
504								less_idx = less - 1;
505								idx = max - 1;
506							} else {
507								idx += less as usize;
508
509								if max - idx < (less - less_idx) as usize {
510									less_idx = less - (max - idx) as u16;
511								}
512							}
513
514							self.draw_less(&options, less, idx, less_idx, prev_less);
515						}
516					}
517					(KeyCode::PageUp, _) if idx != 0 => {
518						if let Some(less) = is_less {
519							let prev_less = less_idx;
520
521							if idx <= less as usize {
522								less_idx = 0;
523								idx = 0;
524							} else {
525								idx -= less as usize;
526								less_idx = prev_less.min(idx as u16);
527							}
528
529							self.draw_less(&options, less, idx, less_idx, prev_less);
530						}
531					}
532					(KeyCode::Home, _) if idx != 0 => {
533						if let Some(less) = is_less {
534							let prev_less = less_idx;
535
536							idx = 0;
537							less_idx = 0;
538
539							self.draw_less(&options, less, idx, less_idx, prev_less);
540						} else {
541							self.draw_unfocus(&options, idx);
542
543							let mut stdout = stdout();
544							let _ = execute!(stdout, cursor::MoveUp(idx as u16));
545
546							idx = 0;
547							self.draw_focus(&options, 0);
548						}
549					}
550					(KeyCode::End, _) if idx != max - 1 => {
551						if let Some(less) = is_less {
552							let prev_less = less_idx;
553
554							idx = max - 1;
555							less_idx = less - 1;
556
557							self.draw_less(&options, less, idx, less_idx, prev_less);
558						} else {
559							self.draw_unfocus(&options, idx);
560
561							let mut stdout = stdout();
562							let diff = max - idx - 1;
563							let _ = execute!(stdout, cursor::MoveDown(diff as u16));
564
565							idx = max - 1;
566
567							self.draw_focus(&options, idx);
568						}
569					}
570					(KeyCode::Char(' '), _) => {
571						let opt = options.get_mut(idx).expect("idx should always be in bound");
572						opt.toggle();
573						self.draw_focus(&options, idx);
574					}
575					(KeyCode::Enter, _) => {
576						terminal::disable_raw_mode()?;
577
578						let selected_opts =
579							options.iter().filter(|opt| opt.active).collect::<Vec<_>>();
580
581						if let Some(less) = is_less {
582							self.w_out_less(less, less_idx, &selected_opts);
583						} else {
584							self.w_out(idx, &selected_opts);
585						}
586
587						let all = options
588							.into_iter()
589							.filter(|opt| opt.active)
590							.map(|opt| opt.value)
591							.collect();
592
593						return Ok(all);
594					}
595					(KeyCode::Char('c' | 'd'), KeyModifiers::CONTROL) => {
596						terminal::disable_raw_mode()?;
597
598						if let Some(less) = is_less {
599							self.w_cancel_less(less, idx, less_idx);
600						} else {
601							self.w_cancel(idx);
602						}
603
604						if let Some(cancel) = self.cancel.as_deref() {
605							cancel();
606						} else {
607							outro!();
608						}
609
610						return Err(ClackError::Cancelled);
611					}
612					_ => {}
613				}
614			}
615		}
616	}
617}
618
619impl<M: Display, T: Clone, O: Display + Clone> MultiSelect<M, T, O> {
620	fn draw_focus(&self, options: &[Opt<T, O>], idx: usize) {
621		let opt = options.get(idx).expect("idx should always be in bound");
622		let line = opt.focus();
623		self.draw(&line);
624	}
625
626	fn draw_unfocus(&self, options: &[Opt<T, O>], idx: usize) {
627		let opt = options.get(idx).expect("idx should always be in bound");
628		let line = opt.unfocus();
629		self.draw(&line);
630	}
631
632	fn draw(&self, line: &str) {
633		let mut stdout = stdout();
634		let _ = execute!(stdout, cursor::MoveToColumn(0));
635
636		print!("{}", ansi::CLEAR_LINE);
637		print!("{}  {}", (*chars::BAR).cyan(), line);
638		let _ = stdout.flush();
639	}
640
641	fn draw_less(&self, opts: &[Opt<T, O>], less: u16, idx: usize, less_idx: u16, prev_less: u16) {
642		let mut stdout = stdout();
643		if prev_less > 0 {
644			let _ = execute!(stdout, cursor::MoveToPreviousLine(prev_less));
645		} else {
646			let _ = execute!(stdout, cursor::MoveToColumn(0));
647		}
648
649		for i in 0..less.into() {
650			let i_idx = idx + i - less_idx as usize;
651			let opt = opts.get(i_idx).expect("i_idx should always be in bound");
652			let line = opt.unfocus();
653
654			print!("{}", ansi::CLEAR_LINE);
655			println!("{}  {}\r", (*chars::BAR).cyan(), line);
656
657			let _ = execute!(stdout, cursor::MoveToColumn(0));
658		}
659
660		let max = self.options.len();
661		let amt = max.to_string().len();
662		print!("{}", ansi::CLEAR_LINE);
663		println!(
664			"{}  ......... ({:#0amt$}/{})",
665			(*chars::BAR).cyan(),
666			idx + 1,
667			max,
668			amt = amt
669		);
670
671		let _ = execute!(stdout, cursor::MoveToPreviousLine(less + 1));
672		if less_idx > 0 {
673			let _ = execute!(stdout, cursor::MoveToNextLine(less_idx));
674		}
675
676		self.draw_focus(opts, idx);
677	}
678}
679
680impl<M: Display, T: Clone, O: Display + Clone> MultiSelect<M, T, O> {
681	fn w_init(&self) {
682		let mut stdout = stdout();
683
684		println!("{}", *chars::BAR);
685		println!("{}  {}", (*chars::STEP_ACTIVE).cyan(), self.message);
686
687		for opt in &self.options {
688			let line = opt.unfocus();
689			println!("{}  {}", (*chars::BAR).cyan(), line);
690		}
691
692		print!("{}", (*chars::BAR_END).cyan());
693
694		let len = self.options.len() as u16;
695		let _ = execute!(stdout, cursor::MoveToPreviousLine(len));
696
697		self.draw_focus(&self.options, 0);
698	}
699
700	fn w_init_less(&self, less: u16) {
701		println!("{}", *chars::BAR);
702		println!("{}  {}", (*chars::STEP_ACTIVE).cyan(), self.message);
703
704		self.draw_less(&self.options, less, 0, 0, 0);
705
706		let mut stdout = stdout();
707		let _ = execute!(stdout, cursor::MoveToNextLine(less));
708
709		println!();
710		print!("{}", (*chars::BAR_END).cyan());
711
712		let _ = execute!(stdout, cursor::MoveToPreviousLine(less + 1));
713
714		self.draw_focus(&self.options, 0);
715	}
716
717	fn w_cancel(&self, idx: usize) {
718		let mut stdout = stdout();
719		let _ = execute!(stdout, cursor::MoveToPreviousLine(idx as u16 + 1));
720
721		println!("{}  {}", (*chars::STEP_CANCEL).red(), self.message);
722
723		for _ in &self.options {
724			println!("{}", ansi::CLEAR_LINE);
725		}
726		print!("{}", ansi::CLEAR_LINE);
727
728		let len = self.options.len() as u16;
729		let _ = execute!(stdout, cursor::MoveToPreviousLine(len));
730
731		let label = &self
732			.options
733			.get(idx)
734			.expect("idx should always be in bound")
735			.label;
736		println!("{}  {}", *chars::BAR, label.strikethrough().dimmed());
737	}
738
739	fn w_cancel_less(&self, less: u16, idx: usize, less_idx: u16) {
740		let mut stdout = stdout();
741		if less_idx > 0 {
742			let _ = execute!(stdout, cursor::MoveToPreviousLine(less_idx + 1));
743		} else {
744			let _ = execute!(stdout, cursor::MoveToPreviousLine(1));
745		}
746
747		println!("{}  {}", (*chars::STEP_CANCEL).red(), self.message);
748
749		for _ in 0..less.into() {
750			println!("{}", ansi::CLEAR_LINE);
751		}
752
753		println!("{}", ansi::CLEAR_LINE);
754		println!("{}", ansi::CLEAR_LINE);
755
756		let mv = less + 2;
757		let _ = execute!(stdout, cursor::MoveToPreviousLine(mv));
758
759		let label = &self
760			.options
761			.get(idx)
762			.expect("idx should always be in bound")
763			.label;
764		println!("{}  {}", *chars::BAR, label.strikethrough().dimmed());
765	}
766
767	fn w_out(&self, idx: usize, selected: &[&Opt<T, O>]) {
768		let mut stdout = stdout();
769		let _ = execute!(stdout, cursor::MoveToPreviousLine(idx as u16 + 1));
770
771		println!("{}  {}", (*chars::STEP_SUBMIT).green(), self.message);
772
773		for _ in &self.options {
774			println!("{}", ansi::CLEAR_LINE);
775		}
776		println!("{}", ansi::CLEAR_LINE);
777
778		let mv = self.options.len() as u16 + 1;
779		let _ = execute!(stdout, cursor::MoveToPreviousLine(mv));
780
781		let vals = selected.iter().map(|&opt| &opt.label).collect::<Vec<_>>();
782
783		if vals.is_empty() {
784			println!("{}  {}", *chars::BAR, "none".dimmed().italic());
785		} else {
786			let vals = self.join(&vals);
787			println!("{}  {}", *chars::BAR, vals.dimmed());
788		};
789	}
790
791	fn w_out_less(&self, less: u16, less_idx: u16, selected: &[&Opt<T, O>]) {
792		let mut stdout = stdout();
793		if less_idx > 0 {
794			let _ = execute!(stdout, cursor::MoveToPreviousLine(less_idx + 1));
795		} else {
796			let _ = execute!(stdout, cursor::MoveToPreviousLine(1));
797		}
798
799		println!("{}  {}", (*chars::STEP_SUBMIT).green(), self.message);
800
801		for _ in 0..less.into() {
802			println!("{}", ansi::CLEAR_LINE);
803		}
804		println!("{}", ansi::CLEAR_LINE);
805		println!("{}", ansi::CLEAR_LINE);
806
807		let mv = less + 2;
808		let _ = execute!(stdout, cursor::MoveToPreviousLine(mv));
809
810		let vals = selected.iter().map(|&opt| &opt.label).collect::<Vec<_>>();
811
812		if vals.is_empty() {
813			println!("{}  {}", *chars::BAR, "none".dimmed().italic());
814		} else {
815			let vals = self.join(&vals);
816			println!("{}  {}", *chars::BAR, vals.dimmed());
817		};
818	}
819
820	fn join(&self, v: &[&O]) -> String {
821		v.iter()
822			.map(|val| val.to_string())
823			.collect::<Vec<_>>()
824			.join(", ")
825	}
826}
827
828/// Shorthand for [`MultiSelect::new()`]
829pub fn multi_select<M: Display, T: Clone, O: Display + Clone>(message: M) -> MultiSelect<M, T, O> {
830	MultiSelect::new(message)
831}