diff --git a/src/multiselect.rs b/src/multiselect.rs index f88bcb0..887f189 100644 --- a/src/multiselect.rs +++ b/src/multiselect.rs @@ -29,7 +29,7 @@ use crate::{theme, DemandOption}; /// .option(DemandOption::new("Nutella")); /// let toppings = multiselect.run().expect("error running multi select"); /// ``` -pub struct MultiSelect<'a, T: Display> { +pub struct MultiSelect<'a, T> { /// The title of the selector pub title: String, /// The colors/style of the selector @@ -55,7 +55,7 @@ pub struct MultiSelect<'a, T: Display> { capacity: usize, } -impl<'a, T: Display> MultiSelect<'a, T> { +impl<'a, T> MultiSelect<'a, T> { /// Create a new multi select with the given title pub fn new>(title: S) -> Self { let mut ms = MultiSelect { @@ -474,4 +474,53 @@ mod tests { without_ansi(select.render().unwrap().as_str()) ); } + + #[test] + fn non_display() { + struct Thing { + num: u32, + thing: Option<()>, + } + let things = [ + Thing { + num: 1, + thing: Some(()), + }, + Thing { + num: 2, + thing: None, + }, + Thing { + num: 3, + thing: None, + }, + ]; + let select = MultiSelect::new("things") + .description("pick a thing") + .options( + things + .iter() + .enumerate() + .map(|(i, t)| { + if i == 0 { + DemandOption::with_label("First", t) + } else { + DemandOption::new(t.num).item(t).selected(true) + } + }) + .collect(), + ); + assert_eq!( + indoc! { + " things + pick a thing + >[ ] First + [•] 2 + [•] 3 + + ↑/↓/k/j up/down • x/space toggle • a toggle all • enter confirm" + }, + without_ansi(select.render().unwrap().as_str()) + ); + } } diff --git a/src/option.rs b/src/option.rs index 09fb0d0..c8974f7 100644 --- a/src/option.rs +++ b/src/option.rs @@ -3,7 +3,7 @@ use std::sync::atomic::AtomicUsize; /// An individual option in a select or multi-select. #[derive(Debug, Clone)] -pub struct DemandOption { +pub struct DemandOption { /// Unique ID for this option. pub(crate) id: usize, /// The item this option represents. @@ -14,8 +14,8 @@ pub struct DemandOption { pub selected: bool, } -impl DemandOption { - /// Create a new option with the given key. +impl DemandOption { + /// Create a new option with the item as the label pub fn new(item: T) -> Self { static ID: AtomicUsize = AtomicUsize::new(0); Self { @@ -25,7 +25,27 @@ impl DemandOption { selected: false, } } +} +impl DemandOption { + /// Create a new option with a label and item + pub fn with_label>(label: S, item: T) -> Self { + static ID: AtomicUsize = AtomicUsize::new(0); + Self { + id: ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed), + label: label.into(), + item, + selected: false, + } + } + pub fn item(self, item: I) -> DemandOption { + DemandOption { + id: self.id, + item, + label: self.label, + selected: self.selected, + } + } /// Set the display label for this option. pub fn label(mut self, name: &str) -> Self { self.label = name.to_string(); diff --git a/src/select.rs b/src/select.rs index b78e41e..01d5a1b 100644 --- a/src/select.rs +++ b/src/select.rs @@ -26,7 +26,7 @@ use crate::{theme, DemandOption}; /// .option(DemandOption::new("Nutella")); /// let topping = select.run().expect("error running multi select"); /// ``` -pub struct Select<'a, T: Display> { +pub struct Select<'a, T> { /// The title of the selector pub title: String, /// The colors/style of the selector @@ -48,7 +48,7 @@ pub struct Select<'a, T: Display> { capacity: usize, } -impl<'a, T: Display> Select<'a, T> { +impl<'a, T> Select<'a, T> { /// Create a new select with the given title pub fn new>(title: S) -> Self { let mut s = Select { @@ -136,7 +136,7 @@ impl<'a, T: Display> Select<'a, T> { self.term.show_cursor()?; let id = self.visible_options().get(self.cursor).unwrap().id; let selected = self.options.iter().find(|o| o.id == id).unwrap(); - let output = self.render_success(&selected.item.to_string())?; + let output = self.render_success(&selected.label)?; let selected = self.options.into_iter().find(|o| o.id == id).unwrap(); self.term.write_all(output.as_bytes())?; return Ok(selected.item); @@ -359,4 +359,46 @@ mod tests { without_ansi(select.render().unwrap().as_str()) ); } + + #[test] + fn non_display() { + struct Thing { + num: u32, + thing: Option<()>, + } + let things = [ + Thing { + num: 1, + thing: Some(()), + }, + Thing { + num: 2, + thing: None, + }, + ]; + let select = Select::new("things").description("pick a thing").options( + things + .iter() + .enumerate() + .map(|(i, t)| { + if i == 0 { + DemandOption::with_label("First", t).selected(true) + } else { + DemandOption::new(t.num).item(t) + } + }) + .collect(), + ); + assert_eq!( + indoc! { + " things + pick a thing + > First + 2 + + ↑/↓/k/j up/down • enter confirm" + }, + without_ansi(select.render().unwrap().as_str()) + ); + } } diff --git a/src/spinner.rs b/src/spinner.rs index d370ba5..b762a8c 100644 --- a/src/spinner.rs +++ b/src/spinner.rs @@ -64,28 +64,30 @@ impl<'a> Spinner<'a> { } /// Displays the dialog to the user and returns their response - pub fn run(mut self, func: F) -> io::Result<()> + pub fn run<'scope, F, T>(mut self, func: F) -> io::Result where - F: Fn() + Send + 'static, + F: FnOnce() -> T + Send + 'scope, + T: Send + 'scope, { - let handle = std::thread::spawn(move || { - func(); - }); - - self.term.hide_cursor()?; - loop { - self.clear()?; - let output = self.render()?; - self.height = output.lines().count() - 1; - self.term.write_all(output.as_bytes())?; - sleep(self.style.fps); - if handle.is_finished() { + std::thread::scope(|s| { + let handle = s.spawn(func); + self.term.hide_cursor()?; + loop { self.clear()?; - self.term.show_cursor()?; - break; + let output = self.render()?; + self.height = output.lines().count(); + self.term.write_all(output.as_bytes())?; + sleep(self.style.fps); + if handle.is_finished() { + self.clear()?; + self.term.show_cursor()?; + break; + } } - } - Ok(()) + handle.join().map_err(|e| { + io::Error::new(io::ErrorKind::Other, format!("thread panicked: {e:?}")) + }) + }) } /// Render the spinner and return the output @@ -217,4 +219,25 @@ mod test { } } } + + #[test] + fn scope_test() { + let spinner = Spinner::new("Scoped"); + let mut a = [1, 2, 3]; + let mut i = 0; + let out = spinner + .run(|| { + for n in &mut a { + if i == 1 { + *n = 5; + } + i += 1; + std::thread::sleep(Duration::from_millis(*n)); + } + i * 5 + }) + .unwrap(); + assert_eq!(a, [1, 5, 3]); + assert_eq!(out, 15); + } }