-
Notifications
You must be signed in to change notification settings - Fork 27
Added accounting for terminal size #83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
875a7f9
35a1882
c324bf5
5af7a8e
7611784
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| 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) | ||
| .set_max_rows(10) // Specify a custom window-size | ||
| .filter_mode() // Try filtering on "1" | ||
| .interact()?; | ||
|
|
||
| cliclack::outro(format!("You selected: {}", selected))?; | ||
|
|
||
| Ok(()) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| pub mod cursor; | ||
| pub mod interaction; | ||
| pub mod term; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| pub(crate) struct TermSize { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The better naming would be |
||
| window_max_rows: usize, | ||
| window_pos: usize, | ||
|
Comment on lines
+2
to
+3
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's just remove the |
||
| } | ||
|
|
||
| impl Default for TermSize { | ||
| fn default() -> Self { | ||
| let mut window_max_rows = usize::MAX; | ||
|
|
||
| if let Some(termsize) = get_term_size() { | ||
| window_max_rows = (termsize.rows as usize) | ||
| .checked_sub(3) | ||
| .unwrap_or(termsize.rows as usize); | ||
| } | ||
|
Comment on lines
+10
to
+14
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realized that it just not needed. Try to shrink your terminal to a few rows of height, and then run the So, with |
||
|
|
||
| Self { | ||
| window_max_rows, | ||
| window_pos: 0, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl TermSize { | ||
| pub fn get_max_rows(&self) -> usize { | ||
| self.window_max_rows | ||
| } | ||
|
|
||
| pub fn set_max_rows(&mut self, rows: usize) { | ||
| self.window_max_rows = rows; | ||
| } | ||
|
|
||
| pub fn get_pos(&self) -> usize { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| self.window_pos | ||
| } | ||
|
|
||
| pub fn set_pos(&mut self, pos: usize) { | ||
| self.window_pos = pos; | ||
| } | ||
| } | ||
|
|
||
| // IMPORTANT - Everything bellow this should be removed once | ||
| // https://github.com/softprops/termsize/pull/24 is merged! | ||
| // and the forbid unsafe rule should be reenabled! | ||
|
|
||
| #[cfg(unix)] | ||
| use std::io::IsTerminal; | ||
|
|
||
| #[cfg(unix)] | ||
| use std::ffi::{c_ushort, CString}; | ||
|
|
||
| #[cfg(unix)] | ||
| use libc::{ioctl, O_RDONLY, STDOUT_FILENO, TIOCGWINSZ}; | ||
|
|
||
| /// A representation of the size of the current terminal | ||
| #[repr(C)] | ||
| #[derive(Debug)] | ||
| #[cfg(unix)] | ||
| pub struct UnixSize { | ||
| /// number of rows | ||
| pub rows: c_ushort, | ||
| /// number of columns | ||
| pub cols: c_ushort, | ||
| x: c_ushort, | ||
| y: c_ushort, | ||
| } | ||
|
|
||
|
|
||
| /// Workaround for SSH terminal size | ||
| pub fn get_term_size() -> Option<termsize::Size> { | ||
| #[cfg(not(unix))] | ||
| { | ||
| termsize::get() | ||
| } | ||
|
|
||
| #[cfg(unix)] | ||
| { | ||
| _get_unix_termsize() | ||
| } | ||
| } | ||
|
|
||
| /// Gets the current terminal size | ||
| #[cfg(unix)] | ||
| fn _get_unix_termsize() -> Option<termsize::Size> { | ||
| // http://rosettacode.org/wiki/Terminal_control/Dimensions#Library:_BSD_libc | ||
| if !std::io::stdout().is_terminal() { | ||
| return None; | ||
| } | ||
| let mut us = UnixSize { | ||
| rows: 0, | ||
| cols: 0, | ||
| x: 0, | ||
| y: 0, | ||
| }; | ||
|
|
||
| let fd = if let Ok(ssh_term) = std::env::var("SSH_TTY") { | ||
| // Convert path to a C-compatible string | ||
| let c_path = CString::new(ssh_term).expect("Failed to convert path to CString"); | ||
|
|
||
| // Open the terminal device | ||
| let fd = unsafe { libc::open(c_path.as_ptr(), O_RDONLY) }; | ||
| if fd < 0 { | ||
| return None; // Failed to open the terminal device | ||
| } | ||
|
|
||
| fd | ||
| } else { | ||
| STDOUT_FILENO | ||
| }; | ||
|
|
||
| let r = unsafe { ioctl(fd, TIOCGWINSZ, &mut us) }; | ||
|
|
||
| // Closing the open file descriptor | ||
| if fd != STDOUT_FILENO { | ||
| unsafe { libc::close(fd); } | ||
| } | ||
|
|
||
| if r == 0 { | ||
| Some(termsize::Size { | ||
| rows: us.rows, | ||
| cols: us.cols, | ||
| }) | ||
| } else { | ||
| None | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ use std::{fmt::Display, rc::Rc}; | |
|
|
||
| use console::Key; | ||
|
|
||
| use crate::prompt::term::TermSize; | ||
| use crate::{ | ||
| filter::{FilteredView, LabeledItem}, | ||
| prompt::{ | ||
|
|
@@ -33,6 +34,7 @@ pub struct Select<T> { | |
| cursor: usize, | ||
| initial_value: Option<T>, | ||
| filter: FilteredView<RadioButton<T>>, | ||
| term: TermSize, | ||
| } | ||
|
|
||
| impl<T> Select<T> | ||
|
|
@@ -47,6 +49,7 @@ where | |
| cursor: 0, | ||
| initial_value: None, | ||
| filter: FilteredView::default(), | ||
| term: TermSize::default(), | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -78,10 +81,19 @@ where | |
| /// | ||
| /// The filter mode allows to filter the items by typing. | ||
| pub fn filter_mode(mut self) -> Self { | ||
| let term_size = self.term.get_max_rows(); | ||
| self.term | ||
| .set_max_rows(term_size.checked_sub(1).unwrap_or(term_size as usize)); | ||
| self.filter.enable(); | ||
| self | ||
| } | ||
|
|
||
| /// Set the max number of rows of items that are able to be displayed at once | ||
| pub fn set_max_rows(mut self, size: usize) -> Self { | ||
| self.term.set_max_rows(size); | ||
| self | ||
| } | ||
|
|
||
| /// Starts the prompt interaction. | ||
| pub fn interact(&mut self) -> io::Result<T> { | ||
| if self.items.is_empty() { | ||
|
|
@@ -118,11 +130,19 @@ impl<T: Clone> PromptInteraction<T> for Select<T> { | |
| if self.cursor > 0 { | ||
| self.cursor -= 1; | ||
| } | ||
|
|
||
| if self.cursor < self.term.get_pos() { | ||
| self.term.set_pos(self.cursor); | ||
| } | ||
| } | ||
| Key::ArrowDown | Key::ArrowRight | Key::Char('j') | Key::Char('l') => { | ||
| if !self.filter.items().is_empty() && self.cursor < self.filter.items().len() - 1 { | ||
| self.cursor += 1; | ||
| } | ||
|
|
||
| if self.cursor >= self.term.get_pos() + self.term.get_max_rows() { | ||
| self.term.set_pos(self.cursor - self.term.get_max_rows() + 1); | ||
| } | ||
| } | ||
| Key::Enter => { | ||
| return State::Submit(self.filter.items()[self.cursor].borrow().value.clone()); | ||
|
|
@@ -153,6 +173,8 @@ impl<T: Clone> PromptInteraction<T> for Select<T> { | |
| .items() | ||
| .iter() | ||
| .enumerate() | ||
| .skip(self.term.get_pos()) | ||
| .take(self.term.get_max_rows()) | ||
|
Comment on lines
+176
to
+177
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After renaming it's gonna be like |
||
| .map(|(i, item)| { | ||
| let item = item.borrow(); | ||
| theme.format_select_item(&state.into(), self.cursor == i, &item.label, &item.hint) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This trick is not gonna work if you set
filter_modebefore setting the "max rows". Usually, these setters should be more or less pure/idempotent.You should remove it here and check the "filter_mode" somewhere, perhaps, in the
oncallback incrementing or decrementing "1" as needed.