diff --git a/src/app.rs b/src/app.rs index a21d2af..94a022a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -205,29 +205,91 @@ impl App { } fn watch(mode: &Option, args: Option>) -> 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, args: Option>) -> std::io::Result<()> { use notify::{Event, RecursiveMode, Watcher, recommended_watcher}; + use std::io::Write; use std::sync::mpsc; use std::time::{Duration, Instant}; - println!( - " {} project for changes (ctrl+c to stop)", - "Watching".green().bold() - ); + let clear = || { + let mut stdout = std::io::stdout(); + write!(stdout, "\x1b[2J\x1b[H").ok(); + stdout.flush().ok(); + }; - // do an initial build+run before watching - build(mode, false)?; - run(mode, args.clone())?; + let print_header = |label: &str| { + println!( + " {} project (ctrl+c to stop) — {label}", + "Watching".green().bold() + ); + println!("{}", "─".repeat(50).dimmed()); + }; + + // track the running child process + let mut child: Option = None; + + let kill_child = |child: &mut Option| { + if let Some(c) = child { + c.kill().ok(); + c.wait().ok(); + } + *child = None; + }; + + let run_child = |mode: &Option, + args: &Option>| + -> std::io::Result { + 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::>(); - 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()); @@ -235,37 +297,37 @@ fn watch(mode: &Option, args: Option>) -> std::io::Result<() 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()); + + // kill whatever is currently running + kill_child(&mut child); + + clear(); + print_header("rebuilding"); + + match build(mode, false) { + Ok(_) => { + child = run_child(mode, &args).ok(); + } + Err(e) => { + eprintln!(" {} {e}", "Error".red().bold()); + // child stays None until next successful build + } } } } @@ -277,6 +339,7 @@ fn watch(mode: &Option, args: Option>) -> std::io::Result<() } } + kill_child(&mut child); Ok(()) }