may_clack/
multi_select.rs

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