1use std::borrow::Cow;
9use std::fmt::Debug;
10
11use textwrap::wrap;
12use unicode_segmentation::UnicodeSegmentation;
13use unicode_width::UnicodeWidthStr;
14
15pub trait Dialog<A>: Debug + Send {
17 fn render(&mut self, max_rows: usize, max_cols: usize) -> Vec<Cow<'_, str>>;
19
20 fn input(&mut self, c: char) -> Option<Vec<A>>;
23}
24
25#[derive(Clone, Debug)]
27pub struct PromptYesNo<A: Clone + Debug> {
28 res: Vec<A>,
29 msg: Cow<'static, str>,
30}
31
32impl<A: Debug> PromptYesNo<A>
33where
34 A: Clone + Debug,
35{
36 pub fn new<T>(prompt: T, actions: Vec<A>) -> Self
38 where
39 T: Into<Cow<'static, str>>,
40 {
41 PromptYesNo { res: actions, msg: prompt.into() }
42 }
43}
44
45impl<A> Dialog<A> for PromptYesNo<A>
46where
47 A: Clone + Debug + Send + 'static,
48{
49 fn render(&mut self, max_rows: usize, max_cols: usize) -> Vec<Cow<'_, str>> {
50 if max_rows == 0 {
51 return vec![];
52 }
53
54 let mut lines = wrap(self.msg.as_ref(), max_cols);
55
56 if let Some(last) = lines.last_mut() {
57 last.to_mut().push_str(" (y/N)");
58 }
59
60 return lines;
61 }
62
63 fn input(&mut self, c: char) -> Option<Vec<A>> {
64 match c {
65 'y' | 'Y' => Some(self.res.clone()),
66 _ => Some(vec![]),
67 }
68 }
69}
70
71fn find_end(s: &str, start: usize, mut rows: usize, width: usize) -> usize {
72 let mut idx = 0;
73 let mut full = true;
74 let mut cols = width;
75
76 if rows == 0 || width == 0 {
77 return s.len();
80 }
81
82 for (i, grapheme) in UnicodeSegmentation::grapheme_indices(&s[start..], false) {
83 idx = i;
84
85 if rows == 0 {
86 full = false;
87 break;
88 }
89
90 if let "\n" | "\r" | "\r\n" = grapheme {
91 cols = width;
92 rows = rows.saturating_sub(1);
93 continue;
94 }
95
96 if cols == 0 {
97 cols = width;
98 rows = rows.saturating_sub(1);
99 }
100
101 cols -= UnicodeWidthStr::width(grapheme);
102 }
103
104 if full {
105 return s.len();
106 } else {
107 return start + idx;
108 }
109}
110
111#[derive(Clone, Debug)]
113pub struct Pager<A: Clone + Debug> {
114 text: Cow<'static, str>,
115 idx_start: usize,
116 idx_end: usize,
117 area: (usize, usize),
118 res: Vec<A>,
119}
120
121impl<A> Pager<A>
122where
123 A: Clone + Debug,
124{
125 pub fn new<T>(text: T, actions: Vec<A>) -> Self
127 where
128 T: Into<Cow<'static, str>>,
129 {
130 let text = text.into();
131 let idx_start = 0;
132 let idx_end = text.len();
133
134 Pager {
135 text,
136 idx_start,
137 idx_end,
138 area: (0, 0),
139 res: actions,
140 }
141 }
142
143 fn next_page(&mut self) -> bool {
144 self.idx_start = self.idx_end;
145 self.idx_end = self.text.len();
146
147 return self.idx_start == self.idx_end;
148 }
149}
150
151impl<A> Dialog<A> for Pager<A>
152where
153 A: Clone + Debug + Send + 'static,
154{
155 fn render(&mut self, max_rows: usize, max_cols: usize) -> Vec<Cow<'_, str>> {
156 if max_rows == 0 {
157 return vec![];
158 }
159
160 let max_rows = max_rows.saturating_sub(1);
161
162 if (max_rows, max_cols) != self.area {
163 self.idx_end = find_end(self.text.as_ref(), self.idx_start, max_rows, max_cols);
164 self.area = (max_rows, max_cols);
165 }
166
167 let s = &self.text[self.idx_start..self.idx_end];
168 let options = textwrap::Options::new(max_cols).break_words(true);
169 let mut lines = wrap(s.trim_end(), options);
170 lines.push("--- Press Space To Continue ---".into());
171 lines
172 }
173
174 fn input(&mut self, c: char) -> Option<Vec<A>> {
175 if c == ' ' {
176 if self.next_page() {
177 return Some(self.res.clone());
178 } else {
179 return None;
180 }
181 } else {
182 return None;
183 }
184 }
185}
186
187#[derive(Clone, Debug)]
189pub struct MultiChoiceItem<A: Clone + Debug> {
190 choice: char,
191 text: Cow<'static, str>,
192 actions: Vec<A>,
193}
194
195impl<A> MultiChoiceItem<A>
196where
197 A: Clone + Debug,
198{
199 pub fn new<T>(choice: char, text: T, actions: Vec<A>) -> Self
201 where
202 T: Into<Cow<'static, str>>,
203 {
204 let text = text.into();
205
206 MultiChoiceItem { text, choice, actions }
207 }
208}
209
210#[derive(Clone, Debug)]
212pub struct MultiChoice<A: Clone + Debug> {
213 choices: Vec<MultiChoiceItem<A>>,
214 idx_start: usize,
215 idx_end: usize,
216 area: (usize, usize),
217}
218
219impl<A> MultiChoice<A>
220where
221 A: Clone + Debug,
222{
223 pub fn new(choices: Vec<MultiChoiceItem<A>>) -> Self {
225 MultiChoice {
226 idx_start: 0,
227 idx_end: choices.len(),
228 area: (0, 0),
229 choices,
230 }
231 }
232
233 fn next_page(&mut self) -> bool {
234 self.idx_start = self.idx_end;
235 self.idx_end = self.choices.len();
236
237 return self.idx_start == self.idx_end;
238 }
239}
240
241impl<A> Dialog<A> for MultiChoice<A>
242where
243 A: Clone + Debug + Send + 'static,
244{
245 fn render(&mut self, max_rows: usize, max_cols: usize) -> Vec<Cow<'_, str>> {
246 if max_rows == 0 {
247 return vec![];
248 }
249
250 let max_rows = max_rows.saturating_sub(1);
251
252 if (max_rows, max_cols) != self.area {
253 self.idx_end = self.choices.len().min(self.idx_start + max_rows);
254 self.area = (max_rows, max_cols);
255 }
256
257 let mut lines = self.choices[self.idx_start..self.idx_end]
258 .iter()
259 .map(|c| format!("({}) {}", c.choice, c.text))
260 .map(Cow::Owned)
261 .collect::<Vec<_>>();
262 lines.push("--- Select A Choice Or Press Space To Continue ---".into());
263
264 return lines;
265 }
266
267 fn input(&mut self, c: char) -> Option<Vec<A>> {
268 if c == ' ' {
269 if self.next_page() {
270 return Some(vec![]);
271 } else {
272 return None;
273 }
274 }
275
276 for item in self.choices.iter() {
277 if item.choice == c {
278 return Some(item.actions.clone());
279 }
280 }
281
282 return None;
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn test_yes_no() {
292 let mut dialog = PromptYesNo::new("Are you sure?", vec![1, 2, 3]);
293
294 let lines = dialog.render(1, 100);
295 assert_eq!(lines.len(), 1);
296 assert_eq!(lines[0].as_ref(), "Are you sure? (y/N)");
297
298 assert_eq!(dialog.input('y'), Some(vec![1, 2, 3]));
300 assert_eq!(dialog.input('Y'), Some(vec![1, 2, 3]));
301
302 assert_eq!(dialog.input('n'), Some(vec![]));
304 assert_eq!(dialog.input('N'), Some(vec![]));
305
306 assert_eq!(dialog.input('q'), Some(vec![]));
308 }
309
310 #[test]
311 fn test_pager() {
312 let mut dialog =
313 Pager::new("This is Line 1\nThis is Line 2\nThis is Line 3\nThis is Line 4", vec![5]);
314
315 let lines = dialog.render(3, 15);
317 assert_eq!(lines.len(), 3);
318 assert_eq!(lines[0].as_ref(), "This is Line 1");
319 assert_eq!(lines[1].as_ref(), "This is Line 2");
320 assert_eq!(lines[2].as_ref(), "--- Press Space To Continue ---");
321
322 let lines = dialog.render(3, 10);
324 assert_eq!(lines.len(), 3);
325 assert_eq!(lines[0].as_ref(), "This is");
326 assert_eq!(lines[1].as_ref(), "Line 1");
327 assert_eq!(lines[2].as_ref(), "--- Press Space To Continue ---");
328
329 let lines = dialog.render(5, 10);
331 assert_eq!(lines.len(), 5);
332 assert_eq!(lines[0].as_ref(), "This is");
333 assert_eq!(lines[1].as_ref(), "Line 1");
334 assert_eq!(lines[2].as_ref(), "This is");
335 assert_eq!(lines[3].as_ref(), "Line 2");
336 assert_eq!(lines[4].as_ref(), "--- Press Space To Continue ---");
337
338 let res = dialog.input(' ');
340 assert_eq!(res, None);
341
342 let lines = dialog.render(5, 10);
344 assert_eq!(lines.len(), 5);
345 assert_eq!(lines[0].as_ref(), "This is");
346 assert_eq!(lines[1].as_ref(), "Line 3");
347 assert_eq!(lines[2].as_ref(), "This is");
348 assert_eq!(lines[3].as_ref(), "Line 4");
349 assert_eq!(lines[4].as_ref(), "--- Press Space To Continue ---");
350
351 let res = dialog.input(' ');
353 assert_eq!(res, Some(vec![5]));
354 }
355
356 #[test]
357 fn test_multi_choice() {
358 let choice1 = MultiChoiceItem::new('a', "Choice A", vec![0, 1]);
359 let choice2 = MultiChoiceItem::new('q', "Choice Q", vec![2]);
360 let choice3 = MultiChoiceItem::new('5', "Choice 5", vec![3]);
361 let choices = vec![choice1, choice2, choice3];
362 let mut dialog = MultiChoice::new(choices.clone());
363
364 let lines = dialog.render(2, 15);
366 assert_eq!(lines.len(), 2);
367 assert_eq!(lines[0].as_ref(), "(a) Choice A");
368 assert_eq!(lines[1].as_ref(), "--- Select A Choice Or Press Space To Continue ---");
369
370 let lines = dialog.render(3, 15);
372 assert_eq!(lines.len(), 3);
373 assert_eq!(lines[0].as_ref(), "(a) Choice A");
374 assert_eq!(lines[1].as_ref(), "(q) Choice Q");
375 assert_eq!(lines[2].as_ref(), "--- Select A Choice Or Press Space To Continue ---");
376
377 assert_eq!(dialog.input(' '), None);
379
380 let lines = dialog.render(3, 15);
382 assert_eq!(lines.len(), 2);
383 assert_eq!(lines[0].as_ref(), "(5) Choice 5");
384 assert_eq!(lines[1].as_ref(), "--- Select A Choice Or Press Space To Continue ---");
385
386 assert_eq!(dialog.input('q'), Some(vec![2]));
388
389 let mut dialog = MultiChoice::new(choices.clone());
391 assert_eq!(dialog.input('a'), Some(vec![0, 1]));
392
393 let mut dialog = MultiChoice::new(choices.clone());
395 assert_eq!(dialog.input('5'), Some(vec![3]));
396 }
397}