Skip to content

Commit 3947c4d

Browse files
committed
Pause input while running an exercise
1 parent 664228e commit 3947c4d

File tree

4 files changed

+72
-42
lines changed

4 files changed

+72
-42
lines changed

src/watch.rs

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use anyhow::{Context, Error, Result};
1+
use anyhow::{Error, Result};
22
use notify_debouncer_mini::{
33
new_debouncer,
44
notify::{self, RecursiveMode},
@@ -7,7 +7,6 @@ use std::{
77
io::{self, Write},
88
path::Path,
99
sync::mpsc::channel,
10-
thread,
1110
time::Duration,
1211
};
1312

@@ -16,11 +15,7 @@ use crate::{
1615
list,
1716
};
1817

19-
use self::{
20-
notify_event::NotifyEventHandler,
21-
state::WatchState,
22-
terminal_event::{terminal_event_handler, InputEvent},
23-
};
18+
use self::{notify_event::NotifyEventHandler, state::WatchState, terminal_event::InputEvent};
2419

2520
mod notify_event;
2621
mod state;
@@ -47,7 +42,7 @@ fn run_watch(
4742
app_state: &mut AppState,
4843
notify_exercise_names: Option<&'static [&'static [u8]]>,
4944
) -> Result<WatchExit> {
50-
let (tx, rx) = channel();
45+
let (watch_event_sender, watch_event_receiver) = channel();
5146

5247
let mut manual_run = false;
5348
// Prevent dropping the guard until the end of the function.
@@ -56,7 +51,7 @@ fn run_watch(
5651
let mut debouncer = new_debouncer(
5752
Duration::from_millis(200),
5853
NotifyEventHandler {
59-
tx: tx.clone(),
54+
sender: watch_event_sender.clone(),
6055
exercise_names,
6156
},
6257
)
@@ -72,16 +67,12 @@ fn run_watch(
7267
None
7368
};
7469

75-
let mut watch_state = WatchState::build(app_state, manual_run)?;
76-
70+
let mut watch_state = WatchState::build(app_state, watch_event_sender, manual_run)?;
7771
let mut stdout = io::stdout().lock();
78-
watch_state.run_current_exercise(&mut stdout)?;
7972

80-
thread::Builder::new()
81-
.spawn(move || terminal_event_handler(tx, manual_run))
82-
.context("Failed to spawn a thread to handle terminal events")?;
73+
watch_state.run_current_exercise(&mut stdout)?;
8374

84-
while let Ok(event) = rx.recv() {
75+
while let Ok(event) = watch_event_receiver.recv() {
8576
match event {
8677
WatchEvent::Input(InputEvent::Next) => match watch_state.next_exercise(&mut stdout)? {
8778
ExercisesProgress::AllDone => break,

src/watch/notify_event.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::sync::mpsc::Sender;
44
use super::WatchEvent;
55

66
pub struct NotifyEventHandler {
7-
pub tx: Sender<WatchEvent>,
7+
pub sender: Sender<WatchEvent>,
88
/// Used to report which exercise was modified.
99
pub exercise_names: &'static [&'static [u8]],
1010
}
@@ -47,6 +47,6 @@ impl notify_debouncer_mini::DebounceEventHandler for NotifyEventHandler {
4747

4848
// An error occurs when the receiver is dropped.
4949
// After dropping the receiver, the debouncer guard should also be dropped.
50-
let _ = self.tx.send(output_event);
50+
let _ = self.sender.send(output_event);
5151
}
5252
}

src/watch/state.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ use crossterm::{
55
},
66
terminal, QueueableCommand,
77
};
8-
use std::io::{self, StdoutLock, Write};
8+
use std::{
9+
io::{self, StdoutLock, Write},
10+
sync::mpsc::Sender,
11+
thread,
12+
};
913

1014
use crate::{
1115
app_state::{AppState, ExercisesProgress},
@@ -14,6 +18,11 @@ use crate::{
1418
term::progress_bar,
1519
};
1620

21+
use super::{
22+
terminal_event::{terminal_event_handler, InputPauseGuard},
23+
WatchEvent,
24+
};
25+
1726
#[derive(PartialEq, Eq)]
1827
enum DoneStatus {
1928
DoneWithSolution(String),
@@ -31,11 +40,19 @@ pub struct WatchState<'a> {
3140
}
3241

3342
impl<'a> WatchState<'a> {
34-
pub fn build(app_state: &'a mut AppState, manual_run: bool) -> Result<Self> {
43+
pub fn build(
44+
app_state: &'a mut AppState,
45+
watch_event_sender: Sender<WatchEvent>,
46+
manual_run: bool,
47+
) -> Result<Self> {
3548
let term_width = terminal::size()
3649
.context("Failed to get the terminal size")?
3750
.0;
3851

52+
thread::Builder::new()
53+
.spawn(move || terminal_event_handler(watch_event_sender, manual_run))
54+
.context("Failed to spawn a thread to handle terminal events")?;
55+
3956
Ok(Self {
4057
app_state,
4158
output: Vec::with_capacity(OUTPUT_CAPACITY),
@@ -47,6 +64,9 @@ impl<'a> WatchState<'a> {
4764
}
4865

4966
pub fn run_current_exercise(&mut self, stdout: &mut StdoutLock) -> Result<()> {
67+
// Ignore any input until running the exercise is done.
68+
let _input_pause_guard = InputPauseGuard::scoped_pause();
69+
5070
self.show_hint = false;
5171

5272
writeln!(

src/watch/terminal_event.rs

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,32 @@
11
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
2-
use std::sync::mpsc::Sender;
2+
use std::sync::{
3+
atomic::{AtomicBool, Ordering::Relaxed},
4+
mpsc::Sender,
5+
};
36

47
use super::WatchEvent;
58

9+
static INPUT_PAUSED: AtomicBool = AtomicBool::new(false);
10+
11+
// Private unit type to force using the constructor function.
12+
#[must_use = "When the guard is dropped, the input is unpaused"]
13+
pub struct InputPauseGuard(());
14+
15+
impl InputPauseGuard {
16+
#[inline]
17+
pub fn scoped_pause() -> Self {
18+
INPUT_PAUSED.store(true, Relaxed);
19+
Self(())
20+
}
21+
}
22+
23+
impl Drop for InputPauseGuard {
24+
#[inline]
25+
fn drop(&mut self) {
26+
INPUT_PAUSED.store(false, Relaxed);
27+
}
28+
}
29+
630
pub enum InputEvent {
731
Run,
832
Next,
@@ -11,46 +35,41 @@ pub enum InputEvent {
1135
Quit,
1236
}
1337

14-
pub fn terminal_event_handler(tx: Sender<WatchEvent>, manual_run: bool) {
15-
let last_input_event = loop {
16-
let terminal_event = match event::read() {
17-
Ok(v) => v,
18-
Err(e) => {
19-
// If `send` returns an error, then the receiver is dropped and
20-
// a shutdown has been already initialized.
21-
let _ = tx.send(WatchEvent::TerminalEventErr(e));
22-
return;
23-
}
24-
};
25-
26-
match terminal_event {
27-
Event::Key(key) => {
38+
pub fn terminal_event_handler(sender: Sender<WatchEvent>, manual_run: bool) {
39+
let last_watch_event = loop {
40+
match event::read() {
41+
Ok(Event::Key(key)) => {
2842
match key.kind {
2943
KeyEventKind::Release | KeyEventKind::Repeat => continue,
3044
KeyEventKind::Press => (),
3145
}
3246

47+
if INPUT_PAUSED.load(Relaxed) {
48+
continue;
49+
}
50+
3351
let input_event = match key.code {
3452
KeyCode::Char('n') => InputEvent::Next,
3553
KeyCode::Char('h') => InputEvent::Hint,
36-
KeyCode::Char('l') => break InputEvent::List,
37-
KeyCode::Char('q') => break InputEvent::Quit,
54+
KeyCode::Char('l') => break WatchEvent::Input(InputEvent::List),
55+
KeyCode::Char('q') => break WatchEvent::Input(InputEvent::Quit),
3856
KeyCode::Char('r') if manual_run => InputEvent::Run,
3957
_ => continue,
4058
};
4159

42-
if tx.send(WatchEvent::Input(input_event)).is_err() {
60+
if sender.send(WatchEvent::Input(input_event)).is_err() {
4361
return;
4462
}
4563
}
46-
Event::Resize(width, _) => {
47-
if tx.send(WatchEvent::TerminalResize { width }).is_err() {
64+
Ok(Event::Resize(width, _)) => {
65+
if sender.send(WatchEvent::TerminalResize { width }).is_err() {
4866
return;
4967
}
5068
}
51-
Event::FocusGained | Event::FocusLost | Event::Mouse(_) => continue,
69+
Ok(Event::FocusGained | Event::FocusLost | Event::Mouse(_)) => continue,
70+
Err(e) => break WatchEvent::TerminalEventErr(e),
5271
}
5372
};
5473

55-
let _ = tx.send(WatchEvent::Input(last_input_event));
74+
let _ = sender.send(last_watch_event);
5675
}

0 commit comments

Comments
 (0)