implement watch
This commit is contained in:
90
src/app.rs
90
src/app.rs
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user