From e215ea20c59782b098a109addb3353db07408c24 Mon Sep 17 00:00:00 2001 From: Omer Tuchfeld Date: Wed, 29 Jan 2025 15:10:41 +0100 Subject: [PATCH] feat: add disabled items support in multiselect Sometimes it's nice to show a user a list of options, some of which the user cannot interact with. e.g. A list of linux users on this machine to import to a new machine, I would like to show root but without allowing the user to deselect it. Current implementation limitations: - No theme support - there's no indication that an item is disabled until the user attempts to interact with it. Would adding such visual indication be a good idea from a compatibility perspective? Are themes all built-in or possibly user defined? - Cannot dynamically add items with a different disabled state. This would require modifying the signature of e.g. `item_checked` which would be a breaking change. I could also add a new method but I wasn't sure if it's worth it. - No tests - No documentation Do you think this is a desired feature? If so, I can work on improving the limitations. Should this maybe be a separate widget altogether? --- src/prompts/multi_select.rs | 49 +++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/src/prompts/multi_select.rs b/src/prompts/multi_select.rs index 42b301d..d949291 100644 --- a/src/prompts/multi_select.rs +++ b/src/prompts/multi_select.rs @@ -33,6 +33,7 @@ use crate::{ #[derive(Clone)] pub struct MultiSelect<'a> { defaults: Vec, + disabled: Vec, items: Vec, prompt: Option, report: bool, @@ -63,15 +64,24 @@ impl MultiSelect<'_> { self } - /// Sets a defaults for the menu. - pub fn defaults(mut self, val: &[bool]) -> Self { - self.defaults = val - .to_vec() + fn get_bool_array(&self, val: &[bool]) -> Vec { + val.to_vec() .iter() .copied() .chain(repeat(false)) .take(self.items.len()) - .collect(); + .collect() + } + + /// Sets a defaults for the menu. + pub fn defaults(mut self, val: &[bool]) -> Self { + self.defaults = self.get_bool_array(val); + self + } + + /// Sets disabled items for the menu. + pub fn disabled(mut self, val: &[bool]) -> Self { + self.disabled = self.get_bool_array(val); self } @@ -97,6 +107,8 @@ impl MultiSelect<'_> { pub fn item_checked(mut self, item: T, checked: bool) -> Self { self.items.push(item.to_string()); self.defaults.push(checked); + // TODO: Add support for adding disabled items + self.disabled.push(false); self } @@ -118,6 +130,8 @@ impl MultiSelect<'_> { for (item, checked) in items.into_iter() { self.items.push(item.to_string()); self.defaults.push(checked); + // TODO: Add support for adding disabled items + self.disabled.push(false); } self } @@ -229,6 +243,7 @@ impl MultiSelect<'_> { } let mut checked: Vec = self.defaults.clone(); + let disabled: Vec = self.disabled.clone(); term.hide_cursor()?; @@ -277,13 +292,26 @@ impl MultiSelect<'_> { } } Key::Char(' ') => { - checked[sel] = !checked[sel]; + if !disabled[sel] { + checked[sel] = !checked[sel]; + } } Key::Char('a') => { - if checked.iter().all(|&item_checked| item_checked) { - checked.fill(false); - } else { - checked.fill(true); + let all_non_disabled_checked = checked + .iter() + .zip(disabled.iter()) + .filter_map(|(checked, disabled)| (!disabled).then_some(checked)) + .all(|&item_checked| item_checked); + + // If all already checked, uncheck all + let new_checked = !all_non_disabled_checked; + + for (idx, (_checked_value, disabled_value)) in + checked.clone().iter().zip(disabled.iter()).enumerate() + { + if !disabled_value { + checked[idx] = new_checked; + } } } Key::Escape | Key::Char('q') => { @@ -367,6 +395,7 @@ impl<'a> MultiSelect<'a> { Self { items: vec![], defaults: vec![], + disabled: vec![], clear: true, prompt: None, report: true,