summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock28
-rw-r--r--Cargo.toml2
-rw-r--r--src/watch.rs43
-rw-r--r--src/watch/notify_event.rs91
-rw-r--r--src/watch/state.rs5
-rw-r--r--src/watch/terminal_event.rs30
6 files changed, 91 insertions, 108 deletions
diff --git a/Cargo.lock b/Cargo.lock
index bededeb..adc3112 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -140,21 +140,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
[[package]]
-name = "crossbeam-channel"
-version = "0.5.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2"
-dependencies = [
- "crossbeam-utils",
-]
-
-[[package]]
-name = "crossbeam-utils"
-version = "0.8.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
-
-[[package]]
name = "crossterm"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -379,7 +364,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
"bitflags 2.6.0",
- "crossbeam-channel",
"filetime",
"fsevent-sys",
"inotify",
@@ -392,16 +376,6 @@ dependencies = [
]
[[package]]
-name = "notify-debouncer-mini"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d40b221972a1fc5ef4d858a2f671fb34c75983eb385463dff3780eeff6a9d43"
-dependencies = [
- "log",
- "notify",
-]
-
-[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -488,7 +462,7 @@ dependencies = [
"anyhow",
"clap",
"crossterm",
- "notify-debouncer-mini",
+ "notify",
"os_pipe",
"rustix",
"rustlings-macros",
diff --git a/Cargo.toml b/Cargo.toml
index 23dc4f6..f14b47a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -50,7 +50,7 @@ ahash = { version = "0.8.11", default-features = false }
anyhow = "1.0.89"
clap = { version = "4.5.17", features = ["derive"] }
crossterm = { version = "0.28.1", default-features = false, features = ["windows", "events"] }
-notify-debouncer-mini = { version = "0.4.1", default-features = false }
+notify = { version = "6.1.1", default-features = false, features = ["macos_fsevent"] }
os_pipe = "1.2.1"
rustlings-macros = { path = "rustlings-macros", version = "=6.3.0" }
serde_json = "1.0.128"
diff --git a/src/watch.rs b/src/watch.rs
index c937bfb..fd89b29 100644
--- a/src/watch.rs
+++ b/src/watch.rs
@@ -1,12 +1,12 @@
use anyhow::{Error, Result};
-use notify_debouncer_mini::{
- new_debouncer,
- notify::{self, RecursiveMode},
-};
+use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
use std::{
io::{self, Write},
path::Path,
- sync::mpsc::channel,
+ sync::{
+ atomic::{AtomicBool, Ordering::Relaxed},
+ mpsc::channel,
+ },
time::Duration,
};
@@ -21,6 +21,27 @@ mod notify_event;
mod state;
mod terminal_event;
+static EXERCISE_RUNNING: AtomicBool = AtomicBool::new(false);
+
+// Private unit type to force using the constructor function.
+#[must_use = "When the guard is dropped, the input is unpaused"]
+pub struct InputPauseGuard(());
+
+impl InputPauseGuard {
+ #[inline]
+ pub fn scoped_pause() -> Self {
+ EXERCISE_RUNNING.store(true, Relaxed);
+ Self(())
+ }
+}
+
+impl Drop for InputPauseGuard {
+ #[inline]
+ fn drop(&mut self) {
+ EXERCISE_RUNNING.store(false, Relaxed);
+ }
+}
+
enum WatchEvent {
Input(InputEvent),
FileChange { exercise_ind: usize },
@@ -47,21 +68,21 @@ fn run_watch(
let mut manual_run = false;
// Prevent dropping the guard until the end of the function.
// Otherwise, the file watcher exits.
- let _debouncer_guard = if let Some(exercise_names) = notify_exercise_names {
- let mut debouncer = new_debouncer(
- Duration::from_millis(200),
+ let _watcher_guard = if let Some(exercise_names) = notify_exercise_names {
+ let mut watcher = RecommendedWatcher::new(
NotifyEventHandler {
sender: watch_event_sender.clone(),
exercise_names,
},
+ Config::default().with_poll_interval(Duration::from_secs(1)),
)
.inspect_err(|_| eprintln!("{NOTIFY_ERR}"))?;
- debouncer
- .watcher()
+
+ watcher
.watch(Path::new("exercises"), RecursiveMode::Recursive)
.inspect_err(|_| eprintln!("{NOTIFY_ERR}"))?;
- Some(debouncer)
+ Some(watcher)
} else {
manual_run = true;
None
diff --git a/src/watch/notify_event.rs b/src/watch/notify_event.rs
index 9b23525..5ed8fd1 100644
--- a/src/watch/notify_event.rs
+++ b/src/watch/notify_event.rs
@@ -1,7 +1,10 @@
-use notify_debouncer_mini::{DebounceEventResult, DebouncedEventKind};
-use std::sync::mpsc::Sender;
+use notify::{
+ event::{MetadataKind, ModifyKind},
+ Event, EventKind,
+};
+use std::sync::{atomic::Ordering::Relaxed, mpsc::Sender};
-use super::WatchEvent;
+use super::{WatchEvent, EXERCISE_RUNNING};
pub struct NotifyEventHandler {
pub sender: Sender<WatchEvent>,
@@ -9,44 +12,56 @@ pub struct NotifyEventHandler {
pub exercise_names: &'static [&'static [u8]],
}
-impl notify_debouncer_mini::DebounceEventHandler for NotifyEventHandler {
- fn handle_event(&mut self, input_event: DebounceEventResult) {
- let output_event = match input_event {
- Ok(input_event) => {
- let Some(exercise_ind) = input_event
- .iter()
- .filter_map(|input_event| {
- if input_event.kind != DebouncedEventKind::Any {
- return None;
- }
-
- let file_name = input_event.path.file_name()?.to_str()?.as_bytes();
-
- if file_name.len() < 4 {
- return None;
- }
- let (file_name_without_ext, ext) = file_name.split_at(file_name.len() - 3);
-
- if ext != b".rs" {
- return None;
- }
-
- self.exercise_names
- .iter()
- .position(|exercise_name| *exercise_name == file_name_without_ext)
- })
- .min()
- else {
- return;
- };
+impl notify::EventHandler for NotifyEventHandler {
+ fn handle_event(&mut self, input_event: notify::Result<Event>) {
+ if EXERCISE_RUNNING.load(Relaxed) {
+ return;
+ }
- WatchEvent::FileChange { exercise_ind }
+ let input_event = match input_event {
+ Ok(v) => v,
+ Err(e) => {
+ // An error occurs when the receiver is dropped.
+ // After dropping the receiver, the debouncer guard should also be dropped.
+ let _ = self.sender.send(WatchEvent::NotifyErr(e));
+ return;
}
- Err(e) => WatchEvent::NotifyErr(e),
};
- // An error occurs when the receiver is dropped.
- // After dropping the receiver, the debouncer guard should also be dropped.
- let _ = self.sender.send(output_event);
+ match input_event.kind {
+ EventKind::Any => (),
+ EventKind::Modify(modify_kind) => match modify_kind {
+ ModifyKind::Any | ModifyKind::Data(_) => (),
+ ModifyKind::Metadata(metadata_kind) => match metadata_kind {
+ MetadataKind::Any | MetadataKind::WriteTime => (),
+ MetadataKind::AccessTime
+ | MetadataKind::Permissions
+ | MetadataKind::Ownership
+ | MetadataKind::Extended
+ | MetadataKind::Other => return,
+ },
+ ModifyKind::Name(_) | ModifyKind::Other => return,
+ },
+ EventKind::Access(_)
+ | EventKind::Create(_)
+ | EventKind::Remove(_)
+ | EventKind::Other => return,
+ }
+
+ let _ = input_event
+ .paths
+ .into_iter()
+ .filter_map(|path| {
+ let file_name = path.file_name()?.to_str()?.as_bytes();
+
+ let [file_name_without_ext @ .., b'.', b'r', b's'] = file_name else {
+ return None;
+ };
+
+ self.exercise_names
+ .iter()
+ .position(|exercise_name| *exercise_name == file_name_without_ext)
+ })
+ .try_for_each(|exercise_ind| self.sender.send(WatchEvent::FileChange { exercise_ind }));
}
}
diff --git a/src/watch/state.rs b/src/watch/state.rs
index 6e76001..8cccb40 100644
--- a/src/watch/state.rs
+++ b/src/watch/state.rs
@@ -18,10 +18,7 @@ use crate::{
term::progress_bar,
};
-use super::{
- terminal_event::{terminal_event_handler, InputPauseGuard},
- WatchEvent,
-};
+use super::{terminal_event::terminal_event_handler, InputPauseGuard, WatchEvent};
#[derive(PartialEq, Eq)]
enum DoneStatus {
diff --git a/src/watch/terminal_event.rs b/src/watch/terminal_event.rs
index 2a1dfdc..050c4ac 100644
--- a/src/watch/terminal_event.rs
+++ b/src/watch/terminal_event.rs
@@ -1,31 +1,7 @@
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
-use std::sync::{
- atomic::{AtomicBool, Ordering::Relaxed},
- mpsc::Sender,
-};
+use std::sync::{atomic::Ordering::Relaxed, mpsc::Sender};
-use super::WatchEvent;
-
-static INPUT_PAUSED: AtomicBool = AtomicBool::new(false);
-
-// Private unit type to force using the constructor function.
-#[must_use = "When the guard is dropped, the input is unpaused"]
-pub struct InputPauseGuard(());
-
-impl InputPauseGuard {
- #[inline]
- pub fn scoped_pause() -> Self {
- INPUT_PAUSED.store(true, Relaxed);
- Self(())
- }
-}
-
-impl Drop for InputPauseGuard {
- #[inline]
- fn drop(&mut self) {
- INPUT_PAUSED.store(false, Relaxed);
- }
-}
+use super::{WatchEvent, EXERCISE_RUNNING};
pub enum InputEvent {
Run,
@@ -44,7 +20,7 @@ pub fn terminal_event_handler(sender: Sender<WatchEvent>, manual_run: bool) {
KeyEventKind::Press => (),
}
- if INPUT_PAUSED.load(Relaxed) {
+ if EXERCISE_RUNNING.load(Relaxed) {
continue;
}