term_inquiry/
checkbox_list.rs

1struct Item<'a, T> {
2    message: &'a str,
3    value: T,
4    selected: bool,
5}
6
7pub struct CheckboxList<'a, T> {
8    message: &'a str,
9    selection_list: Vec<Item<'a, T>>,
10    term_data: TermData,
11}
12
13impl<'a, T> CheckboxList<'a, T> {
14    pub fn new(message: &'a str) -> Self {
15        Self {
16            message,
17            selection_list: Vec::new(),
18            term_data: TermData::new(),
19        }
20    }
21
22    pub fn add_item(mut self, selection_name: &'a str, item: T) -> Self {
23        let item = Item::<T> {
24            message: selection_name,
25            value: item,
26            selected: false,
27        };
28
29        self.selection_list.push(item);
30        self
31    }
32
33    pub fn inquire(mut self) -> Result<Vec<T>, InquiryMessage> {
34        if !self.term_data.enable_raw() {
35            return Err(InquiryMessage::TermEnableRawErr);
36        }
37
38        AnsiBuilder::new()
39            .text("[")
40            .color()
41            .fg()
42            .bright_green()
43            .text("?")
44            .reset_attributes()
45            .text("] ")
46            .text(&self.message)
47            .cursor()
48            .save()
49            .cursor()
50            .hide()
51            .color()
52            .fg()
53            .gray()
54            .text(" Press 'a' to accept selection")
55            .println();
56        let mut selected_index = 0;
57
58        let list_len = self.selection_list.len();
59        for _ in 0..list_len - 1 {
60            println!();
61        }
62
63        loop {
64            AnsiBuilder::new().cursor().up(list_len).print();
65
66            for i in 0..list_len {
67                AnsiBuilder::new()
68                    .text("\n\r")
69                    .erase_line(ClearMode::EntireLine)
70                    .print();
71
72                if i == selected_index {
73                    AnsiBuilder::new()
74                        .color()
75                        .fg()
76                        .bright_green()
77                        .style()
78                        .bold()
79                        .text("  →  ")
80                        .reset_attributes()
81                        .print();
82
83                    CheckboxList::render_item(&self.selection_list[i]);
84                    continue;
85                }
86
87                AnsiBuilder::new().text("    ").print();
88
89                CheckboxList::render_item(&self.selection_list[i]);
90            }
91
92            match stdout().lock().flush() {
93                Ok(..) => {}
94                Err(..) => return Err(InquiryMessage::FlushLockErr),
95            };
96
97            let key = Keys::from(stdin());
98
99            match key {
100                Keys::Up => {
101                    if selected_index > 0 {
102                        selected_index -= 1
103                    }
104                }
105                Keys::Down => {
106                    if selected_index < self.selection_list.len() - 1 {
107                        selected_index += 1;
108                    }
109                }
110                Keys::Enter => {
111                    self.selection_list[selected_index].selected =
112                        !self.selection_list[selected_index].selected;
113                }
114                Keys::A => {
115                    let mut selected_items = Vec::new();
116                    let mut selected_names = String::new();
117
118                    let mut i = 0;
119                    while i < self.selection_list.len() {
120                        if !self.selection_list[i].selected {
121                            i += 1;
122                            continue;
123                        }
124
125                        selected_names.push_str(self.selection_list[i].message);
126                        selected_names.push_str(", ");
127                        selected_items.push(self.selection_list.remove(i).value);
128                    }
129
130                    selected_names.pop();
131                    selected_names.pop();
132
133                    AnsiBuilder::new()
134                        .cursor()
135                        .restore()
136                        .color()
137                        .fg()
138                        .blue()
139                        .text(&format!(" {}", selected_names))
140                        .reset_attributes()
141                        .cursor()
142                        .save()
143                        .erase_in_display(EraseMode::CursorToEnd)
144                        .cursor()
145                        .restore()
146                        .cursor()
147                        .show()
148                        .println();
149
150                    if !self.term_data.disable_raw() {
151                        return Err(InquiryMessage::TermDisableRawErr);
152                    }
153
154                    return Ok(selected_items);
155                }
156                Keys::CtrlC | Keys::CtrlZ => {
157                    AnsiBuilder::new().cursor().show().print();
158
159                    if !self.term_data.disable_raw() {
160                        return Err(InquiryMessage::TermDisableRawErr);
161                    }
162
163                    return Err(InquiryMessage::CloseRequested);
164                }
165                // Uncomment to view missing key data that is not handled.
166                // Keys::Unhandled(data) => {
167                //     panic!("{}-{}-{}-{}", data[0], data[1], data[2], data[3])
168                // },
169                _ => { /* we do nothing and proceed with loop */ }
170            }
171        }
172    }
173
174    fn render_item(item: &Item<T>) {
175        if item.selected {
176            AnsiBuilder::new()
177                .text("[")
178                .color()
179                .fg()
180                .bright_green()
181                .text("✓")
182                .reset_attributes()
183                .text("] ")
184                .text(&item.message)
185                .print();
186            return;
187        }
188
189        AnsiBuilder::new()
190            .text("[")
191            .color()
192            .fg()
193            .bright_red()
194            .text("✘")
195            .reset_attributes()
196            .text("] ")
197            .text(&item.message)
198            .print();
199    }
200}
201
202use std::io::{stdin, stdout, Write};
203
204use ansi_builder::{AnsiBuilder, ClearMode, EraseMode};
205
206use crate::{term_data::TermData, InquiryMessage, Keys};