Implement debounce time for automatic reloading (#117)

This commit is contained in:
Max
2025-11-26 21:22:58 +01:00
committed by GitHub
parent 0a0112a7e1
commit 55e0c2b33f
3 changed files with 67 additions and 27 deletions
Generated
+7
View File
@@ -810,6 +810,12 @@ dependencies = [
"syn 2.0.111",
]
[[package]]
name = "debounce"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f2e5bc95e82bd8e9b333f4c5ff6dceab54e2e99f4d8cef2a680d417206ead34"
[[package]]
name = "deltae"
version = "0.3.2"
@@ -3020,6 +3026,7 @@ dependencies = [
"criterion",
"crossterm",
"csscolorparser 0.8.0",
"debounce",
"flexi_logger",
"flume",
"futures-util",
+1
View File
@@ -51,6 +51,7 @@ flexi_logger = "0.31"
# for tracing with tokio-console
console-subscriber = { version = "0.5.0", optional = true }
debounce = "0.2.2"
[profile.production]
inherits = "release"
+46 -14
View File
@@ -6,7 +6,10 @@ use std::{
borrow::Cow,
ffi::OsString,
io::{BufReader, Read as _, Stdout, Write as _, stdout},
path::PathBuf
mem,
path::PathBuf,
sync::{Arc, Mutex},
time::Duration
};
use crossterm::{
@@ -17,6 +20,7 @@ use crossterm::{
enable_raw_mode, window_size
}
};
use debounce::EventDebouncer;
use flexi_logger::FileSpec;
use flume::{Sender, r#async::RecvStream};
use futures_util::{FutureExt as _, stream::StreamExt as _};
@@ -83,6 +87,8 @@ async fn inner_main() -> Result<(), WrappedErr> {
#[cfg(feature = "tracing")]
console_subscriber::init();
const DEFAULT_DEBOUNCE_DELAY: Duration = Duration::from_millis(50);
let flags = xflags::parse_or_exit! {
/// Display the pdf with the pages starting at the right hand size and moving left and
/// adjust input keys to match
@@ -91,6 +97,9 @@ async fn inner_main() -> Result<(), WrappedErr> {
optional -m,--max-wide max_wide: NonZeroUsize
/// Fullscreen the pdf (hide document name, page count, etc)
optional -f,--fullscreen
/// The time to wait for the file to stop changing before reloading, in milliseconds.
/// Defaults to 50ms.
optional --reload-delay reload_delay: u64
/// The number of pages to prerender surrounding the currently-shown page; 0 means no
/// limit. By default, there is no limit.
optional -p,--prerender prerender: usize
@@ -162,7 +171,10 @@ async fn inner_main() -> Result<(), WrappedErr> {
watch_to_render_tx,
path.file_name()
.ok_or_else(|| WrappedErr("Path does not have a last component??".into()))?
.to_owned()
.to_owned(),
flags
.reload_delay
.map_or(DEFAULT_DEBOUNCE_DELAY, Duration::from_millis)
))
.map_err(|e| WrappedErr(format!("Couldn't start watching the provided file: {e}").into()))?;
@@ -454,12 +466,32 @@ async fn enter_redraw_loop(
fn on_notify_ev(
to_tui_tx: flume::Sender<Result<RenderInfo, RenderError>>,
to_render_tx: flume::Sender<RenderNotif>,
file_name: OsString
file_name: OsString,
debounce_delay: Duration
) -> impl Fn(notify::Result<Event>) {
move |res| match res {
let last_event: Mutex<Result<(), RenderError>> = Mutex::new(Ok(()));
let last_event = Arc::new(last_event);
let debouncer = EventDebouncer::new(debounce_delay, {
let last_event = last_event.clone();
move |()| {
let event = mem::replace(&mut *last_event.lock().unwrap(), Ok(()));
match event {
// This shouldn't fail to send unless the receiver gets disconnected. If that's
// happened, then like the main thread has panicked or something, so it doesn't matter
// we don't handle the error here.
Ok(()) => to_render_tx.send(RenderNotif::Reload).unwrap(),
// If we get an error here, and then an error sending, everything's going wrong. Just give
// up lol.
Err(e) => to_tui_tx.send(Err(RenderError::Notify(e))).unwrap(),
Err(e) => to_tui_tx.send(Err(e)).unwrap()
}
}
});
move |res| {
let event = match res {
Err(e) => Err(RenderError::Notify(e)),
// TODO: Should we match EventKind::Rename and propogate that so that the other parts of the
// process know that too? Or should that be
Ok(ev) => {
@@ -475,17 +507,17 @@ fn on_notify_ev(
}
match ev.kind {
EventKind::Access(_) => (),
EventKind::Remove(_) => to_tui_tx
.send(Err(RenderError::Converting("File was deleted".into())))
.unwrap(),
// This shouldn't fail to send unless the receiver gets disconnected. If that's
// happened, then like the main thread has panicked or something, so it doesn't matter
// we don't handle the error here.
EventKind::Other | EventKind::Any | EventKind::Create(_) | EventKind::Modify(_) =>
to_render_tx.send(RenderNotif::Reload).unwrap(),
EventKind::Access(_) => return,
EventKind::Remove(_) => Err(RenderError::Converting("File was deleted".into())),
EventKind::Other
| EventKind::Any
| EventKind::Create(_)
| EventKind::Modify(_) => Ok(())
}
}
};
*last_event.lock().unwrap() = event;
debouncer.put(());
}
}