• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

facet-rs / facet / 14468617911

15 Apr 2025 11:47AM UTC coverage: 29.024% (-1.8%) from 30.835%
14468617911

Pull #225

github

web-flow
Merge d5543fd61 into 00641afc4
Pull Request #225: Provide best-of-class precommit hook (facet-dev)

4 of 940 new or added lines in 9 files covered. (0.43%)

1 existing line in 1 file now uncovered.

2032 of 7001 relevant lines covered (29.02%)

24.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/facet-dev/src/menu.rs
1
use facet_ansi::{Style, Stylize};
2

3
/// A menu item
4
pub struct MenuItem {
5
    /// may contain `[]`, like `[Y]es` — that's the shortcut key. pressing it immediately executes the action
6
    pub label: String,
7

8
    /// style for the action
9
    pub style: Style,
10

11
    /// returned by show_menu
12
    pub action: String,
13
}
14

NEW
15
pub fn show_menu(question: &str, items: &[MenuItem]) -> Option<String> {
×
16
    // Requires the 'termion' crate for raw input handling
17
    use std::io::{self, Write};
18
    use termion::event::{Event, Key};
19
    use termion::input::TermRead;
20
    use termion::raw::IntoRawMode;
21

22
    use termion::color;
NEW
23
    println!("{}", question);
×
NEW
24
    for item in items.iter() {
×
NEW
25
        let label = &item.label;
×
NEW
26
        let mut chars = label.chars().peekable();
×
NEW
27
        let mut after_colon = false;
×
NEW
28
        while let Some(ch) = chars.next() {
×
29
            // Check for shortcut
NEW
30
            if ch == '[' {
×
NEW
31
                let mut shortcut = String::new();
×
NEW
32
                while let Some(&next_ch) = chars.peek() {
×
NEW
33
                    if next_ch == ']' {
×
NEW
34
                        chars.next(); // consume ']'
×
NEW
35
                        print!("[{}]", shortcut.style(item.style));
×
NEW
36
                        break;
×
37
                    } else {
NEW
38
                        shortcut.push(next_ch);
×
NEW
39
                        chars.next();
×
40
                    }
41
                }
NEW
42
                continue;
×
43
            }
44
            // Check for ':'
NEW
45
            if !after_colon && ch == ':' {
×
NEW
46
                after_colon = true;
×
NEW
47
                print!("{}", color::Fg(color::LightBlack));
×
NEW
48
                print!("{}", ch);
×
NEW
49
                continue;
×
50
            }
51
            // After colon? Dim color, otherwise normal
NEW
52
            if after_colon {
×
NEW
53
                print!("{}", ch.to_string().dim());
×
54
            } else {
NEW
55
                print!("{}", ch);
×
56
            }
57
        }
NEW
58
        if after_colon {
×
NEW
59
            print!("{}", color::Fg(color::Reset));
×
60
        }
NEW
61
        println!();
×
62
    }
NEW
63
    print!("Enter your choice: ");
×
NEW
64
    io::stdout().flush().unwrap();
×
65

66
    #[cfg(any(target_os = "linux", target_os = "macos"))]
NEW
67
    let tty = std::fs::OpenOptions::new()
×
68
        .read(true)
69
        .write(true)
70
        .open("/dev/tty")
71
        .unwrap();
72
    #[cfg(any(target_os = "linux", target_os = "macos"))]
NEW
73
    let mut stdout = tty.try_clone().unwrap().into_raw_mode().unwrap();
×
74
    #[cfg(any(target_os = "linux", target_os = "macos"))]
NEW
75
    let stdin = tty;
×
76
    #[cfg(not(any(target_os = "linux", target_os = "macos")))]
NEW
77
    let mut stdout = stdout().into_raw_mode().unwrap();
×
78
    #[cfg(not(any(target_os = "linux", target_os = "macos")))]
NEW
79
    let stdin = stdin();
×
NEW
80
    let mut result = String::new();
×
NEW
81
    let mut selected_action = String::new();
×
82

NEW
83
    for evt in stdin.events() {
×
NEW
84
        let evt = evt.unwrap();
×
NEW
85
        match evt {
×
NEW
86
            Event::Key(Key::Char(c)) => {
×
NEW
87
                if c.is_ascii_digit() {
×
NEW
88
                    result.push(c);
×
NEW
89
                    write!(stdout, "{}", c).unwrap();
×
NEW
90
                    stdout.flush().unwrap();
×
91

NEW
92
                    if let Ok(idx) = result.parse::<usize>() {
×
NEW
93
                        if idx >= 1 && idx <= items.len() {
×
NEW
94
                            selected_action = items[idx - 1].action.clone();
×
NEW
95
                            break;
×
96
                        }
97
                    }
98
                } else {
99
                    // Check for shortcut key in label. Look for e.g. [Y]
NEW
100
                    let mut found = false;
×
NEW
101
                    for item in items {
×
NEW
102
                        if let Some(start) = item.label.find('[') {
×
NEW
103
                            if let Some(end) = item.label[start + 1..].find(']') {
×
NEW
104
                                let shortcut = &item.label[start + 1..start + 1 + end];
×
NEW
105
                                if shortcut.eq_ignore_ascii_case(&c.to_string()) {
×
NEW
106
                                    selected_action = item.action.clone();
×
NEW
107
                                    found = true;
×
NEW
108
                                    break;
×
109
                                }
110
                            }
111
                        }
112
                    }
NEW
113
                    if found {
×
NEW
114
                        break;
×
115
                    }
116
                }
117
            }
118
            Event::Key(Key::Backspace) => {
NEW
119
                if !result.is_empty() {
×
NEW
120
                    result.pop();
×
NEW
121
                    write!(stdout, "\x08 \x08").unwrap(); // Erase last character
×
NEW
122
                    stdout.flush().unwrap();
×
123
                }
124
            }
125
            Event::Key(Key::Esc) => {
126
                // Allow cancelling with ESC
NEW
127
                break;
×
128
            }
129
            Event::Key(Key::Ctrl('c')) | Event::Key(Key::Ctrl('d')) => {
130
                // Allow cancelling with Ctrl-C or Ctrl-D
NEW
131
                break;
×
132
            }
NEW
133
            _ => {}
×
134
        }
135
    }
136

NEW
137
    if !selected_action.is_empty() {
×
NEW
138
        Some(selected_action)
×
139
    } else {
NEW
140
        None
×
141
    }
142
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc