Minimal TUI for managing containerized projects
1use std::io::Write;
2
3use color_eyre::eyre::{ContextCompat, Result};
4use console::{Key, Term, style};
5use fuzzy_matcher::skim::SkimMatcherV2;
6use itertools::Itertools;
7
8pub fn input_text(term: &mut Term, default: &str, name: &str) -> Result<String> {
9 let mut text = default.to_string();
10
11 term.show_cursor()?;
12 write!(term, "{}: {}", name, text)?;
13
14 loop {
15 term.clear_line()?;
16 write!(term, "{}: {}", name, text)?;
17
18 let key = match term.read_key() {
19 Ok(key) => key,
20 Err(err) => {
21 term.show_cursor()?;
22 return Err(err.into());
23 }
24 };
25
26 match key {
27 Key::Enter => {
28 writeln!(term)?;
29 term.hide_cursor()?;
30 return Ok(text);
31 }
32 Key::Backspace => {
33 text.pop();
34 }
35 Key::Char(char) => {
36 text += &char.to_string();
37 }
38 _ => {}
39 }
40 }
41}
42
43pub fn input_selection<T: Clone>(term: &mut Term, data: Vec<(String, T)>, name: &str) -> Result<T> {
44 let mut filter = "".to_string();
45
46 write!(term, "{}:", name)?;
47
48 loop {
49 let matcher = SkimMatcherV2::default();
50 let matches = data
51 .iter()
52 .filter_map(|(text, data)| {
53 matcher
54 .fuzzy(text, &filter, true)
55 .map(|some| (data, text, some))
56 })
57 .sorted_by(|(_, _, fuzzy_a), (_, _, fuzzy_b)| fuzzy_a.0.cmp(&fuzzy_b.0).reverse())
58 .enumerate()
59 .map(|(index, (path, path_str, fuzzy))| {
60 (
61 highlight_search(path_str, &fuzzy.1, index != 0),
62 path,
63 path_str,
64 )
65 })
66 .collect_vec();
67
68 term.clear_line()?;
69
70 if matches.is_empty() {
71 write!(term, "{}: {}", name, filter)?;
72 } else {
73 write!(
74 term,
75 "{}: {}",
76 name,
77 matches.iter().map(|(path, _, _)| path).join(" ")
78 )?;
79 }
80
81 let key = match term.read_key() {
82 Ok(key) => key,
83 Err(err) => {
84 term.show_cursor()?;
85 return Err(err.into());
86 }
87 };
88 match key {
89 Key::Enter => {
90 let (_, data, text) = matches.first().context(format!("no {} Selected", name))?;
91
92 term.clear_line()?;
93 writeln!(term, "{}: {}", name, text)?;
94 break Ok((*data).clone());
95 }
96 Key::Backspace => {
97 filter.pop();
98 }
99 Key::Char(char) => {
100 filter += &char.to_string();
101 }
102 _ => {}
103 }
104 }
105}
106
107fn highlight_search(text: &str, highlighted_letters: &[usize], is_dark: bool) -> String {
108 text.chars()
109 .enumerate()
110 .map(|(index, c)| {
111 match (is_dark, highlighted_letters.contains(&index)) {
112 (false, true) => style(c).red().to_string(),
113 (false, false) => c.to_string(),
114 //dark red
115 (true, true) => style(c).color256(8).bold().to_string(),
116 //light gray
117 (true, false) => style(c).color256(8).to_string(),
118 }
119 })
120 .collect()
121}