implement watch

This commit is contained in:
2026-03-23 16:42:27 -05:00
parent c5ecb78fd4
commit 03bc56007f
3 changed files with 309 additions and 3 deletions

View File

@@ -60,6 +60,14 @@ enum Subcommand {
/// The package to be added
package: String,
},
/// Watches for source code changes and triggers a recompilation && re-run on change
Watch {
/// The build mode to use
mode: Option<String>,
/// Arguments to pass to the project binary
#[arg(long, short)]
args: Option<Vec<String>>,
},
}
#[derive(clap::Subcommand)]
@@ -186,10 +194,92 @@ impl App {
std::process::exit(1);
}
}
Subcommand::Watch { mode, args } => {
if let Err(e) = watch(&mode, args) {
eprintln!("Error running watch: {e}");
std::process::exit(1);
}
}
}
}
}
fn watch(mode: &Option<String>, args: Option<Vec<String>>) -> std::io::Result<()> {
use notify::{Event, RecursiveMode, Watcher, recommended_watcher};
use std::sync::mpsc;
use std::time::{Duration, Instant};
println!(
" {} project for changes (ctrl+c to stop)",
"Watching".green().bold()
);
// do an initial build+run before watching
build(mode, false)?;
run(mode, args.clone())?;
let (tx, rx) = mpsc::channel::<notify::Result<Event>>();
let mut watcher = recommended_watcher(tx).map_err(|e| std::io::Error::other(format!("{e}")))?;
watcher
.watch(Path::new("src"), RecursiveMode::Recursive)
.map_err(|e| std::io::Error::other(format!("{e}")))?;
// debounce — ignore events within 500ms of the last one
// this prevents double-triggers from editors that write files in multiple steps
let mut last_build = Instant::now()
.checked_sub(Duration::from_secs(1))
.unwrap_or(Instant::now());
loop {
match rx.recv() {
Ok(Ok(event)) => {
// only care about actual file modifications
let is_relevant = matches!(
event.kind,
notify::EventKind::Create(_)
| notify::EventKind::Modify(_)
| notify::EventKind::Remove(_)
);
// only care about .c and .h files
let affects_source = event.paths.iter().any(|p| {
matches!(
p.extension().and_then(|e| e.to_str()),
Some("c") | Some("h")
)
});
let debounced = last_build.elapsed() > Duration::from_millis(500);
if is_relevant && affects_source && debounced {
last_build = Instant::now();
println!(
"\n {} changes detected, rebuilding...",
"Watch".green().bold()
);
if let Err(e) = build(mode, false) {
eprintln!(" {} {e}", "Error".red().bold());
// don't exit — keep watching even if build fails
continue;
}
if let Err(e) = run(mode, args.clone()) {
eprintln!(" {} {e}", "Error".red().bold());
}
}
}
Ok(Err(e)) => eprintln!(" {} watch error: {e}", "Error".red().bold()),
Err(e) => {
eprintln!(" {} channel error: {e}", "Error".red().bold());
break;
}
}
}
Ok(())
}
fn add_package(package: &str) -> std::io::Result<()> {
let status = Command::new("pkg-config")
.arg("--exists")