testx/watcher/
terminal.rs1use std::io::{self, Read};
2use std::sync::mpsc::{self, Receiver, TryRecvError};
3use std::thread;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum WatchAction {
8 RunAll,
10 RunFailed,
12 Quit,
14 Continue,
16 ClearAndRun,
18}
19
20pub struct TerminalInput {
22 rx: Receiver<u8>,
23 _handle: thread::JoinHandle<()>,
24}
25
26impl TerminalInput {
27 pub fn new() -> Self {
29 let (tx, rx) = mpsc::channel();
30
31 let handle = thread::spawn(move || {
32 let stdin = io::stdin();
33 let mut buf = [0u8; 1];
34 loop {
35 match stdin.lock().read(&mut buf) {
36 Ok(0) => break,
37 Ok(_) => {
38 if tx.send(buf[0]).is_err() {
39 break;
40 }
41 }
42 Err(_) => break,
43 }
44 }
45 });
46
47 Self {
48 rx,
49 _handle: handle,
50 }
51 }
52
53 pub fn poll(&self) -> WatchAction {
55 match self.rx.try_recv() {
56 Ok(key) => Self::key_to_action(key),
57 Err(TryRecvError::Empty) => WatchAction::Continue,
58 Err(TryRecvError::Disconnected) => WatchAction::Quit,
59 }
60 }
61
62 fn key_to_action(key: u8) -> WatchAction {
64 match key {
65 b'q' | b'Q' => WatchAction::Quit,
66 b'a' | b'A' => WatchAction::RunAll,
67 b'f' | b'F' => WatchAction::RunFailed,
68 b'c' | b'C' => WatchAction::ClearAndRun,
69 b'\n' | b'\r' => WatchAction::RunAll,
70 _ => WatchAction::Continue,
71 }
72 }
73}
74
75impl Default for TerminalInput {
76 fn default() -> Self {
77 Self::new()
78 }
79}
80
81pub fn clear_screen() {
83 print!("\x1B[2J\x1B[1;1H");
85}
86
87pub fn print_watch_status(changed_count: usize) {
89 use colored::Colorize;
90
91 println!();
92 println!(
93 " {} {}",
94 "watching".cyan().bold(),
95 format!("{} file(s) changed", changed_count).dimmed(),
96 );
97 println!(
98 " {} {}",
99 "keys:".dimmed(),
100 "a = run all · f = run failed · q = quit · Enter = re-run".dimmed()
101 );
102 println!();
103}
104
105pub fn print_watch_separator() {
107 use colored::Colorize;
108
109 println!();
110 println!(
111 "{}",
112 "════════════════════════════════════════════════════════════"
113 .cyan()
114 .dimmed()
115 );
116 println!();
117}
118
119pub fn print_watch_start(root: &std::path::Path) {
121 use colored::Colorize;
122
123 println!();
124 println!(
125 " {} {} {}",
126 "testx".bold().cyan(),
127 "watch mode".bold(),
128 format!("({})", root.display()).dimmed(),
129 );
130 println!(
131 " {} {}",
132 "keys:".dimmed(),
133 "a = run all · f = run failed · q = quit · Enter = re-run".dimmed()
134 );
135 println!("{}", "─".repeat(60).dimmed());
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn key_to_action_quit() {
144 assert_eq!(TerminalInput::key_to_action(b'q'), WatchAction::Quit);
145 assert_eq!(TerminalInput::key_to_action(b'Q'), WatchAction::Quit);
146 }
147
148 #[test]
149 fn key_to_action_run_all() {
150 assert_eq!(TerminalInput::key_to_action(b'a'), WatchAction::RunAll);
151 assert_eq!(TerminalInput::key_to_action(b'A'), WatchAction::RunAll);
152 assert_eq!(TerminalInput::key_to_action(b'\n'), WatchAction::RunAll);
153 assert_eq!(TerminalInput::key_to_action(b'\r'), WatchAction::RunAll);
154 }
155
156 #[test]
157 fn key_to_action_run_failed() {
158 assert_eq!(TerminalInput::key_to_action(b'f'), WatchAction::RunFailed);
159 assert_eq!(TerminalInput::key_to_action(b'F'), WatchAction::RunFailed);
160 }
161
162 #[test]
163 fn key_to_action_clear() {
164 assert_eq!(TerminalInput::key_to_action(b'c'), WatchAction::ClearAndRun);
165 }
166
167 #[test]
168 fn key_to_action_unknown() {
169 assert_eq!(TerminalInput::key_to_action(b'x'), WatchAction::Continue);
170 assert_eq!(TerminalInput::key_to_action(b'z'), WatchAction::Continue);
171 }
172
173 #[test]
174 fn watch_action_equality() {
175 assert_eq!(WatchAction::Quit, WatchAction::Quit);
176 assert_ne!(WatchAction::Quit, WatchAction::RunAll);
177 }
178
179 #[test]
180 fn clear_screen_does_not_panic() {
181 clear_screen();
184 }
185}