Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
This commit is contained in:
		
							
								
								
									
										4
									
								
								vendor/dialoguer/src/completion.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								vendor/dialoguer/src/completion.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
/// Trait for completion handling.
 | 
			
		||||
pub trait Completion {
 | 
			
		||||
    fn get(&self, input: &str) -> Option<String>;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										131
									
								
								vendor/dialoguer/src/edit.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								vendor/dialoguer/src/edit.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,131 @@
 | 
			
		||||
use std::{
 | 
			
		||||
    env,
 | 
			
		||||
    ffi::{OsStr, OsString},
 | 
			
		||||
    fs, io,
 | 
			
		||||
    io::{Read, Write},
 | 
			
		||||
    process,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Launches the default editor to edit a string.
 | 
			
		||||
///
 | 
			
		||||
/// ## Example
 | 
			
		||||
///
 | 
			
		||||
/// ```rust,no_run
 | 
			
		||||
/// use dialoguer::Editor;
 | 
			
		||||
///
 | 
			
		||||
/// if let Some(rv) = Editor::new().edit("Enter a commit message").unwrap() {
 | 
			
		||||
///     println!("Your message:");
 | 
			
		||||
///     println!("{}", rv);
 | 
			
		||||
/// } else {
 | 
			
		||||
///     println!("Abort!");
 | 
			
		||||
/// }
 | 
			
		||||
/// ```
 | 
			
		||||
pub struct Editor {
 | 
			
		||||
    editor: OsString,
 | 
			
		||||
    extension: String,
 | 
			
		||||
    require_save: bool,
 | 
			
		||||
    trim_newlines: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn get_default_editor() -> OsString {
 | 
			
		||||
    if let Some(prog) = env::var_os("VISUAL") {
 | 
			
		||||
        return prog;
 | 
			
		||||
    }
 | 
			
		||||
    if let Some(prog) = env::var_os("EDITOR") {
 | 
			
		||||
        return prog;
 | 
			
		||||
    }
 | 
			
		||||
    if cfg!(windows) {
 | 
			
		||||
        "notepad.exe".into()
 | 
			
		||||
    } else {
 | 
			
		||||
        "vi".into()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for Editor {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self::new()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Editor {
 | 
			
		||||
    /// Creates a new editor.
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            editor: get_default_editor(),
 | 
			
		||||
            extension: ".txt".into(),
 | 
			
		||||
            require_save: true,
 | 
			
		||||
            trim_newlines: true,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Sets a specific editor executable.
 | 
			
		||||
    pub fn executable<S: AsRef<OsStr>>(&mut self, val: S) -> &mut Self {
 | 
			
		||||
        self.editor = val.as_ref().into();
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Sets a specific extension
 | 
			
		||||
    pub fn extension(&mut self, val: &str) -> &mut Self {
 | 
			
		||||
        self.extension = val.into();
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Enables or disables the save requirement.
 | 
			
		||||
    pub fn require_save(&mut self, val: bool) -> &mut Self {
 | 
			
		||||
        self.require_save = val;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Enables or disables trailing newline stripping.
 | 
			
		||||
    ///
 | 
			
		||||
    /// This is on by default.
 | 
			
		||||
    pub fn trim_newlines(&mut self, val: bool) -> &mut Self {
 | 
			
		||||
        self.trim_newlines = val;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Launches the editor to edit a string.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Returns `None` if the file was not saved or otherwise the
 | 
			
		||||
    /// entered text.
 | 
			
		||||
    pub fn edit(&self, s: &str) -> io::Result<Option<String>> {
 | 
			
		||||
        let mut f = tempfile::Builder::new()
 | 
			
		||||
            .prefix("edit-")
 | 
			
		||||
            .suffix(&self.extension)
 | 
			
		||||
            .rand_bytes(12)
 | 
			
		||||
            .tempfile()?;
 | 
			
		||||
        f.write_all(s.as_bytes())?;
 | 
			
		||||
        f.flush()?;
 | 
			
		||||
        let ts = fs::metadata(f.path())?.modified()?;
 | 
			
		||||
 | 
			
		||||
        let s: String = self.editor.clone().into_string().unwrap();
 | 
			
		||||
        let (cmd, args) = match shell_words::split(&s) {
 | 
			
		||||
            Ok(mut parts) => {
 | 
			
		||||
                let cmd = parts.remove(0);
 | 
			
		||||
                (cmd, parts)
 | 
			
		||||
            }
 | 
			
		||||
            Err(_) => (s, vec![]),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let rv = process::Command::new(cmd)
 | 
			
		||||
            .args(args)
 | 
			
		||||
            .arg(f.path())
 | 
			
		||||
            .spawn()?
 | 
			
		||||
            .wait()?;
 | 
			
		||||
 | 
			
		||||
        if rv.success() && self.require_save && ts >= fs::metadata(f.path())?.modified()? {
 | 
			
		||||
            return Ok(None);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut new_f = fs::File::open(f.path())?;
 | 
			
		||||
        let mut rv = String::new();
 | 
			
		||||
        new_f.read_to_string(&mut rv)?;
 | 
			
		||||
 | 
			
		||||
        if self.trim_newlines {
 | 
			
		||||
            let len = rv.trim_end_matches(&['\n', '\r'][..]).len();
 | 
			
		||||
            rv.truncate(len);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(Some(rv))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								vendor/dialoguer/src/history.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								vendor/dialoguer/src/history.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
/// Trait for history handling.
 | 
			
		||||
pub trait History<T> {
 | 
			
		||||
    /// This is called with the current position that should
 | 
			
		||||
    /// be read from history. The `pos` represents the number
 | 
			
		||||
    /// of times the `Up`/`Down` arrow key has been pressed.
 | 
			
		||||
    /// This would normally be used as an index to some sort
 | 
			
		||||
    /// of vector.  If the `pos` does not have an entry, [`None`](Option::None)
 | 
			
		||||
    /// should be returned.
 | 
			
		||||
    fn read(&self, pos: usize) -> Option<String>;
 | 
			
		||||
 | 
			
		||||
    /// This is called with the next value you should store
 | 
			
		||||
    /// in history at the first location. Normally history
 | 
			
		||||
    /// is implemented as a FIFO queue.
 | 
			
		||||
    fn write(&mut self, val: &T);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										62
									
								
								vendor/dialoguer/src/lib.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								vendor/dialoguer/src/lib.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
//! dialoguer is a library for Rust that helps you build useful small
 | 
			
		||||
//! interactive user inputs for the command line.  It provides utilities
 | 
			
		||||
//! to render various simple dialogs like confirmation prompts, text
 | 
			
		||||
//! inputs and more.
 | 
			
		||||
//!
 | 
			
		||||
//! Best paired with other libraries in the family:
 | 
			
		||||
//!
 | 
			
		||||
//! * [indicatif](https://docs.rs/indicatif)
 | 
			
		||||
//! * [console](https://docs.rs/console)
 | 
			
		||||
//!
 | 
			
		||||
//! # Crate Contents
 | 
			
		||||
//!
 | 
			
		||||
//! * Confirmation prompts
 | 
			
		||||
//! * Input prompts (regular and password)
 | 
			
		||||
//! * Input validation
 | 
			
		||||
//! * Selections prompts (single and multi)
 | 
			
		||||
//! * Fuzzy select prompt
 | 
			
		||||
//! * Other kind of prompts
 | 
			
		||||
//! * Editor launching
 | 
			
		||||
//!
 | 
			
		||||
//! # Crate Features
 | 
			
		||||
//!
 | 
			
		||||
//! The following crate features are available:
 | 
			
		||||
//! * `editor`: enables bindings to launch editor to edit strings
 | 
			
		||||
//! * `fuzzy-select`: enables fuzzy select prompt
 | 
			
		||||
//! * `history`: enables input prompts to be able to track history of inputs
 | 
			
		||||
//! * `password`: enables password input prompt
 | 
			
		||||
//! * `completion`: enables ability to implement custom tab-completion for input prompts
 | 
			
		||||
//!
 | 
			
		||||
//! By default `editor` and `password` are enabled.
 | 
			
		||||
 | 
			
		||||
#![deny(clippy::all)]
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "completion")]
 | 
			
		||||
pub use completion::Completion;
 | 
			
		||||
pub use console;
 | 
			
		||||
#[cfg(feature = "editor")]
 | 
			
		||||
pub use edit::Editor;
 | 
			
		||||
#[cfg(feature = "history")]
 | 
			
		||||
pub use history::History;
 | 
			
		||||
use paging::Paging;
 | 
			
		||||
pub use prompts::{
 | 
			
		||||
    confirm::Confirm, input::Input, multi_select::MultiSelect, select::Select, sort::Sort,
 | 
			
		||||
};
 | 
			
		||||
pub use validate::Validator;
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "fuzzy-select")]
 | 
			
		||||
pub use prompts::fuzzy_select::FuzzySelect;
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "password")]
 | 
			
		||||
pub use prompts::password::Password;
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "completion")]
 | 
			
		||||
mod completion;
 | 
			
		||||
#[cfg(feature = "editor")]
 | 
			
		||||
mod edit;
 | 
			
		||||
#[cfg(feature = "history")]
 | 
			
		||||
mod history;
 | 
			
		||||
mod paging;
 | 
			
		||||
mod prompts;
 | 
			
		||||
pub mod theme;
 | 
			
		||||
mod validate;
 | 
			
		||||
							
								
								
									
										118
									
								
								vendor/dialoguer/src/paging.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								vendor/dialoguer/src/paging.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
			
		||||
use std::io;
 | 
			
		||||
 | 
			
		||||
use console::Term;
 | 
			
		||||
 | 
			
		||||
/// Creates a paging module
 | 
			
		||||
///
 | 
			
		||||
/// The paging module serves as tracking structure to allow paged views
 | 
			
		||||
/// and automatically (de-)activates paging depending on the current terminal size.
 | 
			
		||||
pub struct Paging<'a> {
 | 
			
		||||
    pub pages: usize,
 | 
			
		||||
    pub current_page: usize,
 | 
			
		||||
    pub capacity: usize,
 | 
			
		||||
    pub active: bool,
 | 
			
		||||
    pub max_capacity: Option<usize>,
 | 
			
		||||
    term: &'a Term,
 | 
			
		||||
    current_term_size: (u16, u16),
 | 
			
		||||
    items_len: usize,
 | 
			
		||||
    activity_transition: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> Paging<'a> {
 | 
			
		||||
    pub fn new(term: &'a Term, items_len: usize, max_capacity: Option<usize>) -> Paging<'a> {
 | 
			
		||||
        let term_size = term.size();
 | 
			
		||||
        // Subtract -2 because we need space to render the prompt, if paging is active
 | 
			
		||||
        let capacity = max_capacity
 | 
			
		||||
            .unwrap_or(std::usize::MAX)
 | 
			
		||||
            .clamp(3, term_size.0 as usize)
 | 
			
		||||
            - 2;
 | 
			
		||||
        let pages = (items_len as f64 / capacity as f64).ceil() as usize;
 | 
			
		||||
 | 
			
		||||
        Paging {
 | 
			
		||||
            pages,
 | 
			
		||||
            current_page: 0,
 | 
			
		||||
            capacity,
 | 
			
		||||
            active: pages > 1,
 | 
			
		||||
            term,
 | 
			
		||||
            current_term_size: term_size,
 | 
			
		||||
            items_len,
 | 
			
		||||
            max_capacity,
 | 
			
		||||
            // Set transition initially to true to trigger prompt rendering for inactive paging on start
 | 
			
		||||
            activity_transition: true,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Updates all internal based on the current terminal size and cursor position
 | 
			
		||||
    pub fn update(&mut self, cursor_pos: usize) -> io::Result<()> {
 | 
			
		||||
        let new_term_size = self.term.size();
 | 
			
		||||
 | 
			
		||||
        if self.current_term_size != new_term_size {
 | 
			
		||||
            self.current_term_size = new_term_size;
 | 
			
		||||
            self.capacity = self
 | 
			
		||||
                .max_capacity
 | 
			
		||||
                .unwrap_or(std::usize::MAX)
 | 
			
		||||
                .clamp(3, self.current_term_size.0 as usize)
 | 
			
		||||
                - 2;
 | 
			
		||||
            self.pages = (self.items_len as f64 / self.capacity as f64).ceil() as usize;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if self.active == (self.pages > 1) {
 | 
			
		||||
            self.activity_transition = false;
 | 
			
		||||
        } else {
 | 
			
		||||
            self.active = self.pages > 1;
 | 
			
		||||
            self.activity_transition = true;
 | 
			
		||||
            // Clear everything to prevent "ghost" lines in terminal when a resize happened
 | 
			
		||||
            self.term.clear_last_lines(self.capacity)?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if cursor_pos != !0
 | 
			
		||||
            && (cursor_pos < self.current_page * self.capacity
 | 
			
		||||
                || cursor_pos >= (self.current_page + 1) * self.capacity)
 | 
			
		||||
        {
 | 
			
		||||
            self.current_page = cursor_pos / self.capacity;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Renders a prompt when the following conditions are met:
 | 
			
		||||
    /// * Paging is active
 | 
			
		||||
    /// * Transition of the paging activity happened (active -> inactive / inactive -> active)
 | 
			
		||||
    pub fn render_prompt<F>(&mut self, mut render_prompt: F) -> io::Result<()>
 | 
			
		||||
    where
 | 
			
		||||
        F: FnMut(Option<(usize, usize)>) -> io::Result<()>,
 | 
			
		||||
    {
 | 
			
		||||
        if self.active {
 | 
			
		||||
            let paging_info = Some((self.current_page + 1, self.pages));
 | 
			
		||||
            render_prompt(paging_info)?;
 | 
			
		||||
        } else if self.activity_transition {
 | 
			
		||||
            render_prompt(None)?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.term.flush()?;
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Navigates to the next page
 | 
			
		||||
    pub fn next_page(&mut self) -> usize {
 | 
			
		||||
        if self.current_page == self.pages - 1 {
 | 
			
		||||
            self.current_page = 0;
 | 
			
		||||
        } else {
 | 
			
		||||
            self.current_page += 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.current_page * self.capacity
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Navigates to the previous page
 | 
			
		||||
    pub fn previous_page(&mut self) -> usize {
 | 
			
		||||
        if self.current_page == 0 {
 | 
			
		||||
            self.current_page = self.pages - 1;
 | 
			
		||||
        } else {
 | 
			
		||||
            self.current_page -= 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.current_page * self.capacity
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										287
									
								
								vendor/dialoguer/src/prompts/confirm.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								vendor/dialoguer/src/prompts/confirm.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -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,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										326
									
								
								vendor/dialoguer/src/prompts/fuzzy_select.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										326
									
								
								vendor/dialoguer/src/prompts/fuzzy_select.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -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(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										691
									
								
								vendor/dialoguer/src/prompts/input.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										691
									
								
								vendor/dialoguer/src/prompts/input.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								vendor/dialoguer/src/prompts/mod.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								vendor/dialoguer/src/prompts/mod.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
							
								
								
									
										356
									
								
								vendor/dialoguer/src/prompts/multi_select.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										356
									
								
								vendor/dialoguer/src/prompts/multi_select.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -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,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										194
									
								
								vendor/dialoguer/src/prompts/password.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								vendor/dialoguer/src/prompts/password.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -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,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										419
									
								
								vendor/dialoguer/src/prompts/select.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										419
									
								
								vendor/dialoguer/src/prompts/select.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										348
									
								
								vendor/dialoguer/src/prompts/sort.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										348
									
								
								vendor/dialoguer/src/prompts/sort.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -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,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										976
									
								
								vendor/dialoguer/src/theme.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										976
									
								
								vendor/dialoguer/src/theme.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,976 @@
 | 
			
		||||
//! Customizes the rendering of the elements.
 | 
			
		||||
use std::{fmt, io};
 | 
			
		||||
 | 
			
		||||
use console::{measure_text_width, style, Style, StyledObject, Term};
 | 
			
		||||
#[cfg(feature = "fuzzy-select")]
 | 
			
		||||
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
 | 
			
		||||
 | 
			
		||||
/// Implements a theme for dialoguer.
 | 
			
		||||
pub trait Theme {
 | 
			
		||||
    /// Formats a prompt.
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn format_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
 | 
			
		||||
        write!(f, "{}:", prompt)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats out an error.
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn format_error(&self, f: &mut dyn fmt::Write, err: &str) -> fmt::Result {
 | 
			
		||||
        write!(f, "error: {}", err)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a confirm prompt.
 | 
			
		||||
    fn format_confirm_prompt(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
        default: Option<bool>,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        if !prompt.is_empty() {
 | 
			
		||||
            write!(f, "{} ", &prompt)?;
 | 
			
		||||
        }
 | 
			
		||||
        match default {
 | 
			
		||||
            None => write!(f, "[y/n] ")?,
 | 
			
		||||
            Some(true) => write!(f, "[Y/n] ")?,
 | 
			
		||||
            Some(false) => write!(f, "[y/N] ")?,
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a confirm prompt after selection.
 | 
			
		||||
    fn format_confirm_prompt_selection(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
        selection: Option<bool>,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        let selection = selection.map(|b| if b { "yes" } else { "no" });
 | 
			
		||||
 | 
			
		||||
        match selection {
 | 
			
		||||
            Some(selection) if prompt.is_empty() => {
 | 
			
		||||
                write!(f, "{}", selection)
 | 
			
		||||
            }
 | 
			
		||||
            Some(selection) => {
 | 
			
		||||
                write!(f, "{} {}", &prompt, selection)
 | 
			
		||||
            }
 | 
			
		||||
            None if prompt.is_empty() => Ok(()),
 | 
			
		||||
            None => {
 | 
			
		||||
                write!(f, "{}", &prompt)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats an input prompt.
 | 
			
		||||
    fn format_input_prompt(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
        default: Option<&str>,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        match default {
 | 
			
		||||
            Some(default) if prompt.is_empty() => write!(f, "[{}]: ", default),
 | 
			
		||||
            Some(default) => write!(f, "{} [{}]: ", prompt, default),
 | 
			
		||||
            None => write!(f, "{}: ", prompt),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats an input prompt after selection.
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn format_input_prompt_selection(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
        sel: &str,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        write!(f, "{}: {}", prompt, sel)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a password prompt.
 | 
			
		||||
    #[inline]
 | 
			
		||||
    #[cfg(feature = "password")]
 | 
			
		||||
    fn format_password_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
 | 
			
		||||
        self.format_input_prompt(f, prompt, None)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a password prompt after selection.
 | 
			
		||||
    #[inline]
 | 
			
		||||
    #[cfg(feature = "password")]
 | 
			
		||||
    fn format_password_prompt_selection(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        self.format_input_prompt_selection(f, prompt, "[hidden]")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a select prompt.
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn format_select_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
 | 
			
		||||
        self.format_prompt(f, prompt)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a select prompt after selection.
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn format_select_prompt_selection(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
        sel: &str,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        self.format_input_prompt_selection(f, prompt, sel)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a multi select prompt.
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn format_multi_select_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
 | 
			
		||||
        self.format_prompt(f, prompt)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a sort prompt.
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn format_sort_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
 | 
			
		||||
        self.format_prompt(f, prompt)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a multi_select prompt after selection.
 | 
			
		||||
    fn format_multi_select_prompt_selection(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
        selections: &[&str],
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        write!(f, "{}: ", prompt)?;
 | 
			
		||||
        for (idx, sel) in selections.iter().enumerate() {
 | 
			
		||||
            write!(f, "{}{}", if idx == 0 { "" } else { ", " }, sel)?;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a sort prompt after selection.
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn format_sort_prompt_selection(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
        selections: &[&str],
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        self.format_multi_select_prompt_selection(f, prompt, selections)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a select prompt item.
 | 
			
		||||
    fn format_select_prompt_item(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        text: &str,
 | 
			
		||||
        active: bool,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        write!(f, "{} {}", if active { ">" } else { " " }, text)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a multi select prompt item.
 | 
			
		||||
    fn format_multi_select_prompt_item(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        text: &str,
 | 
			
		||||
        checked: bool,
 | 
			
		||||
        active: bool,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        write!(
 | 
			
		||||
            f,
 | 
			
		||||
            "{} {}",
 | 
			
		||||
            match (checked, active) {
 | 
			
		||||
                (true, true) => "> [x]",
 | 
			
		||||
                (true, false) => "  [x]",
 | 
			
		||||
                (false, true) => "> [ ]",
 | 
			
		||||
                (false, false) => "  [ ]",
 | 
			
		||||
            },
 | 
			
		||||
            text
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a sort prompt item.
 | 
			
		||||
    fn format_sort_prompt_item(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        text: &str,
 | 
			
		||||
        picked: bool,
 | 
			
		||||
        active: bool,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        write!(
 | 
			
		||||
            f,
 | 
			
		||||
            "{} {}",
 | 
			
		||||
            match (picked, active) {
 | 
			
		||||
                (true, true) => "> [x]",
 | 
			
		||||
                (false, true) => "> [ ]",
 | 
			
		||||
                (_, false) => "  [ ]",
 | 
			
		||||
            },
 | 
			
		||||
            text
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a fuzzy select prompt item.
 | 
			
		||||
    #[cfg(feature = "fuzzy-select")]
 | 
			
		||||
    fn format_fuzzy_select_prompt_item(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        text: &str,
 | 
			
		||||
        active: bool,
 | 
			
		||||
        highlight_matches: bool,
 | 
			
		||||
        matcher: &SkimMatcherV2,
 | 
			
		||||
        search_term: &str,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        write!(f, "{} ", if active { ">" } else { " " })?;
 | 
			
		||||
 | 
			
		||||
        if highlight_matches {
 | 
			
		||||
            if let Some((_score, indices)) = matcher.fuzzy_indices(text, &search_term) {
 | 
			
		||||
                for (idx, c) in text.chars().into_iter().enumerate() {
 | 
			
		||||
                    if indices.contains(&idx) {
 | 
			
		||||
                        write!(f, "{}", style(c).for_stderr().bold())?;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        write!(f, "{}", c)?;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return Ok(());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        write!(f, "{}", text)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a fuzzy select prompt.
 | 
			
		||||
    #[cfg(feature = "fuzzy-select")]
 | 
			
		||||
    fn format_fuzzy_select_prompt(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
        search_term: &str,
 | 
			
		||||
        cursor_pos: usize,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        if !prompt.is_empty() {
 | 
			
		||||
            write!(f, "{} ", prompt,)?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if cursor_pos < search_term.len() {
 | 
			
		||||
            let st_head = search_term[0..cursor_pos].to_string();
 | 
			
		||||
            let st_tail = search_term[cursor_pos..search_term.len()].to_string();
 | 
			
		||||
            let st_cursor = "|".to_string();
 | 
			
		||||
            write!(f, "{}{}{}", st_head, st_cursor, st_tail)
 | 
			
		||||
        } else {
 | 
			
		||||
            let cursor = "|".to_string();
 | 
			
		||||
            write!(f, "{}{}", search_term.to_string(), cursor)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// The default theme.
 | 
			
		||||
pub struct SimpleTheme;
 | 
			
		||||
 | 
			
		||||
impl Theme for SimpleTheme {}
 | 
			
		||||
 | 
			
		||||
/// A colorful theme
 | 
			
		||||
pub struct ColorfulTheme {
 | 
			
		||||
    /// The style for default values
 | 
			
		||||
    pub defaults_style: Style,
 | 
			
		||||
    /// The style for prompt
 | 
			
		||||
    pub prompt_style: Style,
 | 
			
		||||
    /// Prompt prefix value and style
 | 
			
		||||
    pub prompt_prefix: StyledObject<String>,
 | 
			
		||||
    /// Prompt suffix value and style
 | 
			
		||||
    pub prompt_suffix: StyledObject<String>,
 | 
			
		||||
    /// Prompt on success prefix value and style
 | 
			
		||||
    pub success_prefix: StyledObject<String>,
 | 
			
		||||
    /// Prompt on success suffix value and style
 | 
			
		||||
    pub success_suffix: StyledObject<String>,
 | 
			
		||||
    /// Error prefix value and style
 | 
			
		||||
    pub error_prefix: StyledObject<String>,
 | 
			
		||||
    /// The style for error message
 | 
			
		||||
    pub error_style: Style,
 | 
			
		||||
    /// The style for hints
 | 
			
		||||
    pub hint_style: Style,
 | 
			
		||||
    /// The style for values on prompt success
 | 
			
		||||
    pub values_style: Style,
 | 
			
		||||
    /// The style for active items
 | 
			
		||||
    pub active_item_style: Style,
 | 
			
		||||
    /// The style for inactive items
 | 
			
		||||
    pub inactive_item_style: Style,
 | 
			
		||||
    /// Active item in select prefix value and style
 | 
			
		||||
    pub active_item_prefix: StyledObject<String>,
 | 
			
		||||
    /// Inctive item in select prefix value and style
 | 
			
		||||
    pub inactive_item_prefix: StyledObject<String>,
 | 
			
		||||
    /// Checked item in multi select prefix value and style
 | 
			
		||||
    pub checked_item_prefix: StyledObject<String>,
 | 
			
		||||
    /// Unchecked item in multi select prefix value and style
 | 
			
		||||
    pub unchecked_item_prefix: StyledObject<String>,
 | 
			
		||||
    /// Picked item in sort prefix value and style
 | 
			
		||||
    pub picked_item_prefix: StyledObject<String>,
 | 
			
		||||
    /// Unpicked item in sort prefix value and style
 | 
			
		||||
    pub unpicked_item_prefix: StyledObject<String>,
 | 
			
		||||
    /// Formats the cursor for a fuzzy select prompt
 | 
			
		||||
    #[cfg(feature = "fuzzy-select")]
 | 
			
		||||
    pub fuzzy_cursor_style: Style,
 | 
			
		||||
    // Formats the highlighting if matched characters
 | 
			
		||||
    #[cfg(feature = "fuzzy-select")]
 | 
			
		||||
    pub fuzzy_match_highlight_style: Style,
 | 
			
		||||
    /// Show the selections from certain prompts inline
 | 
			
		||||
    pub inline_selections: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for ColorfulTheme {
 | 
			
		||||
    fn default() -> ColorfulTheme {
 | 
			
		||||
        ColorfulTheme {
 | 
			
		||||
            defaults_style: Style::new().for_stderr().cyan(),
 | 
			
		||||
            prompt_style: Style::new().for_stderr().bold(),
 | 
			
		||||
            prompt_prefix: style("?".to_string()).for_stderr().yellow(),
 | 
			
		||||
            prompt_suffix: style("›".to_string()).for_stderr().black().bright(),
 | 
			
		||||
            success_prefix: style("✔".to_string()).for_stderr().green(),
 | 
			
		||||
            success_suffix: style("·".to_string()).for_stderr().black().bright(),
 | 
			
		||||
            error_prefix: style("✘".to_string()).for_stderr().red(),
 | 
			
		||||
            error_style: Style::new().for_stderr().red(),
 | 
			
		||||
            hint_style: Style::new().for_stderr().black().bright(),
 | 
			
		||||
            values_style: Style::new().for_stderr().green(),
 | 
			
		||||
            active_item_style: Style::new().for_stderr().cyan(),
 | 
			
		||||
            inactive_item_style: Style::new().for_stderr(),
 | 
			
		||||
            active_item_prefix: style("❯".to_string()).for_stderr().green(),
 | 
			
		||||
            inactive_item_prefix: style(" ".to_string()).for_stderr(),
 | 
			
		||||
            checked_item_prefix: style("✔".to_string()).for_stderr().green(),
 | 
			
		||||
            unchecked_item_prefix: style("✔".to_string()).for_stderr().black(),
 | 
			
		||||
            picked_item_prefix: style("❯".to_string()).for_stderr().green(),
 | 
			
		||||
            unpicked_item_prefix: style(" ".to_string()).for_stderr(),
 | 
			
		||||
            #[cfg(feature = "fuzzy-select")]
 | 
			
		||||
            fuzzy_cursor_style: Style::new().for_stderr().black().on_white(),
 | 
			
		||||
            #[cfg(feature = "fuzzy-select")]
 | 
			
		||||
            fuzzy_match_highlight_style: Style::new().for_stderr().bold(),
 | 
			
		||||
            inline_selections: true,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Theme for ColorfulTheme {
 | 
			
		||||
    /// Formats a prompt.
 | 
			
		||||
    fn format_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
 | 
			
		||||
        if !prompt.is_empty() {
 | 
			
		||||
            write!(
 | 
			
		||||
                f,
 | 
			
		||||
                "{} {} ",
 | 
			
		||||
                &self.prompt_prefix,
 | 
			
		||||
                self.prompt_style.apply_to(prompt)
 | 
			
		||||
            )?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        write!(f, "{}", &self.prompt_suffix)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats an error
 | 
			
		||||
    fn format_error(&self, f: &mut dyn fmt::Write, err: &str) -> fmt::Result {
 | 
			
		||||
        write!(
 | 
			
		||||
            f,
 | 
			
		||||
            "{} {}",
 | 
			
		||||
            &self.error_prefix,
 | 
			
		||||
            self.error_style.apply_to(err)
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats an input prompt.
 | 
			
		||||
    fn format_input_prompt(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
        default: Option<&str>,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        if !prompt.is_empty() {
 | 
			
		||||
            write!(
 | 
			
		||||
                f,
 | 
			
		||||
                "{} {} ",
 | 
			
		||||
                &self.prompt_prefix,
 | 
			
		||||
                self.prompt_style.apply_to(prompt)
 | 
			
		||||
            )?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        match default {
 | 
			
		||||
            Some(default) => write!(
 | 
			
		||||
                f,
 | 
			
		||||
                "{} {} ",
 | 
			
		||||
                self.hint_style.apply_to(&format!("({})", default)),
 | 
			
		||||
                &self.prompt_suffix
 | 
			
		||||
            ),
 | 
			
		||||
            None => write!(f, "{} ", &self.prompt_suffix),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a confirm prompt.
 | 
			
		||||
    fn format_confirm_prompt(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
        default: Option<bool>,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        if !prompt.is_empty() {
 | 
			
		||||
            write!(
 | 
			
		||||
                f,
 | 
			
		||||
                "{} {} ",
 | 
			
		||||
                &self.prompt_prefix,
 | 
			
		||||
                self.prompt_style.apply_to(prompt)
 | 
			
		||||
            )?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        match default {
 | 
			
		||||
            None => write!(
 | 
			
		||||
                f,
 | 
			
		||||
                "{} {}",
 | 
			
		||||
                self.hint_style.apply_to("(y/n)"),
 | 
			
		||||
                &self.prompt_suffix
 | 
			
		||||
            ),
 | 
			
		||||
            Some(true) => write!(
 | 
			
		||||
                f,
 | 
			
		||||
                "{} {} {}",
 | 
			
		||||
                self.hint_style.apply_to("(y/n)"),
 | 
			
		||||
                &self.prompt_suffix,
 | 
			
		||||
                self.defaults_style.apply_to("yes")
 | 
			
		||||
            ),
 | 
			
		||||
            Some(false) => write!(
 | 
			
		||||
                f,
 | 
			
		||||
                "{} {} {}",
 | 
			
		||||
                self.hint_style.apply_to("(y/n)"),
 | 
			
		||||
                &self.prompt_suffix,
 | 
			
		||||
                self.defaults_style.apply_to("no")
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a confirm prompt after selection.
 | 
			
		||||
    fn format_confirm_prompt_selection(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
        selection: Option<bool>,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        if !prompt.is_empty() {
 | 
			
		||||
            write!(
 | 
			
		||||
                f,
 | 
			
		||||
                "{} {} ",
 | 
			
		||||
                &self.success_prefix,
 | 
			
		||||
                self.prompt_style.apply_to(prompt)
 | 
			
		||||
            )?;
 | 
			
		||||
        }
 | 
			
		||||
        let selection = selection.map(|b| if b { "yes" } else { "no" });
 | 
			
		||||
 | 
			
		||||
        match selection {
 | 
			
		||||
            Some(selection) => {
 | 
			
		||||
                write!(
 | 
			
		||||
                    f,
 | 
			
		||||
                    "{} {}",
 | 
			
		||||
                    &self.success_suffix,
 | 
			
		||||
                    self.values_style.apply_to(selection)
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
            None => {
 | 
			
		||||
                write!(f, "{}", &self.success_suffix)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats an input prompt after selection.
 | 
			
		||||
    fn format_input_prompt_selection(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
        sel: &str,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        if !prompt.is_empty() {
 | 
			
		||||
            write!(
 | 
			
		||||
                f,
 | 
			
		||||
                "{} {} ",
 | 
			
		||||
                &self.success_prefix,
 | 
			
		||||
                self.prompt_style.apply_to(prompt)
 | 
			
		||||
            )?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        write!(
 | 
			
		||||
            f,
 | 
			
		||||
            "{} {}",
 | 
			
		||||
            &self.success_suffix,
 | 
			
		||||
            self.values_style.apply_to(sel)
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a password prompt after selection.
 | 
			
		||||
    #[cfg(feature = "password")]
 | 
			
		||||
    fn format_password_prompt_selection(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        self.format_input_prompt_selection(f, prompt, "********")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a multi select prompt after selection.
 | 
			
		||||
    fn format_multi_select_prompt_selection(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
        selections: &[&str],
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        if !prompt.is_empty() {
 | 
			
		||||
            write!(
 | 
			
		||||
                f,
 | 
			
		||||
                "{} {} ",
 | 
			
		||||
                &self.success_prefix,
 | 
			
		||||
                self.prompt_style.apply_to(prompt)
 | 
			
		||||
            )?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        write!(f, "{} ", &self.success_suffix)?;
 | 
			
		||||
 | 
			
		||||
        if self.inline_selections {
 | 
			
		||||
            for (idx, sel) in selections.iter().enumerate() {
 | 
			
		||||
                write!(
 | 
			
		||||
                    f,
 | 
			
		||||
                    "{}{}",
 | 
			
		||||
                    if idx == 0 { "" } else { ", " },
 | 
			
		||||
                    self.values_style.apply_to(sel)
 | 
			
		||||
                )?;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a select prompt item.
 | 
			
		||||
    fn format_select_prompt_item(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        text: &str,
 | 
			
		||||
        active: bool,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        let details = if active {
 | 
			
		||||
            (
 | 
			
		||||
                &self.active_item_prefix,
 | 
			
		||||
                self.active_item_style.apply_to(text),
 | 
			
		||||
            )
 | 
			
		||||
        } else {
 | 
			
		||||
            (
 | 
			
		||||
                &self.inactive_item_prefix,
 | 
			
		||||
                self.inactive_item_style.apply_to(text),
 | 
			
		||||
            )
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        write!(f, "{} {}", details.0, details.1)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a multi select prompt item.
 | 
			
		||||
    fn format_multi_select_prompt_item(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        text: &str,
 | 
			
		||||
        checked: bool,
 | 
			
		||||
        active: bool,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        let details = match (checked, active) {
 | 
			
		||||
            (true, true) => (
 | 
			
		||||
                &self.checked_item_prefix,
 | 
			
		||||
                self.active_item_style.apply_to(text),
 | 
			
		||||
            ),
 | 
			
		||||
            (true, false) => (
 | 
			
		||||
                &self.checked_item_prefix,
 | 
			
		||||
                self.inactive_item_style.apply_to(text),
 | 
			
		||||
            ),
 | 
			
		||||
            (false, true) => (
 | 
			
		||||
                &self.unchecked_item_prefix,
 | 
			
		||||
                self.active_item_style.apply_to(text),
 | 
			
		||||
            ),
 | 
			
		||||
            (false, false) => (
 | 
			
		||||
                &self.unchecked_item_prefix,
 | 
			
		||||
                self.inactive_item_style.apply_to(text),
 | 
			
		||||
            ),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        write!(f, "{} {}", details.0, details.1)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a sort prompt item.
 | 
			
		||||
    fn format_sort_prompt_item(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        text: &str,
 | 
			
		||||
        picked: bool,
 | 
			
		||||
        active: bool,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        let details = match (picked, active) {
 | 
			
		||||
            (true, true) => (
 | 
			
		||||
                &self.picked_item_prefix,
 | 
			
		||||
                self.active_item_style.apply_to(text),
 | 
			
		||||
            ),
 | 
			
		||||
            (false, true) => (
 | 
			
		||||
                &self.unpicked_item_prefix,
 | 
			
		||||
                self.active_item_style.apply_to(text),
 | 
			
		||||
            ),
 | 
			
		||||
            (_, false) => (
 | 
			
		||||
                &self.unpicked_item_prefix,
 | 
			
		||||
                self.inactive_item_style.apply_to(text),
 | 
			
		||||
            ),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        write!(f, "{} {}", details.0, details.1)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a fuzzy select prompt item.
 | 
			
		||||
    #[cfg(feature = "fuzzy-select")]
 | 
			
		||||
    fn format_fuzzy_select_prompt_item(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        text: &str,
 | 
			
		||||
        active: bool,
 | 
			
		||||
        highlight_matches: bool,
 | 
			
		||||
        matcher: &SkimMatcherV2,
 | 
			
		||||
        search_term: &str,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        write!(
 | 
			
		||||
            f,
 | 
			
		||||
            "{} ",
 | 
			
		||||
            if active {
 | 
			
		||||
                &self.active_item_prefix
 | 
			
		||||
            } else {
 | 
			
		||||
                &self.inactive_item_prefix
 | 
			
		||||
            }
 | 
			
		||||
        )?;
 | 
			
		||||
 | 
			
		||||
        if highlight_matches {
 | 
			
		||||
            if let Some((_score, indices)) = matcher.fuzzy_indices(text, &search_term) {
 | 
			
		||||
                for (idx, c) in text.chars().into_iter().enumerate() {
 | 
			
		||||
                    if indices.contains(&idx) {
 | 
			
		||||
                        if active {
 | 
			
		||||
                            write!(
 | 
			
		||||
                                f,
 | 
			
		||||
                                "{}",
 | 
			
		||||
                                self.active_item_style
 | 
			
		||||
                                    .apply_to(self.fuzzy_match_highlight_style.apply_to(c))
 | 
			
		||||
                            )?;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            write!(f, "{}", self.fuzzy_match_highlight_style.apply_to(c))?;
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if active {
 | 
			
		||||
                            write!(f, "{}", self.active_item_style.apply_to(c))?;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            write!(f, "{}", c)?;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return Ok(());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        write!(f, "{}", text)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Formats a fuzzy-selectprompt after selection.
 | 
			
		||||
    #[cfg(feature = "fuzzy-select")]
 | 
			
		||||
    fn format_fuzzy_select_prompt(
 | 
			
		||||
        &self,
 | 
			
		||||
        f: &mut dyn fmt::Write,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
        search_term: &str,
 | 
			
		||||
        cursor_pos: usize,
 | 
			
		||||
    ) -> fmt::Result {
 | 
			
		||||
        if !prompt.is_empty() {
 | 
			
		||||
            write!(
 | 
			
		||||
                f,
 | 
			
		||||
                "{} {} ",
 | 
			
		||||
                &self.prompt_prefix,
 | 
			
		||||
                self.prompt_style.apply_to(prompt)
 | 
			
		||||
            )?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if cursor_pos < search_term.len() {
 | 
			
		||||
            let st_head = search_term[0..cursor_pos].to_string();
 | 
			
		||||
            let st_tail = search_term[cursor_pos + 1..search_term.len()].to_string();
 | 
			
		||||
            let st_cursor = self
 | 
			
		||||
                .fuzzy_cursor_style
 | 
			
		||||
                .apply_to(search_term.to_string().chars().nth(cursor_pos).unwrap());
 | 
			
		||||
            write!(
 | 
			
		||||
                f,
 | 
			
		||||
                "{} {}{}{}",
 | 
			
		||||
                &self.prompt_suffix, st_head, st_cursor, st_tail
 | 
			
		||||
            )
 | 
			
		||||
        } else {
 | 
			
		||||
            let cursor = self.fuzzy_cursor_style.apply_to(" ");
 | 
			
		||||
            write!(
 | 
			
		||||
                f,
 | 
			
		||||
                "{} {}{}",
 | 
			
		||||
                &self.prompt_suffix,
 | 
			
		||||
                search_term.to_string(),
 | 
			
		||||
                cursor
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Helper struct to conveniently render a theme of a term.
 | 
			
		||||
pub(crate) struct TermThemeRenderer<'a> {
 | 
			
		||||
    term: &'a Term,
 | 
			
		||||
    theme: &'a dyn Theme,
 | 
			
		||||
    height: usize,
 | 
			
		||||
    prompt_height: usize,
 | 
			
		||||
    prompts_reset_height: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> TermThemeRenderer<'a> {
 | 
			
		||||
    pub fn new(term: &'a Term, theme: &'a dyn Theme) -> TermThemeRenderer<'a> {
 | 
			
		||||
        TermThemeRenderer {
 | 
			
		||||
            term,
 | 
			
		||||
            theme,
 | 
			
		||||
            height: 0,
 | 
			
		||||
            prompt_height: 0,
 | 
			
		||||
            prompts_reset_height: true,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[cfg(feature = "password")]
 | 
			
		||||
    pub fn set_prompts_reset_height(&mut self, val: bool) {
 | 
			
		||||
        self.prompts_reset_height = val;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[cfg(feature = "password")]
 | 
			
		||||
    pub fn term(&self) -> &Term {
 | 
			
		||||
        self.term
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn add_line(&mut self) {
 | 
			
		||||
        self.height += 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn write_formatted_str<
 | 
			
		||||
        F: FnOnce(&mut TermThemeRenderer, &mut dyn fmt::Write) -> fmt::Result,
 | 
			
		||||
    >(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        f: F,
 | 
			
		||||
    ) -> io::Result<usize> {
 | 
			
		||||
        let mut buf = String::new();
 | 
			
		||||
        f(self, &mut buf).map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
 | 
			
		||||
        self.height += buf.chars().filter(|&x| x == '\n').count();
 | 
			
		||||
        self.term.write_str(&buf)?;
 | 
			
		||||
        Ok(measure_text_width(&buf))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn write_formatted_line<
 | 
			
		||||
        F: FnOnce(&mut TermThemeRenderer, &mut dyn fmt::Write) -> fmt::Result,
 | 
			
		||||
    >(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        f: F,
 | 
			
		||||
    ) -> io::Result<()> {
 | 
			
		||||
        let mut buf = String::new();
 | 
			
		||||
        f(self, &mut buf).map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
 | 
			
		||||
        self.height += buf.chars().filter(|&x| x == '\n').count() + 1;
 | 
			
		||||
        self.term.write_line(&buf)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn write_formatted_prompt<
 | 
			
		||||
        F: FnOnce(&mut TermThemeRenderer, &mut dyn fmt::Write) -> fmt::Result,
 | 
			
		||||
    >(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        f: F,
 | 
			
		||||
    ) -> io::Result<()> {
 | 
			
		||||
        self.write_formatted_line(f)?;
 | 
			
		||||
        if self.prompts_reset_height {
 | 
			
		||||
            self.prompt_height = self.height;
 | 
			
		||||
            self.height = 0;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn write_paging_info(buf: &mut dyn fmt::Write, paging_info: (usize, usize)) -> fmt::Result {
 | 
			
		||||
        write!(buf, " [Page {}/{}] ", paging_info.0, paging_info.1)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn error(&mut self, err: &str) -> io::Result<()> {
 | 
			
		||||
        self.write_formatted_line(|this, buf| this.theme.format_error(buf, err))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn confirm_prompt(&mut self, prompt: &str, default: Option<bool>) -> io::Result<usize> {
 | 
			
		||||
        self.write_formatted_str(|this, buf| this.theme.format_confirm_prompt(buf, prompt, default))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn confirm_prompt_selection(&mut self, prompt: &str, sel: Option<bool>) -> io::Result<()> {
 | 
			
		||||
        self.write_formatted_prompt(|this, buf| {
 | 
			
		||||
            this.theme.format_confirm_prompt_selection(buf, prompt, sel)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[cfg(feature = "fuzzy-select")]
 | 
			
		||||
    pub fn fuzzy_select_prompt(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
        search_term: &str,
 | 
			
		||||
        cursor_pos: usize,
 | 
			
		||||
    ) -> io::Result<()> {
 | 
			
		||||
        self.write_formatted_prompt(|this, buf| {
 | 
			
		||||
            this.theme
 | 
			
		||||
                .format_fuzzy_select_prompt(buf, prompt, search_term, cursor_pos)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn input_prompt(&mut self, prompt: &str, default: Option<&str>) -> io::Result<usize> {
 | 
			
		||||
        self.write_formatted_str(|this, buf| this.theme.format_input_prompt(buf, prompt, default))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn input_prompt_selection(&mut self, prompt: &str, sel: &str) -> io::Result<()> {
 | 
			
		||||
        self.write_formatted_prompt(|this, buf| {
 | 
			
		||||
            this.theme.format_input_prompt_selection(buf, prompt, sel)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[cfg(feature = "password")]
 | 
			
		||||
    pub fn password_prompt(&mut self, prompt: &str) -> io::Result<usize> {
 | 
			
		||||
        self.write_formatted_str(|this, buf| {
 | 
			
		||||
            write!(buf, "\r")?;
 | 
			
		||||
            this.theme.format_password_prompt(buf, prompt)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[cfg(feature = "password")]
 | 
			
		||||
    pub fn password_prompt_selection(&mut self, prompt: &str) -> io::Result<()> {
 | 
			
		||||
        self.write_formatted_prompt(|this, buf| {
 | 
			
		||||
            this.theme.format_password_prompt_selection(buf, prompt)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn select_prompt(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
        paging_info: Option<(usize, usize)>,
 | 
			
		||||
    ) -> io::Result<()> {
 | 
			
		||||
        self.write_formatted_prompt(|this, buf| {
 | 
			
		||||
            this.theme.format_select_prompt(buf, prompt)?;
 | 
			
		||||
 | 
			
		||||
            if let Some(paging_info) = paging_info {
 | 
			
		||||
                TermThemeRenderer::write_paging_info(buf, paging_info)?;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Ok(())
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn select_prompt_selection(&mut self, prompt: &str, sel: &str) -> io::Result<()> {
 | 
			
		||||
        self.write_formatted_prompt(|this, buf| {
 | 
			
		||||
            this.theme.format_select_prompt_selection(buf, prompt, sel)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn select_prompt_item(&mut self, text: &str, active: bool) -> io::Result<()> {
 | 
			
		||||
        self.write_formatted_line(|this, buf| {
 | 
			
		||||
            this.theme.format_select_prompt_item(buf, text, active)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[cfg(feature = "fuzzy-select")]
 | 
			
		||||
    pub fn fuzzy_select_prompt_item(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        text: &str,
 | 
			
		||||
        active: bool,
 | 
			
		||||
        highlight: bool,
 | 
			
		||||
        matcher: &SkimMatcherV2,
 | 
			
		||||
        search_term: &str,
 | 
			
		||||
    ) -> io::Result<()> {
 | 
			
		||||
        self.write_formatted_line(|this, buf| {
 | 
			
		||||
            this.theme.format_fuzzy_select_prompt_item(
 | 
			
		||||
                buf,
 | 
			
		||||
                text,
 | 
			
		||||
                active,
 | 
			
		||||
                highlight,
 | 
			
		||||
                matcher,
 | 
			
		||||
                search_term,
 | 
			
		||||
            )
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn multi_select_prompt(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
        paging_info: Option<(usize, usize)>,
 | 
			
		||||
    ) -> io::Result<()> {
 | 
			
		||||
        self.write_formatted_prompt(|this, buf| {
 | 
			
		||||
            this.theme.format_multi_select_prompt(buf, prompt)?;
 | 
			
		||||
 | 
			
		||||
            if let Some(paging_info) = paging_info {
 | 
			
		||||
                TermThemeRenderer::write_paging_info(buf, paging_info)?;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Ok(())
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn multi_select_prompt_selection(&mut self, prompt: &str, sel: &[&str]) -> io::Result<()> {
 | 
			
		||||
        self.write_formatted_prompt(|this, buf| {
 | 
			
		||||
            this.theme
 | 
			
		||||
                .format_multi_select_prompt_selection(buf, prompt, sel)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn multi_select_prompt_item(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        text: &str,
 | 
			
		||||
        checked: bool,
 | 
			
		||||
        active: bool,
 | 
			
		||||
    ) -> io::Result<()> {
 | 
			
		||||
        self.write_formatted_line(|this, buf| {
 | 
			
		||||
            this.theme
 | 
			
		||||
                .format_multi_select_prompt_item(buf, text, checked, active)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn sort_prompt(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        prompt: &str,
 | 
			
		||||
        paging_info: Option<(usize, usize)>,
 | 
			
		||||
    ) -> io::Result<()> {
 | 
			
		||||
        self.write_formatted_prompt(|this, buf| {
 | 
			
		||||
            this.theme.format_sort_prompt(buf, prompt)?;
 | 
			
		||||
 | 
			
		||||
            if let Some(paging_info) = paging_info {
 | 
			
		||||
                TermThemeRenderer::write_paging_info(buf, paging_info)?;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Ok(())
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn sort_prompt_selection(&mut self, prompt: &str, sel: &[&str]) -> io::Result<()> {
 | 
			
		||||
        self.write_formatted_prompt(|this, buf| {
 | 
			
		||||
            this.theme.format_sort_prompt_selection(buf, prompt, sel)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn sort_prompt_item(&mut self, text: &str, picked: bool, active: bool) -> io::Result<()> {
 | 
			
		||||
        self.write_formatted_line(|this, buf| {
 | 
			
		||||
            this.theme
 | 
			
		||||
                .format_sort_prompt_item(buf, text, picked, active)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn clear(&mut self) -> io::Result<()> {
 | 
			
		||||
        self.term
 | 
			
		||||
            .clear_last_lines(self.height + self.prompt_height)?;
 | 
			
		||||
        self.height = 0;
 | 
			
		||||
        self.prompt_height = 0;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn clear_preserve_prompt(&mut self, size_vec: &[usize]) -> io::Result<()> {
 | 
			
		||||
        let mut new_height = self.height;
 | 
			
		||||
        let prefix_width = 2;
 | 
			
		||||
        //Check each item size, increment on finding an overflow
 | 
			
		||||
        for size in size_vec {
 | 
			
		||||
            if *size > self.term.size().1 as usize {
 | 
			
		||||
                new_height += (((*size as f64 + prefix_width as f64) / self.term.size().1 as f64)
 | 
			
		||||
                    .ceil()) as usize
 | 
			
		||||
                    - 1;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.term.clear_last_lines(new_height)?;
 | 
			
		||||
        self.height = 0;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										49
									
								
								vendor/dialoguer/src/validate.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								vendor/dialoguer/src/validate.rs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
//! Provides validation for text inputs
 | 
			
		||||
 | 
			
		||||
/// Trait for input validators.
 | 
			
		||||
///
 | 
			
		||||
/// A generic implementation for `Fn(&str) -> Result<(), E>` is provided
 | 
			
		||||
/// to facilitate development.
 | 
			
		||||
pub trait Validator<T> {
 | 
			
		||||
    type Err;
 | 
			
		||||
 | 
			
		||||
    /// Invoked with the value to validate.
 | 
			
		||||
    ///
 | 
			
		||||
    /// If this produces `Ok(())` then the value is used and parsed, if
 | 
			
		||||
    /// an error is returned validation fails with that error.
 | 
			
		||||
    fn validate(&mut self, input: &T) -> Result<(), Self::Err>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T, F, E> Validator<T> for F
 | 
			
		||||
where
 | 
			
		||||
    F: FnMut(&T) -> Result<(), E>,
 | 
			
		||||
{
 | 
			
		||||
    type Err = E;
 | 
			
		||||
 | 
			
		||||
    fn validate(&mut self, input: &T) -> Result<(), Self::Err> {
 | 
			
		||||
        self(input)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Trait for password validators.
 | 
			
		||||
#[allow(clippy::ptr_arg)]
 | 
			
		||||
pub trait PasswordValidator {
 | 
			
		||||
    type Err;
 | 
			
		||||
 | 
			
		||||
    /// Invoked with the value to validate.
 | 
			
		||||
    ///
 | 
			
		||||
    /// If this produces `Ok(())` then the value is used and parsed, if
 | 
			
		||||
    /// an error is returned validation fails with that error.
 | 
			
		||||
    fn validate(&self, input: &String) -> Result<(), Self::Err>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<F, E> PasswordValidator for F
 | 
			
		||||
where
 | 
			
		||||
    F: Fn(&String) -> Result<(), E>,
 | 
			
		||||
{
 | 
			
		||||
    type Err = E;
 | 
			
		||||
 | 
			
		||||
    fn validate(&self, input: &String) -> Result<(), Self::Err> {
 | 
			
		||||
        self(input)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user