diff --git a/inquire/src/prompts/mod.rs b/inquire/src/prompts/mod.rs index e8cb5cac..8f470691 100644 --- a/inquire/src/prompts/mod.rs +++ b/inquire/src/prompts/mod.rs @@ -5,8 +5,8 @@ mod custom_type; mod dateselect; #[cfg(feature = "editor")] mod editor; -mod multiselect; mod multicount; +mod multiselect; mod one_liners; mod password; mod prompt; @@ -22,8 +22,8 @@ pub use custom_type::*; pub use dateselect::*; #[cfg(feature = "editor")] pub use editor::*; -pub use multiselect::*; pub use multicount::*; +pub use multiselect::*; #[cfg(feature = "one-liners")] pub use one_liners::*; pub use password::*; diff --git a/inquire/src/prompts/multicount/action.rs b/inquire/src/prompts/multicount/action.rs index e15d5738..7847cc46 100644 --- a/inquire/src/prompts/multicount/action.rs +++ b/inquire/src/prompts/multicount/action.rs @@ -1,10 +1,13 @@ -use crate::{ui::{Key, KeyModifiers}, InnerAction, InputAction}; +use crate::{ + ui::{Key, KeyModifiers}, + InnerAction, InputAction, +}; use super::config::MultiCountConfig; /// Set of actions for a MultiCountPrompt. #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum MultiCountPromptAction{ +pub enum MultiCountPromptAction { /// Action on the value text input handler. FilterInput(InputAction), /// Moves the cursor to the option above. @@ -20,7 +23,7 @@ pub enum MultiCountPromptAction{ /// Moves the cursor to the end of the list. MoveToEnd, /// Toggles the selection of the current option. - SetCountCurrentOption(u32), + SetCountCurrentOption(u32), /// Increments the current selection by one Increment, /// Decrements the current selection by one @@ -49,7 +52,6 @@ impl InnerAction for MultiCountPromptAction { return action; } } - let action = match key { Key::Up(KeyModifiers::NONE) | Key::Char('p', KeyModifiers::CONTROL) => Self::MoveUp, @@ -69,7 +71,7 @@ impl InnerAction for MultiCountPromptAction { Some(action) => Self::FilterInput(action), None => return None, }, - }; - Some(action) + }; + Some(action) } } diff --git a/inquire/src/prompts/multicount/mod.rs b/inquire/src/prompts/multicount/mod.rs index 5ad0f56a..01ea2ed3 100644 --- a/inquire/src/prompts/multicount/mod.rs +++ b/inquire/src/prompts/multicount/mod.rs @@ -10,16 +10,16 @@ pub use action::*; use std::fmt::Display; use crate::{ - config::get_configuration, - error::{InquireError, InquireResult}, - formatter::MultiCountFormatter, - list_option::ListOption, - prompts::prompt::Prompt, - terminal::get_default_terminal, - type_aliases::Scorer, - ui::{Backend, MultiCountBackend, RenderConfig}, - validator::MultiOptionValidator, - }; + config::get_configuration, + error::{InquireError, InquireResult}, + formatter::MultiCountFormatter, + list_option::ListOption, + prompts::prompt::Prompt, + terminal::get_default_terminal, + type_aliases::Scorer, + ui::{Backend, MultiCountBackend, RenderConfig}, + validator::MultiOptionValidator, +}; use self::prompt::MultiCountPrompt; diff --git a/inquire/src/prompts/multicount/prompt.rs b/inquire/src/prompts/multicount/prompt.rs index f3fa1280..1fb3baa3 100644 --- a/inquire/src/prompts/multicount/prompt.rs +++ b/inquire/src/prompts/multicount/prompt.rs @@ -31,7 +31,7 @@ pub struct MultiCountPrompt<'a, T> { error: Option, } -impl <'a, T> MultiCountPrompt<'a, T> +impl<'a, T> MultiCountPrompt<'a, T> where T: Display, { @@ -42,21 +42,20 @@ where )); } - // Check if the default is within bounds - if let Some(default) = &mco.default { // i.e. if it has a default + // Check if the default is within bounds + if let Some(default) = &mco.default { + // i.e. if it has a default for (i, _) in default { if i >= &mco.options.len() { - return Err(InquireError::InvalidConfiguration( - format!("Index {} is out-of-bounds for length {} of options", i, &mco.options.len()) - )); + return Err(InquireError::InvalidConfiguration(format!( + "Index {} is out-of-bounds for length {} of options", + i, + &mco.options.len() + ))); } } } - let string_options = mco - .options - .iter() - .map(T::to_string) - .collect(); // get the string representation of the options + let string_options = mco.options.iter().map(T::to_string).collect(); // get the string representation of the options let scored_options = (0..mco.options.len()).collect(); // get the indices of the options let option_counts = mco @@ -67,14 +66,14 @@ where .cloned() .filter(|(i, _)| *i < mco.options.len()) .collect() - }) - .unwrap_or_default(); + }) + .unwrap_or_default(); let input = match mco.filter_input_enabled { true => Some(Input::new_with( - mco.starting_filter_input.unwrap_or_default(), - )), - false => None, + mco.starting_filter_input.unwrap_or_default(), + )), + false => None, }; Ok(Self { @@ -92,194 +91,188 @@ where error: None, counts: option_counts, }) - } - - - - fn move_cursor_up(&mut self, qty: usize, wrap: bool) -> ActionResult { - let new_position = if wrap { - let after_wrap = qty.saturating_sub(self.cursor_index); - self.cursor_index.checked_sub(qty).unwrap_or_else(|| self.scored_options.len().saturating_sub(after_wrap)) - } else{ - self.cursor_index.saturating_sub(qty) - }; - - self.update_cursor_position(new_position) - } + } - fn move_cursor_down(&mut self, qty: usize, wrap: bool) -> ActionResult { - let mut new_position = self.cursor_index.saturating_add(qty); + fn move_cursor_up(&mut self, qty: usize, wrap: bool) -> ActionResult { + let new_position = if wrap { + let after_wrap = qty.saturating_sub(self.cursor_index); + self.cursor_index + .checked_sub(qty) + .unwrap_or_else(|| self.scored_options.len().saturating_sub(after_wrap)) + } else { + self.cursor_index.saturating_sub(qty) + }; - if new_position >= self.scored_options.len() { - new_position = if self.scored_options.is_empty() { - 0 - } else if wrap { - new_position % self.scored_options.len() - } else { - self.scored_options.len().saturating_sub(1) - } - } + self.update_cursor_position(new_position) + } - self.update_cursor_position(new_position) - } + fn move_cursor_down(&mut self, qty: usize, wrap: bool) -> ActionResult { + let mut new_position = self.cursor_index.saturating_add(qty); - fn update_cursor_position(&mut self, new_position: usize) -> ActionResult { - if new_position != self.cursor_index { - self.cursor_index = new_position; - ActionResult::NeedsRedraw + if new_position >= self.scored_options.len() { + new_position = if self.scored_options.is_empty() { + 0 + } else if wrap { + new_position % self.scored_options.len() } else { - ActionResult::Clean + self.scored_options.len().saturating_sub(1) } } - fn set_value(&mut self, qty: u32) -> ActionResult { - // get the id of the currently selected option, if it exists - let idx = match self.scored_options.get(self.cursor_index) { - Some(idx) => idx, - None => return ActionResult::Clean, - }; - self.counts.insert((*idx, qty)); - ActionResult::NeedsRedraw - } + self.update_cursor_position(new_position) + } - fn alter_value(&mut self, diff: i32) -> ActionResult { - let idx = match self.scored_options.get(self.cursor_index) { - Some(idx) => idx, - None => return ActionResult::Clean, - }; - - let current_val = self.counts - .iter() - .find(|(i, _)| i == idx); - let old_val; - let new_val; - - match current_val { - // At the beginning, the tree is empty. - // if the value is not in the tree, we insert it at a value of 1. - None => { // because zero-vals are removed from the btree, we need to - // jump-start with a basic value - if diff > 0 { - self.counts.insert((*idx, diff as u32)); - } - } - // i.e. if it finds the pair - Some((_, c)) => { - old_val = *c; - if diff < 0 { - new_val = (*c).saturating_sub(-diff as u32); - } else { - new_val = (*c).saturating_add(diff as u32); - } - self.counts.insert((*idx, new_val)); - self.counts.remove(&(*idx, old_val)); - } - } + fn update_cursor_position(&mut self, new_position: usize) -> ActionResult { + if new_position != self.cursor_index { + self.cursor_index = new_position; ActionResult::NeedsRedraw + } else { + ActionResult::Clean } + } - fn clear_input_if_needed(&mut self, action: MultiCountPromptAction) -> ActionResult { - if !self.config.keep_filter { - return ActionResult::Clean; - } + fn set_value(&mut self, qty: u32) -> ActionResult { + // get the id of the currently selected option, if it exists + let idx = match self.scored_options.get(self.cursor_index) { + Some(idx) => idx, + None => return ActionResult::Clean, + }; + self.counts.insert((*idx, qty)); + ActionResult::NeedsRedraw + } - match action { - MultiCountPromptAction::SetCountCurrentOption(_) - | MultiCountPromptAction::ClearSelections => { - self.input.as_mut().map(Input::clear); - ActionResult::NeedsRedraw + fn alter_value(&mut self, diff: i32) -> ActionResult { + let idx = match self.scored_options.get(self.cursor_index) { + Some(idx) => idx, + None => return ActionResult::Clean, + }; + + let current_val = self.counts.iter().find(|(i, _)| i == idx); + let old_val; + let new_val; + + match current_val { + // At the beginning, the tree is empty. + // if the value is not in the tree, we insert it at a value of 1. + None => { + // because zero-vals are removed from the btree, we need to + // jump-start with a basic value + if diff > 0 { + self.counts.insert((*idx, diff as u32)); + } + } + // i.e. if it finds the pair + Some((_, c)) => { + old_val = *c; + if diff < 0 { + new_val = (*c).saturating_sub(-diff as u32); + } else { + new_val = (*c).saturating_add(diff as u32); } - _ => ActionResult::Clean, + self.counts.insert((*idx, new_val)); + self.counts.remove(&(*idx, old_val)); } } + ActionResult::NeedsRedraw + } - // Used to validate the the current "answer" is valid. - fn validate_current_answer(&self) -> InquireResult { - if let Some(validator) = &self.validator { - // for each of the options, create a list option if the number is positive - let mut option_counts = vec![]; - for (idx, count) in &self.counts { - if *count > 0 { - let value = self.options.get(*idx).unwrap(); - let lo = ListOption::new(*idx, value); - option_counts.push(lo); - } - } - + fn clear_input_if_needed(&mut self, action: MultiCountPromptAction) -> ActionResult { + if !self.config.keep_filter { + return ActionResult::Clean; + } - let res = validator.validate(&option_counts)?; - Ok(res) - } else { - Ok(Validation::Valid) + match action { + MultiCountPromptAction::SetCountCurrentOption(_) + | MultiCountPromptAction::ClearSelections => { + self.input.as_mut().map(Input::clear); + ActionResult::NeedsRedraw } + _ => ActionResult::Clean, } + } - /// used to produce the actual vector of type values - /// THIS IS WRONG AT THE MOMENT: NEED TO SEE WHAT IS GIONG ON WITH self.OPTIONS - fn get_final_answer(&mut self) -> Vec<(u32, ListOption)> { - let mut answer = vec![]; - - // by iterating in descending order, we can safely - // swap remove because the elements to the right - // that we did not remove will not matter anymore. - for pair in self.counts.iter().filter(|(_, val)| val>&0).rev() { - let index = pair.0; - let count = pair.1; - let value = self.options.swap_remove(index); - let lo = (count, ListOption::new(index, value)); - answer.push(lo); + // Used to validate the the current "answer" is valid. + fn validate_current_answer(&self) -> InquireResult { + if let Some(validator) = &self.validator { + // for each of the options, create a list option if the number is positive + let mut option_counts = vec![]; + for (idx, count) in &self.counts { + if *count > 0 { + let value = self.options.get(*idx).unwrap(); + let lo = ListOption::new(*idx, value); + option_counts.push(lo); + } } - answer.reverse(); - answer + let res = validator.validate(&option_counts)?; + Ok(res) + } else { + Ok(Validation::Valid) } + } - /// This seems ok in terms of operation 18/03/2024 - no apparent dependence - /// on the fact that it's counts not checks - fn run_scorer(&mut self) { - let content = match &self.input { - Some(input) => input.content(), - None => return, - }; - // apply the scorer to the content and the options, map to a vec - // of score and index - let mut options = self - .options - .iter() - .enumerate() - .filter_map(|(i, opt)| { - (self.scorer)(content, opt, self.string_options.get(i).unwrap(), i) - .map(|score| (i, score)) - }) - .collect::>(); - - - // sort the options by the score - options.sort_unstable_by_key(|(_idx, score)| Reverse(*score)); - - let new_scored_options = options - .iter() - .map(|(idx, _)| *idx) - .collect::>(); - - if self.scored_options == new_scored_options { - return; - } + /// used to produce the actual vector of type values + /// THIS IS WRONG AT THE MOMENT: NEED TO SEE WHAT IS GIONG ON WITH self.OPTIONS + fn get_final_answer(&mut self) -> Vec<(u32, ListOption)> { + let mut answer = vec![]; + + // by iterating in descending order, we can safely + // swap remove because the elements to the right + // that we did not remove will not matter anymore. + for pair in self.counts.iter().filter(|(_, val)| val > &0).rev() { + let index = pair.0; + let count = pair.1; + let value = self.options.swap_remove(index); + let lo = (count, ListOption::new(index, value)); + answer.push(lo); + } + answer.reverse(); + + answer + } - self.scored_options = new_scored_options; + /// This seems ok in terms of operation 18/03/2024 - no apparent dependence + /// on the fact that it's counts not checks + fn run_scorer(&mut self) { + let content = match &self.input { + Some(input) => input.content(), + None => return, + }; + // apply the scorer to the content and the options, map to a vec + // of score and index + let mut options = self + .options + .iter() + .enumerate() + .filter_map(|(i, opt)| { + (self.scorer)(content, opt, self.string_options.get(i).unwrap(), i) + .map(|score| (i, score)) + }) + .collect::>(); - if self.config.reset_cursor { - let _ = self.update_cursor_position(0); - } else if self.scored_options.len() <= self.cursor_index { - let _ = self.update_cursor_position(self.scored_options.len().saturating_sub(1)); - } + // sort the options by the score + options.sort_unstable_by_key(|(_idx, score)| Reverse(*score)); + + let new_scored_options = options.iter().map(|(idx, _)| *idx).collect::>(); + + if self.scored_options == new_scored_options { + return; } + self.scored_options = new_scored_options; + + if self.config.reset_cursor { + let _ = self.update_cursor_position(0); + } else if self.scored_options.len() <= self.cursor_index { + let _ = self.update_cursor_position(self.scored_options.len().saturating_sub(1)); + } + } } -impl <'a, Backend, T> Prompt for MultiCountPrompt<'a, T> -where Backend: MultiCountBackend, - T:Display, +impl<'a, Backend, T> Prompt for MultiCountPrompt<'a, T> +where + Backend: MultiCountBackend, + T: Display, { type Config = MultiCountConfig; type InnerAction = MultiCountPromptAction; @@ -321,9 +314,7 @@ where Backend: MultiCountBackend, MultiCountPromptAction::MoveUp => self.move_cursor_up(1, true), MultiCountPromptAction::MoveDown => self.move_cursor_down(1, true), MultiCountPromptAction::PageUp => self.move_cursor_up(self.config.page_size, false), - MultiCountPromptAction::PageDown => { - self.move_cursor_down(self.config.page_size, false) - } + MultiCountPromptAction::PageDown => self.move_cursor_down(self.config.page_size, false), MultiCountPromptAction::MoveToStart => self.move_cursor_up(usize::MAX, false), MultiCountPromptAction::MoveToEnd => self.move_cursor_down(usize::MAX, false), //MultiCountPromptAction::ToggleCurrentOption => self.toggle_cursor_selection(), @@ -382,4 +373,3 @@ where Backend: MultiCountBackend, Ok(()) } } - diff --git a/inquire/src/prompts/multicount/test.rs b/inquire/src/prompts/multicount/test.rs index 4daa3c5d..d54e8bce 100644 --- a/inquire/src/prompts/multicount/test.rs +++ b/inquire/src/prompts/multicount/test.rs @@ -64,7 +64,10 @@ fn list_option_indexes_are_relative_to_input_vec() { .prompt_with_backend(&mut backend) .unwrap(); - assert_eq!(vec![(1, ListOption::new(1, 2)), (10, ListOption::new(2, 3))], ans); + assert_eq!( + vec![(1, ListOption::new(1, 2)), (10, ListOption::new(2, 3))], + ans + ); } #[test] diff --git a/inquire/src/ui/backend.rs b/inquire/src/ui/backend.rs index ce5c735c..9e04990a 100644 --- a/inquire/src/ui/backend.rs +++ b/inquire/src/ui/backend.rs @@ -51,13 +51,14 @@ pub trait MultiSelectBackend: CommonBackend { checked: &BTreeSet, ) -> Result<()>; } - pub trait MultiCountBackend: CommonBackend { - fn render_multiselect_prompt(&mut self, prompt: &str, cur_input: Option<&Input>) -> Result<()>; - fn render_options( - &mut self, - page: Page<'_, ListOption>, - counts: &BTreeSet<(usize, u32)>) -> Result<()>; - } +pub trait MultiCountBackend: CommonBackend { + fn render_multiselect_prompt(&mut self, prompt: &str, cur_input: Option<&Input>) -> Result<()>; + fn render_options( + &mut self, + page: Page<'_, ListOption>, + counts: &BTreeSet<(usize, u32)>, + ) -> Result<()>; +} pub trait CustomTypeBackend: CommonBackend { fn render_prompt( @@ -455,10 +456,10 @@ where } } -impl <'a, I, T> MultiCountBackend for Backend<'a, I, T> +impl<'a, I, T> MultiCountBackend for Backend<'a, I, T> where I: InputReader, - T:Terminal, + T: Terminal, { fn render_multiselect_prompt(&mut self, prompt: &str, cur_input: Option<&Input>) -> Result<()> { if let Some(input) = cur_input { @@ -490,7 +491,6 @@ where .unwrap_or(&0); let mut countbox = Styled::new(format!("[{}]", count)); - match (self.render_config.selected_option, page.cursor) { (Some(stylesheet), Some(cursor)) if cursor == idx => countbox.style = stylesheet, _ => {} @@ -507,7 +507,6 @@ where Ok(()) } - } #[cfg(feature = "date")]