From dafc11ba97aee4bba9a596b61a4cf13503b10c6b Mon Sep 17 00:00:00 2001 From: garbaz Date: Wed, 13 Jul 2022 19:49:33 +0200 Subject: [PATCH 1/5] Added implementations of History for Vec and VecDeque --- examples/history.rs | 4 ++++ src/history.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/examples/history.rs b/examples/history.rs index 0d69b273..9a14aed2 100644 --- a/examples/history.rs +++ b/examples/history.rs @@ -9,6 +9,10 @@ fn main() { let mut history = MyHistory::default(); + /// We can also use `Vec` or `VecDeque` directly for a simple infinite history. + // let mut history = Vec::new(); + // let mut history = VecDeque::new(); + loop { if let Ok(cmd) = Input::::with_theme(&ColorfulTheme::default()) .with_prompt("dialoguer") diff --git a/src/history.rs b/src/history.rs index d0818ccd..8a89e642 100644 --- a/src/history.rs +++ b/src/history.rs @@ -1,3 +1,5 @@ +use std::collections::VecDeque; + /// Trait for history handling. pub trait History { /// This is called with the current position that should @@ -13,3 +15,33 @@ pub trait History { /// is implemented as a FIFO queue. fn write(&mut self, val: &T); } + +impl History for Vec { + fn read(&self, pos: usize) -> Option { + // We have to check manually here instead of using `Vec::get` since + // subtracting from `usize` into the negative throws an exception. + if pos >= self.len() { + None + } else { + // Since we have already ensured that `pos` + // is in bounds, we can use direct access. + Some(self[self.len() - pos - 1].clone()) + } + } + + fn write(&mut self, val: &String) { + self.push(val.clone()) + } +} + +impl History for VecDeque { + fn read(&self, pos: usize) -> Option { + self.get(pos).cloned() + } + + fn write(&mut self, val: &String) { + // With `VecDeque` we can simply use `push_front`, + // allowing for normal forward indexing in `read`. + self.push_front(val.clone()) + } +} From 107b728dfb4584b04c5c592d1752572971b5c934 Mon Sep 17 00:00:00 2001 From: Garbaz Date: Wed, 30 Aug 2023 11:06:44 +0200 Subject: [PATCH 2/5] removed History impl for Vec --- src/history.rs | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/src/history.rs b/src/history.rs index 8a89e642..8abb79eb 100644 --- a/src/history.rs +++ b/src/history.rs @@ -16,32 +16,12 @@ pub trait History { fn write(&mut self, val: &T); } -impl History for Vec { - fn read(&self, pos: usize) -> Option { - // We have to check manually here instead of using `Vec::get` since - // subtracting from `usize` into the negative throws an exception. - if pos >= self.len() { - None - } else { - // Since we have already ensured that `pos` - // is in bounds, we can use direct access. - Some(self[self.len() - pos - 1].clone()) - } - } - - fn write(&mut self, val: &String) { - self.push(val.clone()) - } -} - -impl History for VecDeque { +impl History for VecDeque { fn read(&self, pos: usize) -> Option { self.get(pos).cloned() } - fn write(&mut self, val: &String) { - // With `VecDeque` we can simply use `push_front`, - // allowing for normal forward indexing in `read`. - self.push_front(val.clone()) + fn write(&mut self, val: &T) { + self.push_front(val.to_string()) } } From 66d98859e107756bd3fed1b374af23cb219be14c Mon Sep 17 00:00:00 2001 From: Garbaz Date: Thu, 31 Aug 2023 14:23:30 +0200 Subject: [PATCH 3/5] history is either borrowed or owned --- examples/history.rs | 36 ++------------------------- examples/history_custom.rs | 51 ++++++++++++++++++++++++++++++++++++++ src/history.rs | 1 + src/prompts/input.rs | 45 +++++++++++++++++++++++++++++++-- 4 files changed, 97 insertions(+), 36 deletions(-) create mode 100644 examples/history_custom.rs diff --git a/examples/history.rs b/examples/history.rs index 9a14aed2..7a826310 100644 --- a/examples/history.rs +++ b/examples/history.rs @@ -1,17 +1,12 @@ -use dialoguer::{theme::ColorfulTheme, History, Input}; +use dialoguer::{theme::ColorfulTheme, Input}; use std::{collections::VecDeque, process}; fn main() { println!("Use 'exit' to quit the prompt"); - println!("In this example, history is limited to 4 entries"); println!("Use the Up/Down arrows to scroll through history"); println!(); - let mut history = MyHistory::default(); - - /// We can also use `Vec` or `VecDeque` directly for a simple infinite history. - // let mut history = Vec::new(); - // let mut history = VecDeque::new(); + let mut history = VecDeque::new(); loop { if let Ok(cmd) = Input::::with_theme(&ColorfulTheme::default()) @@ -26,30 +21,3 @@ fn main() { } } } - -struct MyHistory { - max: usize, - history: VecDeque, -} - -impl Default for MyHistory { - fn default() -> Self { - MyHistory { - max: 4, - history: VecDeque::new(), - } - } -} - -impl History for MyHistory { - fn read(&self, pos: usize) -> Option { - self.history.get(pos).cloned() - } - - fn write(&mut self, val: &T) { - if self.history.len() == self.max { - self.history.pop_back(); - } - self.history.push_front(val.to_string()); - } -} diff --git a/examples/history_custom.rs b/examples/history_custom.rs new file mode 100644 index 00000000..0d69b273 --- /dev/null +++ b/examples/history_custom.rs @@ -0,0 +1,51 @@ +use dialoguer::{theme::ColorfulTheme, History, Input}; +use std::{collections::VecDeque, process}; + +fn main() { + println!("Use 'exit' to quit the prompt"); + println!("In this example, history is limited to 4 entries"); + println!("Use the Up/Down arrows to scroll through history"); + println!(); + + let mut history = MyHistory::default(); + + loop { + if let Ok(cmd) = Input::::with_theme(&ColorfulTheme::default()) + .with_prompt("dialoguer") + .history_with(&mut history) + .interact_text() + { + if cmd == "exit" { + process::exit(0); + } + println!("Entered {}", cmd); + } + } +} + +struct MyHistory { + max: usize, + history: VecDeque, +} + +impl Default for MyHistory { + fn default() -> Self { + MyHistory { + max: 4, + history: VecDeque::new(), + } + } +} + +impl History for MyHistory { + fn read(&self, pos: usize) -> Option { + self.history.get(pos).cloned() + } + + fn write(&mut self, val: &T) { + if self.history.len() == self.max { + self.history.pop_back(); + } + self.history.push_front(val.to_string()); + } +} diff --git a/src/history.rs b/src/history.rs index 8abb79eb..18e141c3 100644 --- a/src/history.rs +++ b/src/history.rs @@ -16,6 +16,7 @@ pub trait History { fn write(&mut self, val: &T); } + impl History for VecDeque { fn read(&self, pos: usize) -> Option { self.get(pos).cloned() diff --git a/src/prompts/input.rs b/src/prompts/input.rs index b7cd829c..3e2c1cc5 100644 --- a/src/prompts/input.rs +++ b/src/prompts/input.rs @@ -13,6 +13,28 @@ use console::{Key, Term}; type ValidatorCallback<'a, T> = Box Option + 'a>; +#[cfg(feature = "history")] +enum HistoryBox<'a, T> { + Borrowed(&'a mut dyn History), + Owned(Box>), +} + +impl<'a, T> History for HistoryBox<'a, T> { + fn read(&self, pos: usize) -> Option { + match self { + HistoryBox::Borrowed(h) => h.read(pos), + HistoryBox::Owned(h) => h.read(pos), + } + } + + fn write(&mut self, val: &T) { + match self { + HistoryBox::Borrowed(h) => h.write(val), + HistoryBox::Owned(h) => h.write(val), + } + } +} + /// Renders an input prompt. /// /// ## Example usage @@ -50,7 +72,8 @@ pub struct Input<'a, T> { permit_empty: bool, validator: Option>, #[cfg(feature = "history")] - history: Option<&'a mut dyn History>, + history: Option>, + // history: Option<&'a mut dyn History>, #[cfg(feature = "completion")] completion: Option<&'a dyn Completion>, } @@ -192,7 +215,25 @@ impl<'a, T> Input<'a, T> { where H: History, { - self.history = Some(history); + self.history = Some(HistoryBox::Borrowed(history)); + self + } + + #[cfg(feature = "history")] + pub fn history_from + 'static>(&mut self, history: H) -> &mut Self { + self.history = Some(HistoryBox::Owned(Box::new(history))); + self + } + + #[cfg(feature = "history")] + pub fn history_infinite(&mut self) -> &mut Self + where + T: ToString, + { + use std::collections::VecDeque; + + let history = VecDeque::new(); + self.history = Some(HistoryBox::Owned(Box::new(history))); self } From fd335ca3044a41e0829ff19b0ba40b9a766959b6 Mon Sep 17 00:00:00 2001 From: Garbaz Date: Thu, 31 Aug 2023 14:42:04 +0200 Subject: [PATCH 4/5] Removed owned history possiblity and added `BasicHistory` --- examples/history.rs | 7 +++--- src/history.rs | 56 +++++++++++++++++++++++++++++++++++++++++--- src/lib.rs | 2 +- src/prompts/input.rs | 44 ++-------------------------------- 4 files changed, 60 insertions(+), 49 deletions(-) diff --git a/examples/history.rs b/examples/history.rs index 7a826310..1e551569 100644 --- a/examples/history.rs +++ b/examples/history.rs @@ -1,12 +1,13 @@ -use dialoguer::{theme::ColorfulTheme, Input}; -use std::{collections::VecDeque, process}; +use dialoguer::{theme::ColorfulTheme, BasicHistory, Input}; +use std::process; fn main() { println!("Use 'exit' to quit the prompt"); + println!("In this example, history is limited to 8 entries and contains no duplicates"); println!("Use the Up/Down arrows to scroll through history"); println!(); - let mut history = VecDeque::new(); + let mut history = BasicHistory::new().max_entries(8).no_duplicates(true); loop { if let Ok(cmd) = Input::::with_theme(&ColorfulTheme::default()) diff --git a/src/history.rs b/src/history.rs index 18e141c3..e0b3aef4 100644 --- a/src/history.rs +++ b/src/history.rs @@ -16,13 +16,63 @@ pub trait History { fn write(&mut self, val: &T); } +pub struct BasicHistory { + max_entries: Option, + deque: VecDeque, + no_duplicates: bool, +} + +impl BasicHistory { + /// Creates a new basic history value which has no limit on the number of + /// entries and allows for duplicates. + pub fn new() -> Self { + Self { + max_entries: None, + deque: VecDeque::new(), + no_duplicates: false, + } + } + + /// Limit the number of entries stored in the history. + pub fn max_entries(self, max_entries: usize) -> Self { + Self { + max_entries: Some(max_entries), + ..self + } + } + + /// Prevent duplicates in the history. This means that any previous entries + /// that are equal to a new entry are removed before the new entry is added. + pub fn no_duplicates(self, no_duplicates: bool) -> Self { + Self { + no_duplicates, + ..self + } + } +} -impl History for VecDeque { +impl History for BasicHistory { fn read(&self, pos: usize) -> Option { - self.get(pos).cloned() + self.deque.get(pos).cloned() } fn write(&mut self, val: &T) { - self.push_front(val.to_string()) + let val = val.to_string(); + + if self.no_duplicates { + self.deque.retain(|v| v != &val); + } + + self.deque.push_front(val); + + if let Some(max_entries) = self.max_entries { + self.deque.truncate(max_entries); + } + } +} + +impl Default for BasicHistory { + fn default() -> Self { + Self::new() } } diff --git a/src/lib.rs b/src/lib.rs index cd8e307c..15857ab9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,7 +37,7 @@ pub use console; #[cfg(feature = "editor")] pub use edit::Editor; #[cfg(feature = "history")] -pub use history::History; +pub use history::{History, BasicHistory}; use paging::Paging; pub use prompts::{ confirm::Confirm, input::Input, multi_select::MultiSelect, select::Select, sort::Sort, diff --git a/src/prompts/input.rs b/src/prompts/input.rs index 3e2c1cc5..a9f900cd 100644 --- a/src/prompts/input.rs +++ b/src/prompts/input.rs @@ -13,27 +13,6 @@ use console::{Key, Term}; type ValidatorCallback<'a, T> = Box Option + 'a>; -#[cfg(feature = "history")] -enum HistoryBox<'a, T> { - Borrowed(&'a mut dyn History), - Owned(Box>), -} - -impl<'a, T> History for HistoryBox<'a, T> { - fn read(&self, pos: usize) -> Option { - match self { - HistoryBox::Borrowed(h) => h.read(pos), - HistoryBox::Owned(h) => h.read(pos), - } - } - - fn write(&mut self, val: &T) { - match self { - HistoryBox::Borrowed(h) => h.write(val), - HistoryBox::Owned(h) => h.write(val), - } - } -} /// Renders an input prompt. /// @@ -72,8 +51,7 @@ pub struct Input<'a, T> { permit_empty: bool, validator: Option>, #[cfg(feature = "history")] - history: Option>, - // history: Option<&'a mut dyn History>, + history: Option<&'a mut dyn History>, #[cfg(feature = "completion")] completion: Option<&'a dyn Completion>, } @@ -215,25 +193,7 @@ impl<'a, T> Input<'a, T> { where H: History, { - self.history = Some(HistoryBox::Borrowed(history)); - self - } - - #[cfg(feature = "history")] - pub fn history_from + 'static>(&mut self, history: H) -> &mut Self { - self.history = Some(HistoryBox::Owned(Box::new(history))); - self - } - - #[cfg(feature = "history")] - pub fn history_infinite(&mut self) -> &mut Self - where - T: ToString, - { - use std::collections::VecDeque; - - let history = VecDeque::new(); - self.history = Some(HistoryBox::Owned(Box::new(history))); + self.history = Some(history); self } From 57cb02517e3b44f77914478c359640d23159a71a Mon Sep 17 00:00:00 2001 From: Garbaz Date: Thu, 31 Aug 2023 14:50:07 +0200 Subject: [PATCH 5/5] BasicHistory doc comment example --- src/history.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/history.rs b/src/history.rs index e0b3aef4..33659235 100644 --- a/src/history.rs +++ b/src/history.rs @@ -25,6 +25,14 @@ pub struct BasicHistory { impl BasicHistory { /// Creates a new basic history value which has no limit on the number of /// entries and allows for duplicates. + /// + /// # Example + /// + /// A history with at most 8 entries and no duplicates: + /// + /// ```rs + /// let mut history = BasicHistory::new().max_entries(8).no_duplicates(true); + /// ``` pub fn new() -> Self { Self { max_entries: None,