picker/
lib.rs

1use std::io;
2use std::io::Write;
3use termion::{event::Key, input::TermRead, raw::IntoRawMode};
4
5#[derive(Debug)]
6pub struct Picker<'a> {
7    pub available_options: &'a Vec<&'a str>,
8    pub selected_options_indexes: Option<Vec<usize>>,
9}
10
11impl<'a> Picker<'a> {
12    pub fn new(options: &'a Vec<&str>) -> Picker<'a> {
13        Picker {
14            available_options: options,
15            selected_options_indexes: None,
16        }
17    }
18
19    pub fn select(&mut self) {
20        if self.selected_options_indexes.is_none() {
21            let mut selected: Vec<usize> = vec![];
22            let mut current_index: usize = 0;
23
24            let stdin = io::stdin();
25            let mut input = stdin.lock().keys();
26            let mut stdout = io::stdout().into_raw_mode().unwrap();
27            loop {
28                write!(
29                    stdout,
30                    "{} {}",
31                    termion::cursor::Goto(0, 2),
32                    termion::clear::AfterCursor
33                )
34                .unwrap();
35                write!(stdout, "Available options:\r\n").unwrap();
36                for (index, value) in self.available_options.iter().enumerate() {
37                    let is_selected = selected.contains(&index);
38                    let is_focused = index == current_index;
39                    let selected_indicator = if is_selected { "[x]" } else { "[ ]" };
40                    let focused_indicator = if is_focused { "<" } else { "" };
41                    write!(
42                        stdout,
43                        "{} {} {}\r\n",
44                        selected_indicator, value, focused_indicator,
45                    )
46                    .unwrap();
47                }
48                write!(
49            stdout,
50            "Use K or ↑ (UP ARROW) and J or ↓ (DOWN ARROW) to select or deselect an item. Press [ENTER] to confirm your selection{}\r\n",
51            termion::cursor::Hide,
52        )
53                .unwrap();
54                stdout.flush().unwrap();
55                if let Some(Ok(key_event)) = input.next() {
56                    match key_event {
57                        Key::Char('j') | Key::Down => {
58                            if current_index < self.available_options.len() - 1 {
59                                current_index += 1;
60                            }
61                        }
62                        Key::Char('k') | Key::Up => {
63                            if current_index > 0 {
64                                current_index = current_index.saturating_sub(1);
65                            }
66                        }
67                        Key::Char(' ') => {
68                            let is_selected = selected.contains(&current_index);
69                            if is_selected {
70                                if let Some(to_remove) =
71                                    selected.iter().position(|x| x == &current_index)
72                                {
73                                    selected.remove(to_remove);
74                                }
75                            } else {
76                                selected.push(current_index);
77                            }
78                        }
79                        Key::Char('\n') => {
80                            self.selected_options_indexes = Some(selected.clone());
81                            break;
82                        },
83                        _ => {}
84                    }
85                }
86            }
87        }
88    }
89}