refinements based on feedback
This commit is contained in:
105
src/app.rs
105
src/app.rs
@@ -205,29 +205,91 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn watch(mode: &Option<String>, args: Option<Vec<String>>) -> std::io::Result<()> {
|
fn watch(mode: &Option<String>, args: Option<Vec<String>>) -> std::io::Result<()> {
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
// save terminal state
|
||||||
|
let mut stdout = std::io::stdout();
|
||||||
|
// enter alternate screen — this is a separate terminal buffer,
|
||||||
|
// exiting it restores everything exactly as it was
|
||||||
|
write!(stdout, "\x1b[?1049h")?;
|
||||||
|
stdout.flush()?;
|
||||||
|
|
||||||
|
// make sure we always restore the terminal, even on panic
|
||||||
|
let result = watch_inner(mode, args);
|
||||||
|
|
||||||
|
// exit alternate screen
|
||||||
|
write!(stdout, "\x1b[?1049l")?;
|
||||||
|
stdout.flush()?;
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn watch_inner(mode: &Option<String>, args: Option<Vec<String>>) -> std::io::Result<()> {
|
||||||
use notify::{Event, RecursiveMode, Watcher, recommended_watcher};
|
use notify::{Event, RecursiveMode, Watcher, recommended_watcher};
|
||||||
|
use std::io::Write;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
let clear = || {
|
||||||
|
let mut stdout = std::io::stdout();
|
||||||
|
write!(stdout, "\x1b[2J\x1b[H").ok();
|
||||||
|
stdout.flush().ok();
|
||||||
|
};
|
||||||
|
|
||||||
|
let print_header = |label: &str| {
|
||||||
println!(
|
println!(
|
||||||
" {} project for changes (ctrl+c to stop)",
|
" {} project (ctrl+c to stop) — {label}",
|
||||||
"Watching".green().bold()
|
"Watching".green().bold()
|
||||||
);
|
);
|
||||||
|
println!("{}", "─".repeat(50).dimmed());
|
||||||
|
};
|
||||||
|
|
||||||
// do an initial build+run before watching
|
// track the running child process
|
||||||
build(mode, false)?;
|
let mut child: Option<std::process::Child> = None;
|
||||||
run(mode, args.clone())?;
|
|
||||||
|
let kill_child = |child: &mut Option<std::process::Child>| {
|
||||||
|
if let Some(c) = child {
|
||||||
|
c.kill().ok();
|
||||||
|
c.wait().ok();
|
||||||
|
}
|
||||||
|
*child = None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let run_child = |mode: &Option<String>,
|
||||||
|
args: &Option<Vec<String>>|
|
||||||
|
-> std::io::Result<std::process::Child> {
|
||||||
|
let conf = get_config().ok_or(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::NotFound,
|
||||||
|
"no Pallet.toml found",
|
||||||
|
))?;
|
||||||
|
let build_config = conf.get_or_default(mode).ok_or(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::NotFound,
|
||||||
|
"build layout not found",
|
||||||
|
))?;
|
||||||
|
let mut command = Command::new(format!("target/{}/{}", build_config.name, conf.name));
|
||||||
|
if let Some(a) = args {
|
||||||
|
for arg in a {
|
||||||
|
command.arg(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
command
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| std::io::Error::other(format!("{e}")))
|
||||||
|
};
|
||||||
|
|
||||||
|
// initial build + run
|
||||||
|
clear();
|
||||||
|
print_header("starting");
|
||||||
|
if build(mode, false).is_ok() {
|
||||||
|
child = run_child(mode, &args).ok();
|
||||||
|
}
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel::<notify::Result<Event>>();
|
let (tx, rx) = mpsc::channel::<notify::Result<Event>>();
|
||||||
|
|
||||||
let mut watcher = recommended_watcher(tx).map_err(|e| std::io::Error::other(format!("{e}")))?;
|
let mut watcher = recommended_watcher(tx).map_err(|e| std::io::Error::other(format!("{e}")))?;
|
||||||
|
|
||||||
watcher
|
watcher
|
||||||
.watch(Path::new("src"), RecursiveMode::Recursive)
|
.watch(Path::new("src"), RecursiveMode::Recursive)
|
||||||
.map_err(|e| std::io::Error::other(format!("{e}")))?;
|
.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()
|
let mut last_build = Instant::now()
|
||||||
.checked_sub(Duration::from_secs(1))
|
.checked_sub(Duration::from_secs(1))
|
||||||
.unwrap_or(Instant::now());
|
.unwrap_or(Instant::now());
|
||||||
@@ -235,37 +297,37 @@ fn watch(mode: &Option<String>, args: Option<Vec<String>>) -> std::io::Result<()
|
|||||||
loop {
|
loop {
|
||||||
match rx.recv() {
|
match rx.recv() {
|
||||||
Ok(Ok(event)) => {
|
Ok(Ok(event)) => {
|
||||||
// only care about actual file modifications
|
|
||||||
let is_relevant = matches!(
|
let is_relevant = matches!(
|
||||||
event.kind,
|
event.kind,
|
||||||
notify::EventKind::Create(_)
|
notify::EventKind::Create(_)
|
||||||
| notify::EventKind::Modify(_)
|
| notify::EventKind::Modify(_)
|
||||||
| notify::EventKind::Remove(_)
|
| notify::EventKind::Remove(_)
|
||||||
);
|
);
|
||||||
|
|
||||||
// only care about .c and .h files
|
|
||||||
let affects_source = event.paths.iter().any(|p| {
|
let affects_source = event.paths.iter().any(|p| {
|
||||||
matches!(
|
matches!(
|
||||||
p.extension().and_then(|e| e.to_str()),
|
p.extension().and_then(|e| e.to_str()),
|
||||||
Some("c") | Some("h")
|
Some("c") | Some("h")
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let debounced = last_build.elapsed() > Duration::from_millis(500);
|
let debounced = last_build.elapsed() > Duration::from_millis(500);
|
||||||
|
|
||||||
if is_relevant && affects_source && debounced {
|
if is_relevant && affects_source && debounced {
|
||||||
last_build = Instant::now();
|
last_build = Instant::now();
|
||||||
println!(
|
|
||||||
"\n {} changes detected, rebuilding...",
|
// kill whatever is currently running
|
||||||
"Watch".green().bold()
|
kill_child(&mut child);
|
||||||
);
|
|
||||||
if let Err(e) = build(mode, false) {
|
clear();
|
||||||
eprintln!(" {} {e}", "Error".red().bold());
|
print_header("rebuilding");
|
||||||
// don't exit — keep watching even if build fails
|
|
||||||
continue;
|
match build(mode, false) {
|
||||||
|
Ok(_) => {
|
||||||
|
child = run_child(mode, &args).ok();
|
||||||
}
|
}
|
||||||
if let Err(e) = run(mode, args.clone()) {
|
Err(e) => {
|
||||||
eprintln!(" {} {e}", "Error".red().bold());
|
eprintln!(" {} {e}", "Error".red().bold());
|
||||||
|
// child stays None until next successful build
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,6 +339,7 @@ fn watch(mode: &Option<String>, args: Option<Vec<String>>) -> std::io::Result<()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kill_child(&mut child);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user