diff options
author | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
---|---|---|
committer | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
commit | 1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch) | |
tree | 7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/dialoguer/src/prompts/select.rs | |
parent | 5ecd8cf2cba827454317368b68571df0d13d7842 (diff) | |
download | fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip |
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/dialoguer/src/prompts/select.rs')
-rw-r--r-- | vendor/dialoguer/src/prompts/select.rs | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/vendor/dialoguer/src/prompts/select.rs b/vendor/dialoguer/src/prompts/select.rs new file mode 100644 index 0000000..d080abd --- /dev/null +++ b/vendor/dialoguer/src/prompts/select.rs @@ -0,0 +1,419 @@ +use std::{io, ops::Rem}; + +use crate::paging::Paging; +use crate::theme::{SimpleTheme, TermThemeRenderer, Theme}; + +use console::{Key, Term}; + +/// Renders a select prompt. +/// +/// User can select from one or more options. +/// Interaction returns index of an item selected in the order they appear in `item` invocation or `items` slice. +/// +/// ## Examples +/// +/// ```rust,no_run +/// use dialoguer::{console::Term, theme::ColorfulTheme, Select}; +/// +/// fn main() -> std::io::Result<()> { +/// let items = vec!["Item 1", "item 2"]; +/// let selection = Select::with_theme(&ColorfulTheme::default()) +/// .items(&items) +/// .default(0) +/// .interact_on_opt(&Term::stderr())?; +/// +/// match selection { +/// Some(index) => println!("User selected item : {}", items[index]), +/// None => println!("User did not select anything") +/// } +/// +/// Ok(()) +/// } +/// ``` +pub struct Select<'a> { + default: usize, + items: Vec<String>, + prompt: Option<String>, + report: bool, + clear: bool, + theme: &'a dyn Theme, + max_length: Option<usize>, +} + +impl Default for Select<'static> { + fn default() -> Self { + Self::new() + } +} + +impl Select<'static> { + /// Creates a select prompt builder with default theme. + pub fn new() -> Self { + Self::with_theme(&SimpleTheme) + } +} + +impl Select<'_> { + /// Indicates whether select menu should be erased from the screen after interaction. + /// + /// The default is to clear the menu. + pub fn clear(&mut self, val: bool) -> &mut Self { + self.clear = val; + self + } + + /// Sets initial selected element when select menu is rendered + /// + /// Element is indicated by the index at which it appears in `item` method invocation or `items` slice. + pub fn default(&mut self, val: usize) -> &mut Self { + self.default = val; + self + } + + /// Sets an optional max length for a page. + /// + /// Max length is disabled by None + pub fn max_length(&mut self, val: usize) -> &mut Self { + // Paging subtracts two from the capacity, paging does this to + // make an offset for the page indicator. So to make sure that + // we can show the intended amount of items we need to add two + // to our value. + self.max_length = Some(val + 2); + self + } + + /// Add a single item to the selector. + /// + /// ## Examples + /// ```rust,no_run + /// use dialoguer::Select; + /// + /// fn main() -> std::io::Result<()> { + /// let selection: usize = Select::new() + /// .item("Item 1") + /// .item("Item 2") + /// .interact()?; + /// + /// Ok(()) + /// } + /// ``` + pub fn item<T: ToString>(&mut self, item: T) -> &mut Self { + self.items.push(item.to_string()); + self + } + + /// Adds multiple items to the selector. + /// + /// ## Examples + /// ```rust,no_run + /// use dialoguer::Select; + /// + /// fn main() -> std::io::Result<()> { + /// let items = vec!["Item 1", "Item 2"]; + /// let selection: usize = Select::new() + /// .items(&items) + /// .interact()?; + /// + /// println!("{}", items[selection]); + /// + /// Ok(()) + /// } + /// ``` + pub fn items<T: ToString>(&mut self, items: &[T]) -> &mut Self { + for item in items { + self.items.push(item.to_string()); + } + self + } + + /// Sets the select prompt. + /// + /// By default, when a prompt is set the system also prints out a confirmation after + /// the selection. You can opt-out of this with [`report`](#method.report). + /// + /// ## Examples + /// ```rust,no_run + /// use dialoguer::Select; + /// + /// fn main() -> std::io::Result<()> { + /// let selection = Select::new() + /// .with_prompt("Which option do you prefer?") + /// .item("Option A") + /// .item("Option B") + /// .interact()?; + /// + /// Ok(()) + /// } + /// ``` + pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Self { + self.prompt = Some(prompt.into()); + self.report = true; + self + } + + /// Indicates whether to report the selected value after interaction. + /// + /// The default is to report the selection. + pub fn report(&mut self, val: bool) -> &mut Self { + self.report = val; + self + } + + /// Enables user interaction and returns the result. + /// + /// The user can select the items with the 'Space' bar or 'Enter' and the index of selected item will be returned. + /// The dialog is rendered on stderr. + /// Result contains `index` if user selected one of items using 'Enter'. + /// This unlike [`interact_opt`](Self::interact_opt) does not allow to quit with 'Esc' or 'q'. + #[inline] + pub fn interact(&self) -> io::Result<usize> { + self.interact_on(&Term::stderr()) + } + + /// Enables user interaction and returns the result. + /// + /// The user can select the items with the 'Space' bar or 'Enter' and the index of selected item will be returned. + /// The dialog is rendered on stderr. + /// Result contains `Some(index)` if user selected one of items using 'Enter' or `None` if user cancelled with 'Esc' or 'q'. + #[inline] + pub fn interact_opt(&self) -> io::Result<Option<usize>> { + self.interact_on_opt(&Term::stderr()) + } + + /// Like [interact](#method.interact) but allows a specific terminal to be set. + /// + /// ## Examples + ///```rust,no_run + /// use dialoguer::{console::Term, Select}; + /// + /// fn main() -> std::io::Result<()> { + /// let selection = Select::new() + /// .item("Option A") + /// .item("Option B") + /// .interact_on(&Term::stderr())?; + /// + /// println!("User selected option at index {}", selection); + /// + /// Ok(()) + /// } + ///``` + #[inline] + pub fn interact_on(&self, term: &Term) -> io::Result<usize> { + self._interact_on(term, false)? + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Quit not allowed in this case")) + } + + /// Like [`interact_opt`](Self::interact_opt) but allows a specific terminal to be set. + /// + /// ## Examples + /// ```rust,no_run + /// use dialoguer::{console::Term, Select}; + /// + /// fn main() -> std::io::Result<()> { + /// let selection = Select::new() + /// .item("Option A") + /// .item("Option B") + /// .interact_on_opt(&Term::stdout())?; + /// + /// match selection { + /// Some(position) => println!("User selected option at index {}", position), + /// None => println!("User did not select anything or exited using Esc or q") + /// } + /// + /// Ok(()) + /// } + /// ``` + #[inline] + pub fn interact_on_opt(&self, term: &Term) -> io::Result<Option<usize>> { + self._interact_on(term, true) + } + + /// Like `interact` but allows a specific terminal to be set. + fn _interact_on(&self, term: &Term, allow_quit: bool) -> io::Result<Option<usize>> { + if self.items.is_empty() { + return Err(io::Error::new( + io::ErrorKind::Other, + "Empty list of items given to `Select`", + )); + } + + let mut paging = Paging::new(term, self.items.len(), self.max_length); + let mut render = TermThemeRenderer::new(term, self.theme); + let mut sel = self.default; + + let mut size_vec = Vec::new(); + + for items in self + .items + .iter() + .flat_map(|i| i.split('\n')) + .collect::<Vec<_>>() + { + let size = &items.len(); + size_vec.push(*size); + } + + term.hide_cursor()?; + + loop { + if let Some(ref prompt) = self.prompt { + paging.render_prompt(|paging_info| render.select_prompt(prompt, paging_info))?; + } + + for (idx, item) in self + .items + .iter() + .enumerate() + .skip(paging.current_page * paging.capacity) + .take(paging.capacity) + { + render.select_prompt_item(item, sel == idx)?; + } + + term.flush()?; + + match term.read_key()? { + Key::ArrowDown | Key::Tab | Key::Char('j') => { + if sel == !0 { + sel = 0; + } else { + sel = (sel as u64 + 1).rem(self.items.len() as u64) as usize; + } + } + Key::Escape | Key::Char('q') => { + if allow_quit { + if self.clear { + render.clear()?; + } else { + term.clear_last_lines(paging.capacity)?; + } + + term.show_cursor()?; + term.flush()?; + + return Ok(None); + } + } + Key::ArrowUp | Key::BackTab | Key::Char('k') => { + if sel == !0 { + sel = self.items.len() - 1; + } else { + sel = ((sel as i64 - 1 + self.items.len() as i64) + % (self.items.len() as i64)) as usize; + } + } + Key::ArrowLeft | Key::Char('h') => { + if paging.active { + sel = paging.previous_page(); + } + } + Key::ArrowRight | Key::Char('l') => { + if paging.active { + sel = paging.next_page(); + } + } + + Key::Enter | Key::Char(' ') if sel != !0 => { + if self.clear { + render.clear()?; + } + + if let Some(ref prompt) = self.prompt { + if self.report { + render.select_prompt_selection(prompt, &self.items[sel])?; + } + } + + term.show_cursor()?; + term.flush()?; + + return Ok(Some(sel)); + } + _ => {} + } + + paging.update(sel)?; + + if paging.active { + render.clear()?; + } else { + render.clear_preserve_prompt(&size_vec)?; + } + } + } +} + +impl<'a> Select<'a> { + /// Creates a select prompt builder with a specific theme. + /// + /// ## Examples + /// ```rust,no_run + /// use dialoguer::{ + /// Select, + /// theme::ColorfulTheme + /// }; + /// + /// fn main() -> std::io::Result<()> { + /// let selection = Select::with_theme(&ColorfulTheme::default()) + /// .item("Option A") + /// .item("Option B") + /// .interact()?; + /// + /// Ok(()) + /// } + /// ``` + pub fn with_theme(theme: &'a dyn Theme) -> Self { + Self { + default: !0, + items: vec![], + prompt: None, + report: false, + clear: true, + max_length: None, + theme, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_str() { + let selections = &[ + "Ice Cream", + "Vanilla Cupcake", + "Chocolate Muffin", + "A Pile of sweet, sweet mustard", + ]; + + assert_eq!( + Select::new().default(0).items(&selections[..]).items, + selections + ); + } + + #[test] + fn test_string() { + let selections = vec!["a".to_string(), "b".to_string()]; + + assert_eq!( + Select::new().default(0).items(&selections[..]).items, + selections + ); + } + + #[test] + fn test_ref_str() { + let a = "a"; + let b = "b"; + + let selections = &[a, b]; + + assert_eq!( + Select::new().default(0).items(&selections[..]).items, + selections + ); + } +} |