diff --git a/Cargo.lock b/Cargo.lock index f4e059c..eeef437 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,9 +21,9 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.11.2" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7926860314cbe2fb5d1f13731e387ab43bd32bca224e82e6e2db85de0a3dba49" +checksum = "f860ee6746d0c5b682147b2f7f8ef036d4f92fe518251a3a35ffa3650eafdf0e" dependencies = [ "actix-codec", "actix-rt", @@ -70,9 +70,9 @@ dependencies = [ [[package]] name = "actix-router" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" dependencies = [ "bytestring", "cfg-if", @@ -132,9 +132,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.12.1" +version = "4.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1654a77ba142e37f049637a3e5685f864514af11fcbc51cb51eb6596afe5b8d6" +checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf" dependencies = [ "actix-codec", "actix-http", @@ -396,9 +396,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytes" @@ -441,9 +441,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.58" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", "clap_derive", @@ -451,9 +451,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.58" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", @@ -584,9 +584,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] @@ -696,6 +696,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "file-id" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc6a637b6dc58414714eddd9170ff187ecb0933d4c7024d1abbd23a3cc26e9" +dependencies = [ + "windows-sys 0.60.2", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1085,9 +1094,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -1216,6 +1225,7 @@ dependencies = [ "askama", "clap", "notify", + "notify-debouncer-full", "pulldown-cmark", "rust-embed", "tokio", @@ -1286,6 +1296,19 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "notify-debouncer-full" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02b49179cfebc9932238d04d6079912d26de0379328872846118a0fa0dbb302" +dependencies = [ + "file-id", + "log", + "notify", + "notify-types", + "walkdir", +] + [[package]] name = "notify-types" version = "2.1.0" @@ -1303,9 +1326,9 @@ checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "objc2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ "objc2-encode", ] @@ -1427,9 +1450,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" @@ -1478,9 +1501,9 @@ dependencies = [ [[package]] name = "pulldown-cmark" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" +checksum = "83c41efbf8f90ac44de7f3a868f0867851d261b56291732d0cbf7cceaaeb55a6" dependencies = [ "bitflags 2.11.0", "getopts", @@ -1605,9 +1628,9 @@ checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rust-embed" @@ -1876,9 +1899,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.116" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1990,9 +2013,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -2007,9 +2030,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", @@ -2161,9 +2184,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -2174,9 +2197,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2184,9 +2207,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", @@ -2197,18 +2220,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -2525,18 +2548,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index c91d333..ce32d82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mdwatch" -version = "0.1.18" +version = "0.1.19" edition = "2024" authors = ["Santosh Shrestha "] description = "A simple CLI tool to live-preview Markdown files in your browser." @@ -17,6 +17,7 @@ ammonia = "4.1.1" askama = { version = "0.14.0", features = ["full"] } clap = { version = "4.5.46", features = ["derive"] } notify = "8.2.0" +notify-debouncer-full = "0.7.0" pulldown-cmark = "0.13.0" rust-embed = { version = "8.11.0", features = ["interpolate-folder-path"] } tokio = { version = "1.49.0", features = ["full"] } diff --git a/flake.lock b/flake.lock index e92a682..00015d9 100644 --- a/flake.lock +++ b/flake.lock @@ -59,11 +59,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1771008912, - "narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=", + "lastModified": 1772433332, + "narHash": "sha256-izhTDFKsg6KeVBxJS9EblGeQ8y+O8eCa6RcW874vxEc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a82ccc39b39b621151d6732718e3e250109076fa", + "rev": "cf59864ef8aa2e178cccedbe2c178185b0365705", "type": "github" }, "original": { diff --git a/src/main.rs b/src/main.rs index a1eb9c2..9bef2c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,11 @@ use actix_web::web; +use notify::event::RemoveKind; +use notify_debouncer_full::DebouncedEvent; +use notify_debouncer_full::{DebounceEventResult, new_debouncer, notify::*}; use pulldown_cmark::Options; -use std::fs; use std::path::Path; +use tokio::fs; +use tokio::time::Duration; mod args; use actix_web::App; use actix_web::HttpServer; @@ -13,7 +17,7 @@ use args::MdwatchArgs; use askama::Template; use clap::Parser; -use notify::{Event, RecursiveMode, Result, Watcher}; +use notify::{RecursiveMode, event::ModifyKind}; use rust_embed::Embed; use tokio::sync::mpsc; @@ -63,41 +67,46 @@ async fn ws_handler( ) -> actix_web::Result { let (response, mut session, mut _msg_stream) = actix_ws::handle(&req, body)?; let file_path = file.as_str().to_string(); - let (watch_tx, mut notify_rx) = mpsc::unbounded_channel::>(); - - let mut watcher = notify::recommended_watcher(move |res| { - let _ = watch_tx.send(res); - }) + let (watch_tx, mut notify_rx) = mpsc::unbounded_channel::(); + + let mut debouncer = new_debouncer( + Duration::from_secs(2), + None, + move |result: DebounceEventResult| match result { + Ok(events) => events.into_iter().for_each(|event| { + let _ = watch_tx.send(event); + }), + Err(errors) => errors + .iter() + .for_each(|error| eprintln!("watch error: {error:?}")), + }, + ) .map_err(actix_web::error::ErrorInternalServerError)?; - watcher - .watch(Path::new(&file_path), RecursiveMode::NonRecursive) + debouncer + .watch(&file_path, RecursiveMode::NonRecursive) .map_err(actix_web::error::ErrorInternalServerError)?; actix_web::rt::spawn(async move { // Keep the watcher alive in this async task to keep the msg_stream alive - let _watcher = watcher; - while let Some(res) = notify_rx.recv().await { - match res { - Ok(event) => { - if event.kind.is_remove() { - eprintln!("File removed: {}", file_path); - break; - } - if event.kind.is_modify() { - let latest_markdown = match get_markdown(&file_path) { - Ok(md) => md, - Err(e) => { - eprintln!("Error reading markdown file: {e}"); - continue; - } - }; - if session.text(latest_markdown).await.is_err() { - break; - } + let _watcher = debouncer; + + while let Some(event) = notify_rx.recv().await { + if matches!(event.kind, EventKind::Remove(RemoveKind::File)) { + eprintln!("File removed: {}", file_path); + break; + } + if matches!(event.kind, EventKind::Modify(ModifyKind::Data(_))) { + let latest_markdown = match get_markdown(&file_path).await { + Ok(md) => md, + Err(e) => { + eprintln!("Error reading markdown file: {e}"); + continue; } + }; + if session.text(latest_markdown).await.is_err() { + break; } - Err(e) => eprintln!("watch error: {e:?}"), } } @@ -107,8 +116,8 @@ async fn ws_handler( Ok(response) } -pub fn get_markdown(file_path: &String) -> std::io::Result { - let markdown_input: String = fs::read_to_string(file_path)?; +async fn get_markdown(file_path: &String) -> std::io::Result { + let markdown_input: String = fs::read_to_string(file_path).await?; let options = Options::all(); let parser = pulldown_cmark::Parser::new_ext(&markdown_input, options); @@ -120,11 +129,11 @@ pub fn get_markdown(file_path: &String) -> std::io::Result { #[derive(Template)] #[template(path = "main.html")] -pub struct Mdwatch { - pub content: String, - pub title: String, - pub style: String, - pub script: String, +struct Mdwatch { + content: String, + title: String, + style: String, + script: String, } #[get("/")] @@ -152,7 +161,7 @@ async fn home(file: web::Data) -> actix_web::Result { )); }; - let html_output = match get_markdown(&file.as_str().to_string()) { + let html_output = match get_markdown(&file.as_str().to_string()).await { Ok(html) => html, Err(e) => { eprintln!("Error processing markdown file: {e}");