diff --git a/examples/checkbox.rs b/examples/checkbox.rs new file mode 100644 index 0000000..918f421 --- /dev/null +++ b/examples/checkbox.rs @@ -0,0 +1,14 @@ +extern crate inquirer; + +fn main() { + let choices = &["Pepperoni", "Ham", "Pineapple"]; + + match inquirer::checkbox("Choose your toppings:", choices) { + Ok(result) => println!("You chose {:?}.", result), + Err(inquirer::Error::UserAborted) => { + println!("Pressed Ctrl-C, exiting."); + std::process::exit(1); + } + Err(err) => println!("{:?}", err), + } +} diff --git a/src/checkbox.rs b/src/checkbox.rs new file mode 100644 index 0000000..049718e --- /dev/null +++ b/src/checkbox.rs @@ -0,0 +1,136 @@ +use std::io::{Write, stdout, stdin}; +use termion::{TermRead, TermWrite, IntoRawMode, color, Style, Key}; + +use choice::Choice; +use error::Error; + +/// Render a checkbox style list the user can select multiple value from. +/// +/// The return type is a `Result` that contain a vector of references to results +/// or a custom error. **Please note:** If the user presses +/// CtrlC, this will result in an `UserAborted` error that +/// the application should handle. +/// +/// # Examples +/// +/// Simple example, using only string slices for options. +/// +/// ```rust,no_run +/// extern crate inquirer; +/// +/// let choices = &["Pepperoni", "Ham", "Pineapple"]; +/// let result = inquirer::checkbox("What topppings would you like?", choices).unwrap(); +/// ``` +/// +/// ## Complex types +/// +/// You can also use tuples for options, where the first item is printed to the +/// screen, but the second item is returned as the value of the selection. +/// +/// ```rust,no_run +/// # extern crate inquirer; +/// let choices = &[("Alpha", 1), ("Beta", 2), ("Gamma", 3)]; +/// let result = inquirer::checkbox("Choose an option:", choices).unwrap(); +/// ``` +/// +/// ## Error Handling +/// +/// ```rust,no_run +/// # extern crate inquirer; +/// let choices = &["Pepperoni", "Ham", "Pineapple"]; +/// +/// match inquirer::checkbox("Choose your toppings:", choices) { +/// Ok(result) => println!("You chose {:?}.", result), +/// Err(inquirer::Error::UserAborted) => { +/// println!("Pressed Ctrl-C, exiting."); +/// std::process::exit(1); +/// } +/// Err(err) => println!("{:?}", err) +/// } +/// ``` +pub fn checkbox<'c, C, V>(prompt: &str, choices: &'c [C]) -> Result, Error> + where C: Choice +{ + let stdin = stdin(); + let mut stdout = try!(stdout().into_raw_mode()); + try!(stdout.hide_cursor()); + + try!(stdout.color(color::Green)); + print!("[?] "); + try!(stdout.reset()); + println!("{} (Press to select)", prompt); + + let mut selected: Vec<_> = (0..choices.len()).map(|_| false).collect(); + + for _ in 0..choices.len() - 1 { + println!(""); + } + + let mut cur: usize = 0; + + let mut input = stdin.keys(); + + loop { + try!(stdout.move_cursor_up(choices.len() as u32)); + for (i, s) in choices.iter().enumerate() { + print!("\n\r"); + try!(stdout.clear_line()); + + if cur == i { + try!(stdout.style(Style::Bold)); + print!(" > "); + } else { + print!(" "); + } + + if selected[i] { + try!(stdout.style(Style::Bold)); + print!("● {}", s.text()); + try!(stdout.reset()); + } else { + print!("○ {}", s.text()); + } + + try!(stdout.reset()); + + } + + try!(stdout.lock().flush()); + + let next = try!(input.next().ok_or_else(|| Error::NoMoreInput)); + + match try!(next) { + Key::Char('\n') => { + // Enter + break; + } + Key::Up if cur != 0 => { + cur -= 1; + } + Key::Down if cur != choices.len() - 1 => { + cur += 1; + } + Key::Char(' ') => selected[cur] = !selected[cur], + Key::Ctrl('c') => { + print!("\n\r"); + try!(stdout.show_cursor()); + return Err(Error::UserAborted); + } + _ => { + // pass + } + } + } + + print!("\n\r"); + try!(stdout.show_cursor()); + + Ok(choices.iter() + .zip(selected.iter()) + .filter_map(|(choice, &selected)| if selected { + Some(choice.value()) + } else { + None + }) + .collect()) +} diff --git a/src/lib.rs b/src/lib.rs index b5c08e4..3cdd006 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,8 +17,10 @@ mod choice; mod error; mod list; mod confirm; +mod checkbox; pub use choice::Choice; pub use error::Error; pub use list::list; pub use confirm::confirm; +pub use checkbox::checkbox;