Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ console = "0.15.8"
indicatif = "0.17.8"
once_cell = "1.18.0"
strsim = "0.11.1"
termsize = "0.1.9"
textwrap = "0.16.0"
zeroize = {version = "1.6.0", features = ["derive"]}

Expand Down
23 changes: 23 additions & 0 deletions examples/select.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
fn main() -> std::io::Result<()> {
let mut items: Vec<(String, String, String)> = Vec::new();

for i in 0..20 {
items.push((
format!("Item {}", i),
i.to_string(),
format!("Hint {}", i),
));
}

// Try this example with a terminal height both less than and greater than 10
// to see the automatic window-size adjustment.
let selected = cliclack::select("Select an item")
.items(&items)
.window_size(10) // Specify a custom window-size
.filter_mode() // Try filtering on "1"
.interact()?;

cliclack::outro(format!("You selected: {}", selected))?;

Ok(())
}
21 changes: 21 additions & 0 deletions examples/select_window_size.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
fn main() -> std::io::Result<()> {
let mut items: Vec<(String, String, String)> = Vec::new();

for i in 0..20 {
items.push((
format!("Item {}", i),
i.to_string(),
format!("Hint {}", i),
));
}

let selected = cliclack::select("Select an item")
.items(&items)
.window_size(5)
.filter_mode() // Try filtering on "1"
.interact()?;

cliclack::outro(format!("You selected: {}", selected))?;

Ok(())
}
9 changes: 9 additions & 0 deletions src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,27 @@ impl<I: LabeledItem> Default for FilteredView<I> {
}

impl<I: LabeledItem + Clone> FilteredView<I> {
/// Enable filtering of the items in the view.
pub fn enable(&mut self) {
self.enabled = true;
}

/// Set the items to be filtered.
pub fn set(&mut self, items: Vec<Rc<RefCell<I>>>) {
self.items = items;
}

/// Return the filtered items in the view.
pub fn items(&self) -> &[Rc<RefCell<I>>] {
&self.items
}

/// Get whether or not filtering is enabled.
pub fn is_enabled(&self) -> bool {
self.enabled
}

/// Handle the key event and update the state of the view.
pub fn on<T>(&mut self, key: &Key, all_items: Vec<Rc<RefCell<I>>>) -> Option<State<T>> {
if !self.enabled {
// Pass over the control.
Expand Down
44 changes: 42 additions & 2 deletions src/select.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::cell::RefCell;
use std::io;
use std::{io, usize};
use std::{fmt::Display, rc::Rc};

use console::Key;
use termsize::Size;

use crate::{
filter::{FilteredView, LabeledItem},
Expand Down Expand Up @@ -33,6 +34,9 @@ pub struct Select<T> {
cursor: usize,
initial_value: Option<T>,
filter: FilteredView<RadioButton<T>>,
window_size: usize,
window_pos: usize,
term_size: Option<Size>,
Comment on lines +37 to +39
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cylewitruk

Thanks for the patch! :) I believe this part should be used in the multiselect as well, thus I would ask to move it into a separate structure, e.g. "TermSize", something like I did with filter.rs for filtering in the both selection lists.

TermSize::default()
TermSize::get_size() -> usize
TermSize::set_size(usize)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fadeevab agreed, been a bit tight with time the last weeks but I'll get to this eventually 😄

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cylewitruk hello, no rush :) meanwhile, we managed to release few improvements to the multiprogress bar (insert and println)

}

impl<T> Select<T>
Expand All @@ -47,6 +51,9 @@ where
cursor: 0,
initial_value: None,
filter: FilteredView::default(),
window_size: usize::MAX,
window_pos: 0,
term_size: termsize::get(),
}
}

Expand Down Expand Up @@ -82,6 +89,13 @@ where
self
}

/// Sets the window size. This is the maximum number of items to display
/// at once, triggering scrolling if necessary.
pub fn window_size(mut self, size: usize) -> Self {
self.window_size = size;
self
}

/// Starts the prompt interaction.
pub fn interact(&mut self) -> io::Result<T> {
if self.items.is_empty() {
Expand All @@ -90,6 +104,23 @@ where
"No items added to the list",
));
}

// If the window size hasn't been specified manually, calculate it
// based on the current size of the terminal.
if let Some(size) = &self.term_size {
// Determine the optimal maximum height of the window.
let mut max_height = size.rows as usize - 3;
if self.filter.is_enabled() {
max_height -= 1;
}

// If the window size is not set or exceeds the maximum optimal height,
// use the optimal height instead.
if self.window_size == usize::MAX || self.window_size > max_height {
self.window_size = max_height;
}
}

if let Some(initial_value) = &self.initial_value {
self.cursor = self
.items
Expand Down Expand Up @@ -118,11 +149,18 @@ impl<T: Clone> PromptInteraction<T> for Select<T> {
if self.cursor > 0 {
self.cursor -= 1;
}
if self.cursor < self.window_pos {
self.window_pos = self.cursor;
}
}
Key::ArrowDown | Key::ArrowRight => {
if !self.filter.items().is_empty() && self.cursor < self.filter.items().len() - 1 {
let filtered_item_count = self.filter.items().len();
if !self.filter.items().is_empty() && self.cursor < filtered_item_count - 1 {
self.cursor += 1;
}
if self.cursor >= self.window_pos + self.window_size {
self.window_pos = self.cursor - self.window_size + 1;
}
}
Key::Enter => {
return State::Submit(self.filter.items()[self.cursor].borrow().value.clone());
Expand Down Expand Up @@ -153,6 +191,8 @@ impl<T: Clone> PromptInteraction<T> for Select<T> {
.items()
.iter()
.enumerate()
.skip(self.window_pos)
.take(self.window_size)
.map(|(i, item)| {
let item = item.borrow();
theme.format_select_item(&state.into(), self.cursor == i, &item.label, &item.hint)
Expand Down