use anyhow::Result;
use notify::{Event, RecursiveMode, Watcher};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc;
use std::time::{Duration, Instant};
use crate::builder::Builder;
use crate::cli::BuildOptions;
use crate::color;
fn should_ignore(path: &Path) -> bool {
if path.components().any(|c| c.as_os_str() == ".rsconstruct") {
return true;
}
if path.components().any(|c| c.as_os_str() == "out") {
return true;
}
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
if name.starts_with('.') && name.ends_with(".swp") {
return true;
}
if name.ends_with('~') {
return true;
}
if name.starts_with('#') && name.ends_with('#') {
return true;
}
if name.ends_with(".tmp") {
return true;
}
}
false
}
fn register_watches(
watcher: &mut impl Watcher,
paths: &[PathBuf],
verbose: bool,
) {
for path in paths {
let mode = if path.is_dir() {
RecursiveMode::Recursive
} else {
RecursiveMode::NonRecursive
};
if let Err(e) = watcher.watch(path, mode)
&& verbose {
println!("Warning: could not watch {}: {}", path.display(), e);
}
}
}
pub fn watch(opts: &BuildOptions, interrupted: Arc<AtomicBool>) -> Result<()> {
println!("{}", color::bold("Running initial build..."));
let mut watch_paths;
{
let mut builder = Builder::new()?;
watch_paths = builder.watch_paths();
if let Err(e) = builder.build(opts, Arc::clone(&interrupted), Vec::new()) {
println!("{}", color::red(&format!("Initial build error: {}", e)));
}
}
let (tx, rx) = mpsc::channel::<notify::Result<Event>>();
let mut watcher = notify::recommended_watcher(tx)?;
register_watches(&mut watcher, &watch_paths, opts.verbose);
println!("{}", color::green("Watching for changes... (Ctrl+C to stop)"));
let debounce_duration = Duration::from_millis(200);
let poll_interval = Duration::from_millis(500);
loop {
let got_event = loop {
if interrupted.load(Ordering::SeqCst) {
return Ok(());
}
match rx.recv_timeout(poll_interval) {
Ok(Ok(event)) => {
let all_ignored = event.paths.iter().all(|p| should_ignore(p));
if all_ignored {
continue;
}
break true;
}
Ok(Err(e)) => {
println!("{}", color::red(&format!("Watch error: {}", e)));
continue;
}
Err(mpsc::RecvTimeoutError::Timeout) => continue,
Err(mpsc::RecvTimeoutError::Disconnected) => break false,
}
};
if !got_event {
break;
}
let deadline = Instant::now() + debounce_duration;
loop {
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
break;
}
match rx.recv_timeout(remaining) {
Ok(_) => {}
Err(mpsc::RecvTimeoutError::Timeout) => break,
Err(mpsc::RecvTimeoutError::Disconnected) => break,
}
}
println!();
println!("{}", color::bold("Change detected, rebuilding..."));
{
let mut builder = Builder::new()?;
let new_paths = builder.watch_paths();
if let Err(e) = builder.build(opts, Arc::clone(&interrupted), Vec::new()) {
println!("{}", color::red(&format!("Build error: {}", e)));
}
for path in &new_paths {
if !watch_paths.contains(path) {
register_watches(&mut watcher, std::slice::from_ref(path), opts.verbose);
}
}
for path in &watch_paths {
if !new_paths.contains(path) {
let _ = watcher.unwatch(path);
}
}
watch_paths = new_paths;
}
println!("{}", color::green("Watching for changes... (Ctrl+C to stop)"));
}
Ok(())
}