Minimal TUI for managing containerized projects
at main 121 lines 3.4 kB view raw
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}