summaryrefslogtreecommitdiff
path: root/vendor/dialoguer/src/prompts
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
committerValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
commit1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch)
tree7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/dialoguer/src/prompts
parent5ecd8cf2cba827454317368b68571df0d13d7842 (diff)
downloadfparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz
fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/dialoguer/src/prompts')
-rw-r--r--vendor/dialoguer/src/prompts/confirm.rs287
-rw-r--r--vendor/dialoguer/src/prompts/fuzzy_select.rs326
-rw-r--r--vendor/dialoguer/src/prompts/input.rs691
-rw-r--r--vendor/dialoguer/src/prompts/mod.rs13
-rw-r--r--vendor/dialoguer/src/prompts/multi_select.rs356
-rw-r--r--vendor/dialoguer/src/prompts/password.rs194
-rw-r--r--vendor/dialoguer/src/prompts/select.rs419
-rw-r--r--vendor/dialoguer/src/prompts/sort.rs348
8 files changed, 2634 insertions, 0 deletions
diff --git a/vendor/dialoguer/src/prompts/confirm.rs b/vendor/dialoguer/src/prompts/confirm.rs
new file mode 100644
index 0000000..24bcc4c
--- /dev/null
+++ b/vendor/dialoguer/src/prompts/confirm.rs
@@ -0,0 +1,287 @@
+use std::io;
+
+use crate::theme::{SimpleTheme, TermThemeRenderer, Theme};
+
+use console::{Key, Term};
+
+/// Renders a confirm prompt.
+///
+/// ## Example usage
+///
+/// ```rust,no_run
+/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
+/// use dialoguer::Confirm;
+///
+/// if Confirm::new().with_prompt("Do you want to continue?").interact()? {
+/// println!("Looks like you want to continue");
+/// } else {
+/// println!("nevermind then :(");
+/// }
+/// # Ok(()) } fn main() { test().unwrap(); }
+/// ```
+pub struct Confirm<'a> {
+ prompt: String,
+ report: bool,
+ default: Option<bool>,
+ show_default: bool,
+ wait_for_newline: bool,
+ theme: &'a dyn Theme,
+}
+
+impl Default for Confirm<'static> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Confirm<'static> {
+ /// Creates a confirm prompt.
+ pub fn new() -> Self {
+ Self::with_theme(&SimpleTheme)
+ }
+}
+
+impl Confirm<'_> {
+ /// Sets the confirm prompt.
+ pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Self {
+ self.prompt = prompt.into();
+ self
+ }
+
+ /// Indicates whether or not to report the chosen selection after interaction.
+ ///
+ /// The default is to report the chosen selection.
+ pub fn report(&mut self, val: bool) -> &mut Self {
+ self.report = val;
+ self
+ }
+
+ #[deprecated(note = "Use with_prompt() instead", since = "0.6.0")]
+ #[inline]
+ pub fn with_text(&mut self, text: &str) -> &mut Self {
+ self.with_prompt(text)
+ }
+
+ /// Sets when to react to user input.
+ ///
+ /// When `false` (default), we check on each user keystroke immediately as
+ /// it is typed. Valid inputs can be one of 'y', 'n', or a newline to accept
+ /// the default.
+ ///
+ /// When `true`, the user must type their choice and hit the Enter key before
+ /// proceeding. Valid inputs can be "yes", "no", "y", "n", or an empty string
+ /// to accept the default.
+ pub fn wait_for_newline(&mut self, wait: bool) -> &mut Self {
+ self.wait_for_newline = wait;
+ self
+ }
+
+ /// Sets a default.
+ ///
+ /// Out of the box the prompt does not have a default and will continue
+ /// to display until the user inputs something and hits enter. If a default is set the user
+ /// can instead accept the default with enter.
+ pub fn default(&mut self, val: bool) -> &mut Self {
+ self.default = Some(val);
+ self
+ }
+
+ /// Disables or enables the default value display.
+ ///
+ /// The default is to append the default value to the prompt to tell the user.
+ pub fn show_default(&mut self, val: bool) -> &mut Self {
+ self.show_default = val;
+ self
+ }
+
+ /// Enables user interaction and returns the result.
+ ///
+ /// The dialog is rendered on stderr.
+ ///
+ /// Result contains `bool` if user answered "yes" or "no" or `default` (configured in [`default`](Self::default) if pushes enter.
+ /// This unlike [`interact_opt`](Self::interact_opt) does not allow to quit with 'Esc' or 'q'.
+ #[inline]
+ pub fn interact(&self) -> io::Result<bool> {
+ self.interact_on(&Term::stderr())
+ }
+
+ /// Enables user interaction and returns the result.
+ ///
+ /// The dialog is rendered on stderr.
+ ///
+ /// Result contains `Some(bool)` if user answered "yes" or "no" or `Some(default)` (configured in [`default`](Self::default)) if pushes enter,
+ /// or `None` if user cancelled with 'Esc' or 'q'.
+ #[inline]
+ pub fn interact_opt(&self) -> io::Result<Option<bool>> {
+ self.interact_on_opt(&Term::stderr())
+ }
+
+ /// Like [interact](#method.interact) but allows a specific terminal to be set.
+ ///
+ /// ## Examples
+ ///
+ /// ```rust,no_run
+ /// use dialoguer::Confirm;
+ /// use console::Term;
+ ///
+ /// # fn main() -> std::io::Result<()> {
+ /// let proceed = Confirm::new()
+ /// .with_prompt("Do you wish to continue?")
+ /// .interact_on(&Term::stderr())?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ #[inline]
+ pub fn interact_on(&self, term: &Term) -> io::Result<bool> {
+ 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::Confirm;
+ /// use console::Term;
+ ///
+ /// fn main() -> std::io::Result<()> {
+ /// let confirmation = Confirm::new()
+ /// .interact_on_opt(&Term::stdout())?;
+ ///
+ /// match confirmation {
+ /// Some(answer) => println!("User answered {}", if answer { "yes" } else { "no " }),
+ /// None => println!("User did not answer")
+ /// }
+ ///
+ /// Ok(())
+ /// }
+ /// ```
+ #[inline]
+ pub fn interact_on_opt(&self, term: &Term) -> io::Result<Option<bool>> {
+ self._interact_on(term, true)
+ }
+
+ fn _interact_on(&self, term: &Term, allow_quit: bool) -> io::Result<Option<bool>> {
+ let mut render = TermThemeRenderer::new(term, self.theme);
+
+ let default_if_show = if self.show_default {
+ self.default
+ } else {
+ None
+ };
+
+ render.confirm_prompt(&self.prompt, default_if_show)?;
+
+ term.hide_cursor()?;
+ term.flush()?;
+
+ let rv;
+
+ if self.wait_for_newline {
+ // Waits for user input and for the user to hit the Enter key
+ // before validation.
+ let mut value = default_if_show;
+
+ loop {
+ let input = term.read_key()?;
+
+ match input {
+ Key::Char('y') | Key::Char('Y') => {
+ value = Some(true);
+ }
+ Key::Char('n') | Key::Char('N') => {
+ value = Some(false);
+ }
+ Key::Enter => {
+ if !allow_quit {
+ value = value.or(self.default);
+ }
+
+ if value.is_some() || allow_quit {
+ rv = value;
+ break;
+ }
+ continue;
+ }
+ Key::Escape | Key::Char('q') if allow_quit => {
+ value = None;
+ }
+ Key::Unknown => {
+ return Err(io::Error::new(
+ io::ErrorKind::NotConnected,
+ "Not a terminal",
+ ))
+ }
+ _ => {
+ continue;
+ }
+ };
+
+ term.clear_line()?;
+ render.confirm_prompt(&self.prompt, value)?;
+ }
+ } else {
+ // Default behavior: matches continuously on every keystroke,
+ // and does not wait for user to hit the Enter key.
+ loop {
+ let input = term.read_key()?;
+ let value = match input {
+ Key::Char('y') | Key::Char('Y') => Some(true),
+ Key::Char('n') | Key::Char('N') => Some(false),
+ Key::Enter if self.default.is_some() => Some(self.default.unwrap()),
+ Key::Escape | Key::Char('q') if allow_quit => None,
+ Key::Unknown => {
+ return Err(io::Error::new(
+ io::ErrorKind::NotConnected,
+ "Not a terminal",
+ ))
+ }
+ _ => {
+ continue;
+ }
+ };
+
+ rv = value;
+ break;
+ }
+ }
+
+ term.clear_line()?;
+ if self.report {
+ render.confirm_prompt_selection(&self.prompt, rv)?;
+ }
+ term.show_cursor()?;
+ term.flush()?;
+
+ Ok(rv)
+ }
+}
+
+impl<'a> Confirm<'a> {
+ /// Creates a confirm prompt with a specific theme.
+ ///
+ /// ## Examples
+ /// ```rust,no_run
+ /// use dialoguer::{
+ /// Confirm,
+ /// theme::ColorfulTheme
+ /// };
+ ///
+ /// # fn main() -> std::io::Result<()> {
+ /// let proceed = Confirm::with_theme(&ColorfulTheme::default())
+ /// .with_prompt("Do you wish to continue?")
+ /// .interact()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn with_theme(theme: &'a dyn Theme) -> Self {
+ Self {
+ prompt: "".into(),
+ report: true,
+ default: None,
+ show_default: true,
+ wait_for_newline: false,
+ theme,
+ }
+ }
+}
diff --git a/vendor/dialoguer/src/prompts/fuzzy_select.rs b/vendor/dialoguer/src/prompts/fuzzy_select.rs
new file mode 100644
index 0000000..9b7f992
--- /dev/null
+++ b/vendor/dialoguer/src/prompts/fuzzy_select.rs
@@ -0,0 +1,326 @@
+use crate::theme::{SimpleTheme, TermThemeRenderer, Theme};
+use console::{Key, Term};
+use fuzzy_matcher::FuzzyMatcher;
+use std::{io, ops::Rem};
+
+/// Renders a selection menu that user can fuzzy match to reduce set.
+///
+/// User can use fuzzy search to limit selectable items.
+/// Interaction returns index of an item selected in the order they appear in `item` invocation or `items` slice.
+///
+/// ## Examples
+///
+/// ```rust,no_run
+/// use dialoguer::{
+/// FuzzySelect,
+/// theme::ColorfulTheme
+/// };
+/// use console::Term;
+///
+/// fn main() -> std::io::Result<()> {
+/// let items = vec!["Item 1", "item 2"];
+/// let selection = FuzzySelect::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 FuzzySelect<'a> {
+ default: Option<usize>,
+ items: Vec<String>,
+ prompt: String,
+ report: bool,
+ clear: bool,
+ highlight_matches: bool,
+ max_length: Option<usize>,
+ theme: &'a dyn Theme,
+ /// Search string that a fuzzy search with start with.
+ /// Defaults to an empty string.
+ initial_text: String,
+}
+
+impl Default for FuzzySelect<'static> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl FuzzySelect<'static> {
+ /// Creates the prompt with a specific text.
+ pub fn new() -> Self {
+ Self::with_theme(&SimpleTheme)
+ }
+}
+
+impl FuzzySelect<'_> {
+ /// Sets the clear behavior of the menu.
+ ///
+ /// The default is to clear the menu.
+ pub fn clear(&mut self, val: bool) -> &mut Self {
+ self.clear = val;
+ self
+ }
+
+ /// Sets a default for the menu
+ pub fn default(&mut self, val: usize) -> &mut Self {
+ self.default = Some(val);
+ self
+ }
+
+ /// Add a single item to the fuzzy selector.
+ pub fn item<T: ToString>(&mut self, item: T) -> &mut Self {
+ self.items.push(item.to_string());
+ self
+ }
+
+ /// Adds multiple items to the fuzzy selector.
+ pub fn items<T: ToString>(&mut self, items: &[T]) -> &mut Self {
+ for item in items {
+ self.items.push(item.to_string());
+ }
+ self
+ }
+
+ /// Sets the search text that a fuzzy search starts with.
+ pub fn with_initial_text<S: Into<String>>(&mut self, initial_text: S) -> &mut Self {
+ self.initial_text = initial_text.into();
+ self
+ }
+
+ /// Prefaces the menu with a prompt.
+ ///
+ /// When a prompt is set the system also prints out a confirmation after
+ /// the fuzzy selection.
+ pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Self {
+ self.prompt = prompt.into();
+ 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
+ }
+
+ /// Indicates whether to highlight matched indices
+ ///
+ /// The default is to highlight the indices
+ pub fn highlight_matches(&mut self, val: bool) -> &mut Self {
+ self.highlight_matches = val;
+ self
+ }
+
+ /// Sets the maximum number of visible options.
+ ///
+ /// The default is the height of the terminal minus 2.
+ pub fn max_length(&mut self, rows: usize) -> &mut Self {
+ self.max_length = Some(rows);
+ self
+ }
+
+ /// Enables user interaction and returns the result.
+ ///
+ /// The user can select the items using 'Enter' and the index of selected item will be returned.
+ /// The dialog is rendered on stderr.
+ /// Result contains `index` of selected item if user hit 'Enter'.
+ /// This unlike [interact_opt](#method.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 using 'Enter' and the index of selected item will be returned.
+ /// The dialog is rendered on stderr.
+ /// Result contains `Some(index)` if user hit '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` but allows a specific terminal to be set.
+ #[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` but allows a specific terminal to be set.
+ #[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>> {
+ // Place cursor at the end of the search term
+ let mut position = self.initial_text.len();
+ let mut search_term = self.initial_text.to_owned();
+
+ 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().as_slice() {
+ let size = &items.len();
+ size_vec.push(*size);
+ }
+
+ // Fuzzy matcher
+ let matcher = fuzzy_matcher::skim::SkimMatcherV2::default();
+
+ // Subtract -2 because we need space to render the prompt.
+ let visible_term_rows = (term.size().0 as usize).max(3) - 2;
+ let visible_term_rows = self
+ .max_length
+ .unwrap_or(visible_term_rows)
+ .min(visible_term_rows);
+ // Variable used to determine if we need to scroll through the list.
+ let mut starting_row = 0;
+
+ term.hide_cursor()?;
+
+ loop {
+ render.clear()?;
+ render.fuzzy_select_prompt(self.prompt.as_str(), &search_term, position)?;
+
+ // Maps all items to a tuple of item and its match score.
+ let mut filtered_list = self
+ .items
+ .iter()
+ .map(|item| (item, matcher.fuzzy_match(item, &search_term)))
+ .filter_map(|(item, score)| score.map(|s| (item, s)))
+ .collect::<Vec<_>>();
+
+ // Renders all matching items, from best match to worst.
+ filtered_list.sort_unstable_by(|(_, s1), (_, s2)| s2.cmp(s1));
+
+ for (idx, (item, _)) in filtered_list
+ .iter()
+ .enumerate()
+ .skip(starting_row)
+ .take(visible_term_rows)
+ {
+ render.fuzzy_select_prompt_item(
+ item,
+ Some(idx) == sel,
+ self.highlight_matches,
+ &matcher,
+ &search_term,
+ )?;
+ }
+ term.flush()?;
+
+ match (term.read_key()?, sel) {
+ (Key::Escape, _) if allow_quit => {
+ if self.clear {
+ render.clear()?;
+ term.flush()?;
+ }
+ term.show_cursor()?;
+ return Ok(None);
+ }
+ (Key::ArrowUp | Key::BackTab, _) if !filtered_list.is_empty() => {
+ if sel == Some(0) {
+ starting_row =
+ filtered_list.len().max(visible_term_rows) - visible_term_rows;
+ } else if sel == Some(starting_row) {
+ starting_row -= 1;
+ }
+ sel = match sel {
+ None => Some(filtered_list.len() - 1),
+ Some(sel) => Some(
+ ((sel as i64 - 1 + filtered_list.len() as i64)
+ % (filtered_list.len() as i64))
+ as usize,
+ ),
+ };
+ term.flush()?;
+ }
+ (Key::ArrowDown | Key::Tab, _) if !filtered_list.is_empty() => {
+ sel = match sel {
+ None => Some(0),
+ Some(sel) => {
+ Some((sel as u64 + 1).rem(filtered_list.len() as u64) as usize)
+ }
+ };
+ if sel == Some(visible_term_rows + starting_row) {
+ starting_row += 1;
+ } else if sel == Some(0) {
+ starting_row = 0;
+ }
+ term.flush()?;
+ }
+ (Key::ArrowLeft, _) if position > 0 => {
+ position -= 1;
+ term.flush()?;
+ }
+ (Key::ArrowRight, _) if position < search_term.len() => {
+ position += 1;
+ term.flush()?;
+ }
+ (Key::Enter, Some(sel)) if !filtered_list.is_empty() => {
+ if self.clear {
+ render.clear()?;
+ }
+
+ if self.report {
+ render
+ .input_prompt_selection(self.prompt.as_str(), filtered_list[sel].0)?;
+ }
+
+ let sel_string = filtered_list[sel].0;
+ let sel_string_pos_in_items =
+ self.items.iter().position(|item| item.eq(sel_string));
+
+ term.show_cursor()?;
+ return Ok(sel_string_pos_in_items);
+ }
+ (Key::Backspace, _) if position > 0 => {
+ position -= 1;
+ search_term.remove(position);
+ term.flush()?;
+ }
+ (Key::Char(chr), _) if !chr.is_ascii_control() => {
+ search_term.insert(position, chr);
+ position += 1;
+ term.flush()?;
+ sel = Some(0);
+ starting_row = 0;
+ }
+
+ _ => {}
+ }
+
+ render.clear_preserve_prompt(&size_vec)?;
+ }
+ }
+}
+
+impl<'a> FuzzySelect<'a> {
+ /// Same as `new` but with a specific theme.
+ pub fn with_theme(theme: &'a dyn Theme) -> Self {
+ Self {
+ default: None,
+ items: vec![],
+ prompt: "".into(),
+ report: true,
+ clear: true,
+ highlight_matches: true,
+ max_length: None,
+ theme,
+ initial_text: "".into(),
+ }
+ }
+}
diff --git a/vendor/dialoguer/src/prompts/input.rs b/vendor/dialoguer/src/prompts/input.rs
new file mode 100644
index 0000000..b7cd829
--- /dev/null
+++ b/vendor/dialoguer/src/prompts/input.rs
@@ -0,0 +1,691 @@
+use std::{cmp::Ordering, fmt::Debug, io, iter, str::FromStr};
+
+#[cfg(feature = "completion")]
+use crate::completion::Completion;
+#[cfg(feature = "history")]
+use crate::history::History;
+use crate::{
+ theme::{SimpleTheme, TermThemeRenderer, Theme},
+ validate::Validator,
+};
+
+use console::{Key, Term};
+
+type ValidatorCallback<'a, T> = Box<dyn FnMut(&T) -> Option<String> + 'a>;
+
+/// Renders an input prompt.
+///
+/// ## Example usage
+///
+/// ```rust,no_run
+/// use dialoguer::Input;
+///
+/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
+/// let input : String = Input::new()
+/// .with_prompt("Tea or coffee?")
+/// .with_initial_text("Yes")
+/// .default("No".into())
+/// .interact_text()?;
+/// # Ok(())
+/// # }
+/// ```
+/// It can also be used with turbofish notation:
+///
+/// ```rust,no_run
+/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
+/// # use dialoguer::Input;
+/// let input = Input::<String>::new()
+/// .interact_text()?;
+/// # Ok(())
+/// # }
+/// ```
+pub struct Input<'a, T> {
+ prompt: String,
+ post_completion_text: Option<String>,
+ report: bool,
+ default: Option<T>,
+ show_default: bool,
+ initial_text: Option<String>,
+ theme: &'a dyn Theme,
+ permit_empty: bool,
+ validator: Option<ValidatorCallback<'a, T>>,
+ #[cfg(feature = "history")]
+ history: Option<&'a mut dyn History<T>>,
+ #[cfg(feature = "completion")]
+ completion: Option<&'a dyn Completion>,
+}
+
+impl<T> Default for Input<'static, T> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl<T> Input<'_, T> {
+ /// Creates an input prompt.
+ pub fn new() -> Self {
+ Self::with_theme(&SimpleTheme)
+ }
+
+ /// Sets the input prompt.
+ pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Self {
+ self.prompt = prompt.into();
+ self
+ }
+
+ /// Changes the prompt text to the post completion text after input is complete
+ pub fn with_post_completion_text<S: Into<String>>(
+ &mut self,
+ post_completion_text: S,
+ ) -> &mut Self {
+ self.post_completion_text = Some(post_completion_text.into());
+ self
+ }
+
+ /// Indicates whether to report the input value after interaction.
+ ///
+ /// The default is to report the input value.
+ pub fn report(&mut self, val: bool) -> &mut Self {
+ self.report = val;
+ self
+ }
+
+ /// Sets initial text that user can accept or erase.
+ pub fn with_initial_text<S: Into<String>>(&mut self, val: S) -> &mut Self {
+ self.initial_text = Some(val.into());
+ self
+ }
+
+ /// Sets a default.
+ ///
+ /// Out of the box the prompt does not have a default and will continue
+ /// to display until the user inputs something and hits enter. If a default is set the user
+ /// can instead accept the default with enter.
+ pub fn default(&mut self, value: T) -> &mut Self {
+ self.default = Some(value);
+ self
+ }
+
+ /// Enables or disables an empty input
+ ///
+ /// By default, if there is no default value set for the input, the user must input a non-empty string.
+ pub fn allow_empty(&mut self, val: bool) -> &mut Self {
+ self.permit_empty = val;
+ self
+ }
+
+ /// Disables or enables the default value display.
+ ///
+ /// The default behaviour is to append [`default`](#method.default) to the prompt to tell the
+ /// user what is the default value.
+ ///
+ /// This method does not affect existence of default value, only its display in the prompt!
+ pub fn show_default(&mut self, val: bool) -> &mut Self {
+ self.show_default = val;
+ self
+ }
+}
+
+impl<'a, T> Input<'a, T> {
+ /// Creates an input prompt with a specific theme.
+ pub fn with_theme(theme: &'a dyn Theme) -> Self {
+ Self {
+ prompt: "".into(),
+ post_completion_text: None,
+ report: true,
+ default: None,
+ show_default: true,
+ initial_text: None,
+ theme,
+ permit_empty: false,
+ validator: None,
+ #[cfg(feature = "history")]
+ history: None,
+ #[cfg(feature = "completion")]
+ completion: None,
+ }
+ }
+
+ /// Enable history processing
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// # use dialoguer::{History, Input};
+ /// # use std::{collections::VecDeque, fmt::Display};
+ /// let mut history = MyHistory::default();
+ /// loop {
+ /// if let Ok(input) = Input::<String>::new()
+ /// .with_prompt("hist")
+ /// .history_with(&mut history)
+ /// .interact_text()
+ /// {
+ /// // Do something with the input
+ /// }
+ /// }
+ /// # struct MyHistory {
+ /// # history: VecDeque<String>,
+ /// # }
+ /// #
+ /// # impl Default for MyHistory {
+ /// # fn default() -> Self {
+ /// # MyHistory {
+ /// # history: VecDeque::new(),
+ /// # }
+ /// # }
+ /// # }
+ /// #
+ /// # impl<T: ToString> History<T> for MyHistory {
+ /// # fn read(&self, pos: usize) -> Option<String> {
+ /// # self.history.get(pos).cloned()
+ /// # }
+ /// #
+ /// # fn write(&mut self, val: &T)
+ /// # where
+ /// # {
+ /// # self.history.push_front(val.to_string());
+ /// # }
+ /// # }
+ /// ```
+ #[cfg(feature = "history")]
+ pub fn history_with<H>(&mut self, history: &'a mut H) -> &mut Self
+ where
+ H: History<T>,
+ {
+ self.history = Some(history);
+ self
+ }
+
+ /// Enable completion
+ #[cfg(feature = "completion")]
+ pub fn completion_with<C>(&mut self, completion: &'a C) -> &mut Self
+ where
+ C: Completion,
+ {
+ self.completion = Some(completion);
+ self
+ }
+}
+
+impl<'a, T> Input<'a, T>
+where
+ T: 'a,
+{
+ /// Registers a validator.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// # use dialoguer::Input;
+ /// let mail: String = Input::new()
+ /// .with_prompt("Enter email")
+ /// .validate_with(|input: &String| -> Result<(), &str> {
+ /// if input.contains('@') {
+ /// Ok(())
+ /// } else {
+ /// Err("This is not a mail address")
+ /// }
+ /// })
+ /// .interact()
+ /// .unwrap();
+ /// ```
+ pub fn validate_with<V>(&mut self, mut validator: V) -> &mut Self
+ where
+ V: Validator<T> + 'a,
+ V::Err: ToString,
+ {
+ let mut old_validator_func = self.validator.take();
+
+ self.validator = Some(Box::new(move |value: &T| -> Option<String> {
+ if let Some(old) = old_validator_func.as_mut() {
+ if let Some(err) = old(value) {
+ return Some(err);
+ }
+ }
+
+ match validator.validate(value) {
+ Ok(()) => None,
+ Err(err) => Some(err.to_string()),
+ }
+ }));
+
+ self
+ }
+}
+
+impl<T> Input<'_, T>
+where
+ T: Clone + ToString + FromStr,
+ <T as FromStr>::Err: Debug + ToString,
+{
+ /// Enables the user to enter a printable ascii sequence and returns the result.
+ ///
+ /// Its difference from [`interact`](#method.interact) is that it only allows ascii characters for string,
+ /// while [`interact`](#method.interact) allows virtually any character to be used e.g arrow keys.
+ ///
+ /// The dialog is rendered on stderr.
+ pub fn interact_text(&mut self) -> io::Result<T> {
+ self.interact_text_on(&Term::stderr())
+ }
+
+ /// Like [`interact_text`](#method.interact_text) but allows a specific terminal to be set.
+ pub fn interact_text_on(&mut self, term: &Term) -> io::Result<T> {
+ let mut render = TermThemeRenderer::new(term, self.theme);
+
+ loop {
+ let default_string = self.default.as_ref().map(ToString::to_string);
+
+ let prompt_len = render.input_prompt(
+ &self.prompt,
+ if self.show_default {
+ default_string.as_deref()
+ } else {
+ None
+ },
+ )?;
+
+ // Read input by keystroke so that we can suppress ascii control characters
+ if !term.features().is_attended() {
+ return Ok("".to_owned().parse::<T>().unwrap());
+ }
+
+ let mut chars: Vec<char> = Vec::new();
+ let mut position = 0;
+ #[cfg(feature = "history")]
+ let mut hist_pos = 0;
+
+ if let Some(initial) = self.initial_text.as_ref() {
+ term.write_str(initial)?;
+ chars = initial.chars().collect();
+ position = chars.len();
+ }
+ term.flush()?;
+
+ loop {
+ match term.read_key()? {
+ Key::Backspace if position > 0 => {
+ position -= 1;
+ chars.remove(position);
+ let line_size = term.size().1 as usize;
+ // Case we want to delete last char of a line so the cursor is at the beginning of the next line
+ if (position + prompt_len) % (line_size - 1) == 0 {
+ term.clear_line()?;
+ term.move_cursor_up(1)?;
+ term.move_cursor_right(line_size + 1)?;
+ } else {
+ term.clear_chars(1)?;
+ }
+
+ let tail: String = chars[position..].iter().collect();
+
+ if !tail.is_empty() {
+ term.write_str(&tail)?;
+
+ let total = position + prompt_len + tail.len();
+ let total_line = total / line_size;
+ let line_cursor = (position + prompt_len) / line_size;
+ term.move_cursor_up(total_line - line_cursor)?;
+
+ term.move_cursor_left(line_size)?;
+ term.move_cursor_right((position + prompt_len) % line_size)?;
+ }
+
+ term.flush()?;
+ }
+ Key::Char(chr) if !chr.is_ascii_control() => {
+ chars.insert(position, chr);
+ position += 1;
+ let tail: String =
+ iter::once(&chr).chain(chars[position..].iter()).collect();
+ term.write_str(&tail)?;
+ term.move_cursor_left(tail.len() - 1)?;
+ term.flush()?;
+ }
+ Key::ArrowLeft if position > 0 => {
+ if (position + prompt_len) % term.size().1 as usize == 0 {
+ term.move_cursor_up(1)?;
+ term.move_cursor_right(term.size().1 as usize)?;
+ } else {
+ term.move_cursor_left(1)?;
+ }
+ position -= 1;
+ term.flush()?;
+ }
+ Key::ArrowRight if position < chars.len() => {
+ if (position + prompt_len) % (term.size().1 as usize - 1) == 0 {
+ term.move_cursor_down(1)?;
+ term.move_cursor_left(term.size().1 as usize)?;
+ } else {
+ term.move_cursor_right(1)?;
+ }
+ position += 1;
+ term.flush()?;
+ }
+ Key::UnknownEscSeq(seq) if seq == vec!['b'] => {
+ let line_size = term.size().1 as usize;
+ let nb_space = chars[..position]
+ .iter()
+ .rev()
+ .take_while(|c| c.is_whitespace())
+ .count();
+ let find_last_space = chars[..position - nb_space]
+ .iter()
+ .rposition(|c| c.is_whitespace());
+
+ // If we find a space we set the cursor to the next char else we set it to the beginning of the input
+ if let Some(mut last_space) = find_last_space {
+ if last_space < position {
+ last_space += 1;
+ let new_line = (prompt_len + last_space) / line_size;
+ let old_line = (prompt_len + position) / line_size;
+ let diff_line = old_line - new_line;
+ if diff_line != 0 {
+ term.move_cursor_up(old_line - new_line)?;
+ }
+
+ let new_pos_x = (prompt_len + last_space) % line_size;
+ let old_pos_x = (prompt_len + position) % line_size;
+ let diff_pos_x = new_pos_x as i64 - old_pos_x as i64;
+ //println!("new_pos_x = {}, old_pos_x = {}, diff = {}", new_pos_x, old_pos_x, diff_pos_x);
+ if diff_pos_x < 0 {
+ term.move_cursor_left(-diff_pos_x as usize)?;
+ } else {
+ term.move_cursor_right((diff_pos_x) as usize)?;
+ }
+ position = last_space;
+ }
+ } else {
+ term.move_cursor_left(position)?;
+ position = 0;
+ }
+
+ term.flush()?;
+ }
+ Key::UnknownEscSeq(seq) if seq == vec!['f'] => {
+ let line_size = term.size().1 as usize;
+ let find_next_space =
+ chars[position..].iter().position(|c| c.is_whitespace());
+
+ // If we find a space we set the cursor to the next char else we set it to the beginning of the input
+ if let Some(mut next_space) = find_next_space {
+ let nb_space = chars[position + next_space..]
+ .iter()
+ .take_while(|c| c.is_whitespace())
+ .count();
+ next_space += nb_space;
+ let new_line = (prompt_len + position + next_space) / line_size;
+ let old_line = (prompt_len + position) / line_size;
+ term.move_cursor_down(new_line - old_line)?;
+
+ let new_pos_x = (prompt_len + position + next_space) % line_size;
+ let old_pos_x = (prompt_len + position) % line_size;
+ let diff_pos_x = new_pos_x as i64 - old_pos_x as i64;
+ if diff_pos_x < 0 {
+ term.move_cursor_left(-diff_pos_x as usize)?;
+ } else {
+ term.move_cursor_right((diff_pos_x) as usize)?;
+ }
+ position += next_space;
+ } else {
+ let new_line = (prompt_len + chars.len()) / line_size;
+ let old_line = (prompt_len + position) / line_size;
+ term.move_cursor_down(new_line - old_line)?;
+
+ let new_pos_x = (prompt_len + chars.len()) % line_size;
+ let old_pos_x = (prompt_len + position) % line_size;
+ let diff_pos_x = new_pos_x as i64 - old_pos_x as i64;
+ match diff_pos_x.cmp(&0) {
+ Ordering::Less => {
+ term.move_cursor_left((-diff_pos_x - 1) as usize)?;
+ }
+ Ordering::Equal => {}
+ Ordering::Greater => {
+ term.move_cursor_right((diff_pos_x) as usize)?;
+ }
+ }
+ position = chars.len();
+ }
+
+ term.flush()?;
+ }
+ #[cfg(feature = "completion")]
+ Key::ArrowRight | Key::Tab => {
+ if let Some(completion) = &self.completion {
+ let input: String = chars.clone().into_iter().collect();
+ if let Some(x) = completion.get(&input) {
+ term.clear_chars(chars.len())?;
+ chars.clear();
+ position = 0;
+ for ch in x.chars() {
+ chars.insert(position, ch);
+ position += 1;
+ }
+ term.write_str(&x)?;
+ term.flush()?;
+ }
+ }
+ }
+ #[cfg(feature = "history")]
+ Key::ArrowUp => {
+ let line_size = term.size().1 as usize;
+ if let Some(history) = &self.history {
+ if let Some(previous) = history.read(hist_pos) {
+ hist_pos += 1;
+ let mut chars_len = chars.len();
+ while ((prompt_len + chars_len) / line_size) > 0 {
+ term.clear_chars(chars_len)?;
+ if (prompt_len + chars_len) % line_size == 0 {
+ chars_len -= std::cmp::min(chars_len, line_size);
+ } else {
+ chars_len -= std::cmp::min(
+ chars_len,
+ (prompt_len + chars_len + 1) % line_size,
+ );
+ }
+ if chars_len > 0 {
+ term.move_cursor_up(1)?;
+ term.move_cursor_right(line_size)?;
+ }
+ }
+ term.clear_chars(chars_len)?;
+ chars.clear();
+ position = 0;
+ for ch in previous.chars() {
+ chars.insert(position, ch);
+ position += 1;
+ }
+ term.write_str(&previous)?;
+ term.flush()?;
+ }
+ }
+ }
+ #[cfg(feature = "history")]
+ Key::ArrowDown => {
+ let line_size = term.size().1 as usize;
+ if let Some(history) = &self.history {
+ let mut chars_len = chars.len();
+ while ((prompt_len + chars_len) / line_size) > 0 {
+ term.clear_chars(chars_len)?;
+ if (prompt_len + chars_len) % line_size == 0 {
+ chars_len -= std::cmp::min(chars_len, line_size);
+ } else {
+ chars_len -= std::cmp::min(
+ chars_len,
+ (prompt_len + chars_len + 1) % line_size,
+ );
+ }
+ if chars_len > 0 {
+ term.move_cursor_up(1)?;
+ term.move_cursor_right(line_size)?;
+ }
+ }
+ term.clear_chars(chars_len)?;
+ chars.clear();
+ position = 0;
+ // Move the history position back one in case we have up arrowed into it
+ // and the position is sitting on the next to read
+ if let Some(pos) = hist_pos.checked_sub(1) {
+ hist_pos = pos;
+ // Move it back again to get the previous history entry
+ if let Some(pos) = pos.checked_sub(1) {
+ if let Some(previous) = history.read(pos) {
+ for ch in previous.chars() {
+ chars.insert(position, ch);
+ position += 1;
+ }
+ term.write_str(&previous)?;
+ }
+ }
+ }
+ term.flush()?;
+ }
+ }
+ Key::Enter => break,
+ _ => (),
+ }
+ }
+ let input = chars.iter().collect::<String>();
+
+ term.clear_line()?;
+ render.clear()?;
+
+ if chars.is_empty() {
+ if let Some(ref default) = self.default {
+ if let Some(ref mut validator) = self.validator {
+ if let Some(err) = validator(default) {
+ render.error(&err)?;
+ continue;
+ }
+ }
+
+ if self.report {
+ render.input_prompt_selection(&self.prompt, &default.to_string())?;
+ }
+ term.flush()?;
+ return Ok(default.clone());
+ } else if !self.permit_empty {
+ continue;
+ }
+ }
+
+ match input.parse::<T>() {
+ Ok(value) => {
+ if let Some(ref mut validator) = self.validator {
+ if let Some(err) = validator(&value) {
+ render.error(&err)?;
+ continue;
+ }
+ }
+
+ #[cfg(feature = "history")]
+ if let Some(history) = &mut self.history {
+ history.write(&value);
+ }
+
+ if self.report {
+ if let Some(post_completion_text) = &self.post_completion_text {
+ render.input_prompt_selection(post_completion_text, &input)?;
+ } else {
+ render.input_prompt_selection(&self.prompt, &input)?;
+ }
+ }
+ term.flush()?;
+
+ return Ok(value);
+ }
+ Err(err) => {
+ render.error(&err.to_string())?;
+ continue;
+ }
+ }
+ }
+ }
+}
+
+impl<T> Input<'_, T>
+where
+ T: Clone + ToString + FromStr,
+ <T as FromStr>::Err: ToString,
+{
+ /// Enables user interaction and returns the result.
+ ///
+ /// Allows any characters as input, including e.g arrow keys.
+ /// Some of the keys might have undesired behavior.
+ /// For more limited version, see [`interact_text`](#method.interact_text).
+ ///
+ /// If the user confirms the result is `true`, `false` otherwise.
+ /// The dialog is rendered on stderr.
+ pub fn interact(&mut self) -> io::Result<T> {
+ self.interact_on(&Term::stderr())
+ }
+
+ /// Like [`interact`](#method.interact) but allows a specific terminal to be set.
+ pub fn interact_on(&mut self, term: &Term) -> io::Result<T> {
+ let mut render = TermThemeRenderer::new(term, self.theme);
+
+ loop {
+ let default_string = self.default.as_ref().map(ToString::to_string);
+
+ render.input_prompt(
+ &self.prompt,
+ if self.show_default {
+ default_string.as_deref()
+ } else {
+ None
+ },
+ )?;
+ term.flush()?;
+
+ let input = if let Some(initial_text) = self.initial_text.as_ref() {
+ term.read_line_initial_text(initial_text)?
+ } else {
+ term.read_line()?
+ };
+
+ render.add_line();
+ term.clear_line()?;
+ render.clear()?;
+
+ if input.is_empty() {
+ if let Some(ref default) = self.default {
+ if let Some(ref mut validator) = self.validator {
+ if let Some(err) = validator(default) {
+ render.error(&err)?;
+ continue;
+ }
+ }
+
+ if self.report {
+ render.input_prompt_selection(&self.prompt, &default.to_string())?;
+ }
+ term.flush()?;
+ return Ok(default.clone());
+ } else if !self.permit_empty {
+ continue;
+ }
+ }
+
+ match input.parse::<T>() {
+ Ok(value) => {
+ if let Some(ref mut validator) = self.validator {
+ if let Some(err) = validator(&value) {
+ render.error(&err)?;
+ continue;
+ }
+ }
+
+ if self.report {
+ render.input_prompt_selection(&self.prompt, &input)?;
+ }
+ term.flush()?;
+
+ return Ok(value);
+ }
+ Err(err) => {
+ render.error(&err.to_string())?;
+ continue;
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/dialoguer/src/prompts/mod.rs b/vendor/dialoguer/src/prompts/mod.rs
new file mode 100644
index 0000000..1c13185
--- /dev/null
+++ b/vendor/dialoguer/src/prompts/mod.rs
@@ -0,0 +1,13 @@
+#![allow(clippy::needless_doctest_main)]
+
+pub mod confirm;
+pub mod input;
+pub mod multi_select;
+pub mod select;
+pub mod sort;
+
+#[cfg(feature = "fuzzy-select")]
+pub mod fuzzy_select;
+
+#[cfg(feature = "password")]
+pub mod password;
diff --git a/vendor/dialoguer/src/prompts/multi_select.rs b/vendor/dialoguer/src/prompts/multi_select.rs
new file mode 100644
index 0000000..eed55a1
--- /dev/null
+++ b/vendor/dialoguer/src/prompts/multi_select.rs
@@ -0,0 +1,356 @@
+use std::{io, iter::repeat, ops::Rem};
+
+use crate::{
+ theme::{SimpleTheme, TermThemeRenderer, Theme},
+ Paging,
+};
+
+use console::{Key, Term};
+
+/// Renders a multi select prompt.
+///
+/// ## Example usage
+/// ```rust,no_run
+/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
+/// use dialoguer::MultiSelect;
+///
+/// let items = vec!["Option 1", "Option 2"];
+/// let chosen : Vec<usize> = MultiSelect::new()
+/// .items(&items)
+/// .interact()?;
+/// # Ok(())
+/// # }
+/// ```
+pub struct MultiSelect<'a> {
+ defaults: Vec<bool>,
+ items: Vec<String>,
+ prompt: Option<String>,
+ report: bool,
+ clear: bool,
+ max_length: Option<usize>,
+ theme: &'a dyn Theme,
+}
+
+impl Default for MultiSelect<'static> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl MultiSelect<'static> {
+ /// Creates a multi select prompt.
+ pub fn new() -> Self {
+ Self::with_theme(&SimpleTheme)
+ }
+}
+
+impl MultiSelect<'_> {
+ /// Sets the clear behavior of the menu.
+ ///
+ /// The default is to clear the menu.
+ pub fn clear(&mut self, val: bool) -> &mut Self {
+ self.clear = val;
+ self
+ }
+
+ /// Sets a defaults for the menu.
+ pub fn defaults(&mut self, val: &[bool]) -> &mut Self {
+ self.defaults = val
+ .to_vec()
+ .iter()
+ .copied()
+ .chain(repeat(false))
+ .take(self.items.len())
+ .collect();
+ 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.
+ #[inline]
+ pub fn item<T: ToString>(&mut self, item: T) -> &mut Self {
+ self.item_checked(item, false)
+ }
+
+ /// Add a single item to the selector with a default checked state.
+ pub fn item_checked<T: ToString>(&mut self, item: T, checked: bool) -> &mut Self {
+ self.items.push(item.to_string());
+ self.defaults.push(checked);
+ self
+ }
+
+ /// Adds multiple items to the selector.
+ pub fn items<T: ToString>(&mut self, items: &[T]) -> &mut Self {
+ for item in items {
+ self.items.push(item.to_string());
+ self.defaults.push(false);
+ }
+ self
+ }
+
+ /// Adds multiple items to the selector with checked state
+ pub fn items_checked<T: ToString>(&mut self, items: &[(T, bool)]) -> &mut Self {
+ for &(ref item, checked) in items {
+ self.items.push(item.to_string());
+ self.defaults.push(checked);
+ }
+ self
+ }
+
+ /// Prefaces the menu with a 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).
+ pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Self {
+ self.prompt = Some(prompt.into());
+ self
+ }
+
+ /// Indicates whether to report the selected values after interaction.
+ ///
+ /// The default is to report the selections.
+ 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 and on 'Enter' the indices of selected items will be returned.
+ /// The dialog is rendered on stderr.
+ /// Result contains `Vec<index>` if user hit 'Enter'.
+ /// This unlike [`interact_opt`](Self::interact_opt) does not allow to quit with 'Esc' or 'q'.
+ #[inline]
+ pub fn interact(&self) -> io::Result<Vec<usize>> {
+ self.interact_on(&Term::stderr())
+ }
+
+ /// Enables user interaction and returns the result.
+ ///
+ /// The user can select the items with the 'Space' bar and on 'Enter' the indices of selected items will be returned.
+ /// The dialog is rendered on stderr.
+ /// Result contains `Some(Vec<index>)` if user hit 'Enter' or `None` if user cancelled with 'Esc' or 'q'.
+ #[inline]
+ pub fn interact_opt(&self) -> io::Result<Option<Vec<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::MultiSelect;
+ /// use console::Term;
+ ///
+ /// fn main() -> std::io::Result<()> {
+ /// let selections = MultiSelect::new()
+ /// .item("Option A")
+ /// .item("Option B")
+ /// .interact_on(&Term::stderr())?;
+ ///
+ /// println!("User selected options at indices {:?}", selections);
+ ///
+ /// Ok(())
+ /// }
+ ///```
+ #[inline]
+ pub fn interact_on(&self, term: &Term) -> io::Result<Vec<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::MultiSelect;
+ /// use console::Term;
+ ///
+ /// fn main() -> std::io::Result<()> {
+ /// let selections = MultiSelect::new()
+ /// .item("Option A")
+ /// .item("Option B")
+ /// .interact_on_opt(&Term::stdout())?;
+ ///
+ /// match selections {
+ /// Some(positions) => println!("User selected options at indices {:?}", positions),
+ /// None => println!("User exited using Esc or q")
+ /// }
+ ///
+ /// Ok(())
+ /// }
+ /// ```
+ #[inline]
+ pub fn interact_on_opt(&self, term: &Term) -> io::Result<Option<Vec<usize>>> {
+ self._interact_on(term, true)
+ }
+
+ fn _interact_on(&self, term: &Term, allow_quit: bool) -> io::Result<Option<Vec<usize>>> {
+ if self.items.is_empty() {
+ return Err(io::Error::new(
+ io::ErrorKind::Other,
+ "Empty list of items given to `MultiSelect`",
+ ));
+ }
+
+ let mut paging = Paging::new(term, self.items.len(), self.max_length);
+ let mut render = TermThemeRenderer::new(term, self.theme);
+ let mut sel = 0;
+
+ 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);
+ }
+
+ let mut checked: Vec<bool> = self.defaults.clone();
+
+ term.hide_cursor()?;
+
+ loop {
+ if let Some(ref prompt) = self.prompt {
+ paging
+ .render_prompt(|paging_info| render.multi_select_prompt(prompt, paging_info))?;
+ }
+
+ for (idx, item) in self
+ .items
+ .iter()
+ .enumerate()
+ .skip(paging.current_page * paging.capacity)
+ .take(paging.capacity)
+ {
+ render.multi_select_prompt_item(item, checked[idx], 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::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::Char(' ') => {
+ checked[sel] = !checked[sel];
+ }
+ Key::Char('a') => {
+ if checked.iter().all(|&item_checked| item_checked) {
+ checked.fill(false);
+ } else {
+ checked.fill(true);
+ }
+ }
+ 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::Enter => {
+ if self.clear {
+ render.clear()?;
+ }
+
+ if let Some(ref prompt) = self.prompt {
+ if self.report {
+ let selections: Vec<_> = checked
+ .iter()
+ .enumerate()
+ .filter_map(|(idx, &checked)| {
+ if checked {
+ Some(self.items[idx].as_str())
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ render.multi_select_prompt_selection(prompt, &selections[..])?;
+ }
+ }
+
+ term.show_cursor()?;
+ term.flush()?;
+
+ return Ok(Some(
+ checked
+ .into_iter()
+ .enumerate()
+ .filter_map(|(idx, checked)| if checked { Some(idx) } else { None })
+ .collect(),
+ ));
+ }
+ _ => {}
+ }
+
+ paging.update(sel)?;
+
+ if paging.active {
+ render.clear()?;
+ } else {
+ render.clear_preserve_prompt(&size_vec)?;
+ }
+ }
+ }
+}
+
+impl<'a> MultiSelect<'a> {
+ /// Creates a multi select prompt with a specific theme.
+ pub fn with_theme(theme: &'a dyn Theme) -> Self {
+ Self {
+ items: vec![],
+ defaults: vec![],
+ clear: true,
+ prompt: None,
+ report: true,
+ max_length: None,
+ theme,
+ }
+ }
+}
diff --git a/vendor/dialoguer/src/prompts/password.rs b/vendor/dialoguer/src/prompts/password.rs
new file mode 100644
index 0000000..7327605
--- /dev/null
+++ b/vendor/dialoguer/src/prompts/password.rs
@@ -0,0 +1,194 @@
+use std::io;
+
+use crate::{
+ theme::{SimpleTheme, TermThemeRenderer, Theme},
+ validate::PasswordValidator,
+};
+
+use console::Term;
+use zeroize::Zeroizing;
+
+type PasswordValidatorCallback<'a> = Box<dyn Fn(&String) -> Option<String> + 'a>;
+
+/// Renders a password input prompt.
+///
+/// ## Example usage
+///
+/// ```rust,no_run
+/// # fn test() -> Result<(), Box<std::error::Error>> {
+/// use dialoguer::Password;
+///
+/// let password = Password::new().with_prompt("New Password")
+/// .with_confirmation("Confirm password", "Passwords mismatching")
+/// .interact()?;
+/// println!("Length of the password is: {}", password.len());
+/// # Ok(()) } fn main() { test().unwrap(); }
+/// ```
+pub struct Password<'a> {
+ prompt: String,
+ report: bool,
+ theme: &'a dyn Theme,
+ allow_empty_password: bool,
+ confirmation_prompt: Option<(String, String)>,
+ validator: Option<PasswordValidatorCallback<'a>>,
+}
+
+impl Default for Password<'static> {
+ fn default() -> Password<'static> {
+ Self::new()
+ }
+}
+
+impl Password<'static> {
+ /// Creates a password input prompt.
+ pub fn new() -> Password<'static> {
+ Self::with_theme(&SimpleTheme)
+ }
+}
+
+impl<'a> Password<'a> {
+ /// Sets the password input prompt.
+ pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Self {
+ self.prompt = prompt.into();
+ self
+ }
+
+ /// Indicates whether to report confirmation after interaction.
+ ///
+ /// The default is to report.
+ pub fn report(&mut self, val: bool) -> &mut Self {
+ self.report = val;
+ self
+ }
+
+ /// Enables confirmation prompting.
+ pub fn with_confirmation<A, B>(&mut self, prompt: A, mismatch_err: B) -> &mut Self
+ where
+ A: Into<String>,
+ B: Into<String>,
+ {
+ self.confirmation_prompt = Some((prompt.into(), mismatch_err.into()));
+ self
+ }
+
+ /// Allows/Disables empty password.
+ ///
+ /// By default this setting is set to false (i.e. password is not empty).
+ pub fn allow_empty_password(&mut self, allow_empty_password: bool) -> &mut Self {
+ self.allow_empty_password = allow_empty_password;
+ self
+ }
+
+ /// Registers a validator.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// # use dialoguer::Password;
+ /// let password: String = Password::new()
+ /// .with_prompt("Enter password")
+ /// .validate_with(|input: &String| -> Result<(), &str> {
+ /// if input.len() > 8 {
+ /// Ok(())
+ /// } else {
+ /// Err("Password must be longer than 8")
+ /// }
+ /// })
+ /// .interact()
+ /// .unwrap();
+ /// ```
+ pub fn validate_with<V>(&mut self, validator: V) -> &mut Self
+ where
+ V: PasswordValidator + 'a,
+ V::Err: ToString,
+ {
+ let old_validator_func = self.validator.take();
+
+ self.validator = Some(Box::new(move |value: &String| -> Option<String> {
+ if let Some(old) = &old_validator_func {
+ if let Some(err) = old(value) {
+ return Some(err);
+ }
+ }
+
+ match validator.validate(value) {
+ Ok(()) => None,
+ Err(err) => Some(err.to_string()),
+ }
+ }));
+
+ self
+ }
+
+ /// Enables user interaction and returns the result.
+ ///
+ /// If the user confirms the result is `true`, `false` otherwise.
+ /// The dialog is rendered on stderr.
+ pub fn interact(&self) -> io::Result<String> {
+ self.interact_on(&Term::stderr())
+ }
+
+ /// Like `interact` but allows a specific terminal to be set.
+ pub fn interact_on(&self, term: &Term) -> io::Result<String> {
+ let mut render = TermThemeRenderer::new(term, self.theme);
+ render.set_prompts_reset_height(false);
+
+ loop {
+ let password = Zeroizing::new(self.prompt_password(&mut render, &self.prompt)?);
+
+ if let Some(ref validator) = self.validator {
+ if let Some(err) = validator(&password) {
+ render.error(&err)?;
+ continue;
+ }
+ }
+
+ if let Some((ref prompt, ref err)) = self.confirmation_prompt {
+ let pw2 = Zeroizing::new(self.prompt_password(&mut render, prompt)?);
+
+ if *password != *pw2 {
+ render.error(err)?;
+ continue;
+ }
+ }
+
+ render.clear()?;
+
+ if self.report {
+ render.password_prompt_selection(&self.prompt)?;
+ }
+ term.flush()?;
+
+ return Ok((*password).clone());
+ }
+ }
+
+ fn prompt_password(&self, render: &mut TermThemeRenderer, prompt: &str) -> io::Result<String> {
+ loop {
+ render.password_prompt(prompt)?;
+ render.term().flush()?;
+
+ let input = render.term().read_secure_line()?;
+
+ render.add_line();
+
+ if !input.is_empty() || self.allow_empty_password {
+ return Ok(input);
+ }
+ }
+ }
+}
+
+impl<'a> Password<'a> {
+ /// Creates a password input prompt with a specific theme.
+ pub fn with_theme(theme: &'a dyn Theme) -> Self {
+ Self {
+ prompt: "".into(),
+ report: true,
+ theme,
+ allow_empty_password: false,
+ confirmation_prompt: None,
+ validator: None,
+ }
+ }
+}
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
+ );
+ }
+}
diff --git a/vendor/dialoguer/src/prompts/sort.rs b/vendor/dialoguer/src/prompts/sort.rs
new file mode 100644
index 0000000..03152bf
--- /dev/null
+++ b/vendor/dialoguer/src/prompts/sort.rs
@@ -0,0 +1,348 @@
+use std::{io, ops::Rem};
+
+use crate::{
+ theme::{SimpleTheme, TermThemeRenderer, Theme},
+ Paging,
+};
+
+use console::{Key, Term};
+
+/// Renders a sort prompt.
+///
+/// Returns list of indices in original items list sorted according to user input.
+///
+/// ## Example usage
+/// ```rust,no_run
+/// use dialoguer::Sort;
+///
+/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
+/// let items_to_order = vec!["Item 1", "Item 2", "Item 3"];
+/// let ordered = Sort::new()
+/// .with_prompt("Order the items")
+/// .items(&items_to_order)
+/// .interact()?;
+/// # Ok(())
+/// # }
+/// ```
+pub struct Sort<'a> {
+ items: Vec<String>,
+ prompt: Option<String>,
+ report: bool,
+ clear: bool,
+ max_length: Option<usize>,
+ theme: &'a dyn Theme,
+}
+
+impl Default for Sort<'static> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Sort<'static> {
+ /// Creates a sort prompt.
+ pub fn new() -> Self {
+ Self::with_theme(&SimpleTheme)
+ }
+}
+
+impl Sort<'_> {
+ /// Sets the clear behavior of the menu.
+ ///
+ /// The default is to clear the menu after user interaction.
+ pub fn clear(&mut self, val: bool) -> &mut Self {
+ self.clear = 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.
+ pub fn item<T: ToString>(&mut self, item: T) -> &mut Self {
+ self.items.push(item.to_string());
+ self
+ }
+
+ /// Adds multiple items to the selector.
+ pub fn items<T: ToString>(&mut self, items: &[T]) -> &mut Self {
+ for item in items {
+ self.items.push(item.to_string());
+ }
+ self
+ }
+
+ /// Prefaces the menu with a 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).
+ pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Self {
+ self.prompt = Some(prompt.into());
+ self
+ }
+
+ /// Indicates whether to report the selected order after interaction.
+ ///
+ /// The default is to report the selected order.
+ pub fn report(&mut self, val: bool) -> &mut Self {
+ self.report = val;
+ self
+ }
+
+ /// Enables user interaction and returns the result.
+ ///
+ /// The user can order the items with the 'Space' bar and the arrows. On 'Enter' ordered list of the incides of items will be returned.
+ /// The dialog is rendered on stderr.
+ /// Result contains `Vec<index>` if user hit 'Enter'.
+ /// This unlike [`interact_opt`](Self::interact_opt) does not allow to quit with 'Esc' or 'q'.
+ #[inline]
+ pub fn interact(&self) -> io::Result<Vec<usize>> {
+ self.interact_on(&Term::stderr())
+ }
+
+ /// Enables user interaction and returns the result.
+ ///
+ /// The user can order the items with the 'Space' bar and the arrows. On 'Enter' ordered list of the incides of items will be returned.
+ /// The dialog is rendered on stderr.
+ /// Result contains `Some(Vec<index>)` if user hit 'Enter' or `None` if user cancelled with 'Esc' or 'q'.
+ #[inline]
+ pub fn interact_opt(&self) -> io::Result<Option<Vec<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::Sort;
+ /// use console::Term;
+ ///
+ /// fn main() -> std::io::Result<()> {
+ /// let selections = Sort::new()
+ /// .item("Option A")
+ /// .item("Option B")
+ /// .interact_on(&Term::stderr())?;
+ ///
+ /// println!("User sorted options as indices {:?}", selections);
+ ///
+ /// Ok(())
+ /// }
+ ///```
+ #[inline]
+ pub fn interact_on(&self, term: &Term) -> io::Result<Vec<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::Sort;
+ /// use console::Term;
+ ///
+ /// fn main() -> std::io::Result<()> {
+ /// let selections = Sort::new()
+ /// .item("Option A")
+ /// .item("Option B")
+ /// .interact_on_opt(&Term::stdout())?;
+ ///
+ /// match selections {
+ /// Some(positions) => println!("User sorted options as indices {:?}", positions),
+ /// None => println!("User exited using Esc or q")
+ /// }
+ ///
+ /// Ok(())
+ /// }
+ /// ```
+ #[inline]
+ pub fn interact_on_opt(&self, term: &Term) -> io::Result<Option<Vec<usize>>> {
+ self._interact_on(term, true)
+ }
+
+ fn _interact_on(&self, term: &Term, allow_quit: bool) -> io::Result<Option<Vec<usize>>> {
+ if self.items.is_empty() {
+ return Err(io::Error::new(
+ io::ErrorKind::Other,
+ "Empty list of items given to `Sort`",
+ ));
+ }
+
+ let mut paging = Paging::new(term, self.items.len(), self.max_length);
+ let mut render = TermThemeRenderer::new(term, self.theme);
+ let mut sel = 0;
+
+ let mut size_vec = Vec::new();
+
+ for items in self.items.iter().as_slice() {
+ let size = &items.len();
+ size_vec.push(*size);
+ }
+
+ let mut order: Vec<_> = (0..self.items.len()).collect();
+ let mut checked: bool = false;
+
+ term.hide_cursor()?;
+
+ loop {
+ if let Some(ref prompt) = self.prompt {
+ paging.render_prompt(|paging_info| render.sort_prompt(prompt, paging_info))?;
+ }
+
+ for (idx, item) in order
+ .iter()
+ .enumerate()
+ .skip(paging.current_page * paging.capacity)
+ .take(paging.capacity)
+ {
+ render.sort_prompt_item(&self.items[*item], checked, sel == idx)?;
+ }
+
+ term.flush()?;
+
+ match term.read_key()? {
+ Key::ArrowDown | Key::Tab | Key::Char('j') => {
+ let old_sel = sel;
+
+ if sel == !0 {
+ sel = 0;
+ } else {
+ sel = (sel as u64 + 1).rem(self.items.len() as u64) as usize;
+ }
+
+ if checked && old_sel != sel {
+ order.swap(old_sel, sel);
+ }
+ }
+ Key::ArrowUp | Key::BackTab | Key::Char('k') => {
+ let old_sel = sel;
+
+ 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;
+ }
+
+ if checked && old_sel != sel {
+ order.swap(old_sel, sel);
+ }
+ }
+ Key::ArrowLeft | Key::Char('h') => {
+ if paging.active {
+ let old_sel = sel;
+ let old_page = paging.current_page;
+
+ sel = paging.previous_page();
+
+ if checked {
+ let indexes: Vec<_> = if old_page == 0 {
+ let indexes1: Vec<_> = (0..=old_sel).rev().collect();
+ let indexes2: Vec<_> = (sel..self.items.len()).rev().collect();
+ [indexes1, indexes2].concat()
+ } else {
+ (sel..=old_sel).rev().collect()
+ };
+
+ for index in 0..(indexes.len() - 1) {
+ order.swap(indexes[index], indexes[index + 1]);
+ }
+ }
+ }
+ }
+ Key::ArrowRight | Key::Char('l') => {
+ if paging.active {
+ let old_sel = sel;
+ let old_page = paging.current_page;
+
+ sel = paging.next_page();
+
+ if checked {
+ let indexes: Vec<_> = if old_page == paging.pages - 1 {
+ let indexes1: Vec<_> = (old_sel..self.items.len()).collect();
+ let indexes2: Vec<_> = vec![0];
+ [indexes1, indexes2].concat()
+ } else {
+ (old_sel..=sel).collect()
+ };
+
+ for index in 0..(indexes.len() - 1) {
+ order.swap(indexes[index], indexes[index + 1]);
+ }
+ }
+ }
+ }
+ Key::Char(' ') => {
+ checked = !checked;
+ }
+ 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::Enter => {
+ if self.clear {
+ render.clear()?;
+ }
+
+ if let Some(ref prompt) = self.prompt {
+ if self.report {
+ let list: Vec<_> = order
+ .iter()
+ .enumerate()
+ .map(|(_, item)| self.items[*item].as_str())
+ .collect();
+ render.sort_prompt_selection(prompt, &list[..])?;
+ }
+ }
+
+ term.show_cursor()?;
+ term.flush()?;
+
+ return Ok(Some(order));
+ }
+ _ => {}
+ }
+
+ paging.update(sel)?;
+
+ if paging.active {
+ render.clear()?;
+ } else {
+ render.clear_preserve_prompt(&size_vec)?;
+ }
+ }
+ }
+}
+
+impl<'a> Sort<'a> {
+ /// Creates a sort prompt with a specific theme.
+ pub fn with_theme(theme: &'a dyn Theme) -> Self {
+ Self {
+ items: vec![],
+ clear: true,
+ prompt: None,
+ report: true,
+ max_length: None,
+ theme,
+ }
+ }
+}