diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ee9319c..d4c4ef54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,17 @@ ### Enhancements * Added `dialouger::Result` and `dialouger::Error` -* Resolve some issues on Windows where pressing shift keys sometimes aborted dialogs. -* Resolve `MultiSelect` checked and unchecked variants looking the same on Windows. +* Added a `BasicHistory` implementation for `History` +* Added vim mode for `FuzzySelect` +* All prompts implement `Clone` + +### Bug fixes + +* Resolve some issues on Windows where pressing shift keys sometimes aborted dialogs +* Resolve `MultiSelect` checked and unchecked variants looking the same on Windows +* `Input` values that are invalid are now also stored in `History` +* Resolve some issues with cursor positioning in `Input` when using `utf-8` characters +* Correct page is shown when default selected option is not on the first page for `Select` ### Breaking @@ -16,6 +25,7 @@ * Prompt builder functions now return `Self` instead of `&mut Self` * Prompt interaction functions now take `self` instead of `&self` * Prompt interaction functions and other operations now return `dialouger::Result` instead of `std::io::Result` +* Rename `Validator` to `InputValidator` ## 0.10.4 @@ -111,7 +121,7 @@ ## 0.6.1 -### Bugfixes +### Bug fixes * `theme::ColorfulTheme` default styles are for stderr diff --git a/src/history.rs b/src/history.rs index 1a772d7a..27f659db 100644 --- a/src/history.rs +++ b/src/history.rs @@ -6,7 +6,7 @@ pub trait History { /// 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) + /// of vector. If the `pos` does not have an entry, [`None`](Option::None) /// should be returned. fn read(&self, pos: usize) -> Option; diff --git a/src/lib.rs b/src/lib.rs index f8faae9e..46bf30cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,7 @@ pub use error::{Error, Result}; #[cfg(feature = "history")] pub use history::{BasicHistory, History}; use paging::Paging; -pub use validate::Validator; +pub use validate::{InputValidator, PasswordValidator}; #[cfg(feature = "fuzzy-select")] pub use prompts::fuzzy_select::FuzzySelect; diff --git a/src/prompts/confirm.rs b/src/prompts/confirm.rs index 46b5f9bb..8ff126fb 100644 --- a/src/prompts/confirm.rs +++ b/src/prompts/confirm.rs @@ -27,6 +27,7 @@ use crate::{ /// } /// } /// ``` +#[derive(Clone)] pub struct Confirm<'a> { prompt: String, report: bool, @@ -262,3 +263,15 @@ impl<'a> Confirm<'a> { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_clone() { + let confirm = Confirm::new().with_prompt("Do you want to continue?"); + + let _ = confirm.clone(); + } +} diff --git a/src/prompts/fuzzy_select.rs b/src/prompts/fuzzy_select.rs index 0a2aa9f5..28dc68c2 100644 --- a/src/prompts/fuzzy_select.rs +++ b/src/prompts/fuzzy_select.rs @@ -30,7 +30,7 @@ use crate::{ /// println!("You chose: {}", items[selection]); /// } /// ``` - +#[derive(Clone)] pub struct FuzzySelect<'a> { default: Option, items: Vec, @@ -382,3 +382,15 @@ impl<'a> FuzzySelect<'a> { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_clone() { + let fuzzy_select = FuzzySelect::new().with_prompt("Do you want to continue?"); + + let _ = fuzzy_select.clone(); + } +} diff --git a/src/prompts/input.rs b/src/prompts/input.rs index 4029da2d..510aecfc 100644 --- a/src/prompts/input.rs +++ b/src/prompts/input.rs @@ -1,4 +1,10 @@ -use std::{cmp::Ordering, fmt::Debug, io, iter, str::FromStr}; +use std::{ + cmp::Ordering, + fmt::Debug, + io, iter, + str::FromStr, + sync::{Arc, Mutex}, +}; use console::{Key, Term}; @@ -8,11 +14,11 @@ use crate::completion::Completion; use crate::history::History; use crate::{ theme::{render::TermThemeRenderer, SimpleTheme, Theme}, - validate::Validator, + validate::InputValidator, Result, }; -type ValidatorCallback<'a, T> = Box Option + 'a>; +type InputValidatorCallback<'a, T> = Arc Option + 'a>>; /// Renders an input prompt. /// @@ -45,6 +51,7 @@ type ValidatorCallback<'a, T> = Box Option + 'a>; /// println!("Your name is: {}", name); /// } /// ``` +#[derive(Clone)] pub struct Input<'a, T> { prompt: String, post_completion_text: Option, @@ -54,9 +61,9 @@ pub struct Input<'a, T> { initial_text: Option, theme: &'a dyn Theme, permit_empty: bool, - validator: Option>, + validator: Option>, #[cfg(feature = "history")] - history: Option<&'a mut dyn History>, + history: Option>>>, #[cfg(feature = "completion")] completion: Option<&'a dyn Completion>, } @@ -207,7 +214,7 @@ impl<'a, T> Input<'a, T> { where H: History, { - self.history = Some(history); + self.history = Some(Arc::new(Mutex::new(history))); self } @@ -249,14 +256,14 @@ where /// ``` pub fn validate_with(mut self, mut validator: V) -> Self where - V: Validator + 'a, + V: InputValidator + 'a, V::Err: ToString, { let mut old_validator_func = self.validator.take(); - self.validator = Some(Box::new(move |value: &T| -> Option { + self.validator = Some(Arc::new(Mutex::new(move |value: &T| -> Option { if let Some(old) = old_validator_func.as_mut() { - if let Some(err) = old(value) { + if let Some(err) = old.lock().unwrap()(value) { return Some(err); } } @@ -265,7 +272,7 @@ where Ok(()) => None, Err(err) => Some(err.to_string()), } - })); + }))); self } @@ -491,7 +498,7 @@ where Key::ArrowUp => { let line_size = term.size().1 as usize; if let Some(history) = &self.history { - if let Some(previous) = history.read(hist_pos) { + if let Some(previous) = history.lock().unwrap().read(hist_pos) { hist_pos += 1; let mut chars_len = chars.len(); while ((prompt_len + chars_len) / line_size) > 0 { @@ -550,7 +557,7 @@ where 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) { + if let Some(previous) = history.lock().unwrap().read(pos) { for ch in previous.chars() { chars.insert(position, ch); position += 1; @@ -574,7 +581,7 @@ where 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) { + if let Some(err) = validator.lock().unwrap()(default) { render.error(&err)?; continue; } @@ -594,11 +601,11 @@ where Ok(value) => { #[cfg(feature = "history")] if let Some(history) = &mut self.history { - history.write(&value); + history.lock().unwrap().write(&value); } if let Some(ref mut validator) = self.validator { - if let Some(err) = validator(&value) { + if let Some(err) = validator.lock().unwrap()(&value) { render.error(&err)?; continue; } @@ -675,7 +682,7 @@ where 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) { + if let Some(err) = validator.lock().unwrap()(default) { render.error(&err)?; continue; } @@ -694,7 +701,7 @@ where match input.parse::() { Ok(value) => { if let Some(ref mut validator) = self.validator { - if let Some(err) = validator(&value) { + if let Some(err) = validator.lock().unwrap()(&value) { render.error(&err)?; continue; } @@ -715,3 +722,15 @@ where } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_clone() { + let input = Input::::new().with_prompt("Your name"); + + let _ = input.clone(); + } +} diff --git a/src/prompts/multi_select.rs b/src/prompts/multi_select.rs index 41cb89b9..2afa828f 100644 --- a/src/prompts/multi_select.rs +++ b/src/prompts/multi_select.rs @@ -30,6 +30,7 @@ use crate::{ /// } /// } /// ``` +#[derive(Clone)] pub struct MultiSelect<'a> { defaults: Vec, items: Vec, @@ -370,3 +371,15 @@ impl<'a> MultiSelect<'a> { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_clone() { + let multi_select = MultiSelect::new().with_prompt("Select your favorite(s)"); + + let _ = multi_select.clone(); + } +} diff --git a/src/prompts/password.rs b/src/prompts/password.rs index 6e1316e4..3893a446 100644 --- a/src/prompts/password.rs +++ b/src/prompts/password.rs @@ -1,4 +1,4 @@ -use std::io; +use std::{io, sync::Arc}; use console::Term; use zeroize::Zeroizing; @@ -9,7 +9,7 @@ use crate::{ Result, }; -type PasswordValidatorCallback<'a> = Box Option + 'a>; +type PasswordValidatorCallback<'a> = Arc Option + 'a>; /// Renders a password input prompt. /// @@ -28,6 +28,7 @@ type PasswordValidatorCallback<'a> = Box Option + 'a> /// println!("Your password length is: {}", password.len()); /// } /// ``` +#[derive(Clone)] pub struct Password<'a> { prompt: String, report: bool, @@ -50,7 +51,7 @@ impl Password<'static> { } } -impl<'a> Password<'a> { +impl Password<'_> { /// Sets the password input prompt. pub fn with_prompt>(mut self, prompt: S) -> Self { self.prompt = prompt.into(); @@ -83,50 +84,6 @@ impl<'a> Password<'a> { self } - /// Registers a validator. - /// - /// # Example - /// - /// ```rust,no_run - /// use dialoguer::Password; - /// - /// fn main() { - /// 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(mut self, validator: V) -> Self - where - V: PasswordValidator + 'a, - V::Err: ToString, - { - let old_validator_func = self.validator.take(); - - self.validator = Some(Box::new(move |value: &String| -> Option { - 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. @@ -191,6 +148,50 @@ impl<'a> Password<'a> { } impl<'a> Password<'a> { + /// Registers a validator. + /// + /// # Example + /// + /// ```rust,no_run + /// use dialoguer::Password; + /// + /// fn main() { + /// 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(mut self, validator: V) -> Self + where + V: PasswordValidator + 'a, + V::Err: ToString, + { + let old_validator_func = self.validator.take(); + + self.validator = Some(Arc::new(move |value: &String| -> Option { + 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 + } + /// Creates a password input prompt with a specific theme. /// /// ## Example @@ -215,3 +216,15 @@ impl<'a> Password<'a> { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_clone() { + let password = Password::new().with_prompt("Enter password"); + + let _ = password.clone(); + } +} diff --git a/src/prompts/select.rs b/src/prompts/select.rs index 49f52aa1..63b15d86 100644 --- a/src/prompts/select.rs +++ b/src/prompts/select.rs @@ -29,6 +29,7 @@ use crate::{ /// println!("You chose: {}", items[selection]); /// } /// ``` +#[derive(Clone)] pub struct Select<'a> { default: usize, items: Vec, @@ -335,6 +336,13 @@ impl<'a> Select<'a> { mod tests { use super::*; + #[test] + fn test_clone() { + let select = Select::new().with_prompt("Do you want to continue?"); + + let _ = select.clone(); + } + #[test] fn test_str() { let selections = &[ diff --git a/src/prompts/sort.rs b/src/prompts/sort.rs index 0e3aa7df..e3856856 100644 --- a/src/prompts/sort.rs +++ b/src/prompts/sort.rs @@ -32,6 +32,7 @@ use crate::{ /// } /// } /// ``` +#[derive(Clone)] pub struct Sort<'a> { items: Vec, prompt: Option, @@ -361,3 +362,15 @@ impl<'a> Sort<'a> { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_clone() { + let sort = Sort::new().with_prompt("Which order do you prefer?"); + + let _ = sort.clone(); + } +} diff --git a/src/validate.rs b/src/validate.rs index addc9b47..12cdd226 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -4,7 +4,7 @@ /// /// A generic implementation for `Fn(&str) -> Result<(), E>` is provided /// to facilitate development. -pub trait Validator { +pub trait InputValidator { type Err; /// Invoked with the value to validate. @@ -14,7 +14,7 @@ pub trait Validator { fn validate(&mut self, input: &T) -> Result<(), Self::Err>; } -impl Validator for F +impl InputValidator for F where F: FnMut(&T) -> Result<(), E>, {