summaryrefslogtreecommitdiff
path: root/cli/src/watch.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-07-02 19:59:52 +0200
committerLaurenz <laurmaedje@gmail.com>2023-07-02 20:07:43 +0200
commitebfdb1dafa430786db10dad2ef7d5467c1bdbed1 (patch)
tree2bbc24ddb4124c4bb14dec0e536129d4de37b056 /cli/src/watch.rs
parent3ab19185093d7709f824b95b979060ce125389d8 (diff)
Move everything into `crates/` directory
Diffstat (limited to 'cli/src/watch.rs')
-rw-r--r--cli/src/watch.rs170
1 files changed, 0 insertions, 170 deletions
diff --git a/cli/src/watch.rs b/cli/src/watch.rs
deleted file mode 100644
index 2ad73f85..00000000
--- a/cli/src/watch.rs
+++ /dev/null
@@ -1,170 +0,0 @@
-use std::collections::HashSet;
-use std::io::{self, IsTerminal, Write};
-use std::path::{Path, PathBuf};
-
-use codespan_reporting::term::{self, termcolor};
-use notify::{RecommendedWatcher, RecursiveMode, Watcher};
-use same_file::is_same_file;
-use termcolor::WriteColor;
-use typst::diag::StrResult;
-use typst::eval::eco_format;
-
-use crate::args::CompileCommand;
-use crate::color_stream;
-use crate::compile::compile_once;
-use crate::world::SystemWorld;
-
-/// Execute a watching compilation command.
-pub fn watch(mut command: CompileCommand) -> StrResult<()> {
- // Create the world that serves sources, files, and fonts.
- let mut world = SystemWorld::new(&command)?;
-
- // Perform initial compilation.
- compile_once(&mut world, &mut command, true)?;
-
- // Setup file watching.
- let (tx, rx) = std::sync::mpsc::channel();
- let mut watcher = RecommendedWatcher::new(tx, notify::Config::default())
- .map_err(|_| "failed to setup file watching")?;
-
- // Watch all the files that are used by the input file and its dependencies.
- watch_dependencies(&mut world, &mut watcher, HashSet::new())?;
-
- // Handle events.
- let timeout = std::time::Duration::from_millis(100);
- let output = command.output();
- loop {
- let mut recompile = false;
- for event in rx
- .recv()
- .into_iter()
- .chain(std::iter::from_fn(|| rx.recv_timeout(timeout).ok()))
- {
- let event = event.map_err(|_| "failed to watch directory")?;
- recompile |= is_event_relevant(&event, &output);
- }
-
- if recompile {
- // Retrieve the dependencies of the last compilation.
- let previous: HashSet<PathBuf> =
- world.dependencies().map(ToOwned::to_owned).collect();
-
- // Recompile.
- compile_once(&mut world, &mut command, true)?;
- comemo::evict(10);
-
- // Adjust the watching.
- watch_dependencies(&mut world, &mut watcher, previous)?;
- }
- }
-}
-
-/// Adjust the file watching. Watches all new dependencies and unwatches
-/// all `previous` dependencies that are not relevant anymore.
-#[tracing::instrument(skip_all)]
-fn watch_dependencies(
- world: &mut SystemWorld,
- watcher: &mut dyn Watcher,
- mut previous: HashSet<PathBuf>,
-) -> StrResult<()> {
- // Watch new paths that weren't watched yet.
- for path in world.dependencies() {
- let watched = previous.remove(path);
- if path.exists() && !watched {
- tracing::info!("Watching {}", path.display());
- watcher
- .watch(path, RecursiveMode::NonRecursive)
- .map_err(|_| eco_format!("failed to watch {path:?}"))?;
- }
- }
-
- // Unwatch old paths that don't need to be watched anymore.
- for path in previous {
- tracing::info!("Unwatching {}", path.display());
- watcher.unwatch(&path).ok();
- }
-
- Ok(())
-}
-
-/// Whether a watch event is relevant for compilation.
-fn is_event_relevant(event: &notify::Event, output: &Path) -> bool {
- // Never recompile because the output file changed.
- if event
- .paths
- .iter()
- .all(|path| is_same_file(path, output).unwrap_or(false))
- {
- return false;
- }
-
- match &event.kind {
- notify::EventKind::Any => true,
- notify::EventKind::Access(_) => false,
- notify::EventKind::Create(_) => true,
- notify::EventKind::Modify(kind) => match kind {
- notify::event::ModifyKind::Any => true,
- notify::event::ModifyKind::Data(_) => true,
- notify::event::ModifyKind::Metadata(_) => false,
- notify::event::ModifyKind::Name(_) => true,
- notify::event::ModifyKind::Other => false,
- },
- notify::EventKind::Remove(_) => true,
- notify::EventKind::Other => false,
- }
-}
-
-/// The status in which the watcher can be.
-pub enum Status {
- Compiling,
- Success(std::time::Duration),
- Error,
-}
-
-impl Status {
- /// Clear the terminal and render the status message.
- pub fn print(&self, command: &CompileCommand) -> io::Result<()> {
- let output = command.output();
- let timestamp = chrono::offset::Local::now().format("%H:%M:%S");
- let color = self.color();
-
- let mut w = color_stream();
- if std::io::stderr().is_terminal() {
- // Clear the terminal.
- let esc = 27 as char;
- write!(w, "{esc}c{esc}[1;1H")?;
- }
-
- w.set_color(&color)?;
- write!(w, "watching")?;
- w.reset()?;
- writeln!(w, " {}", command.input.display())?;
-
- w.set_color(&color)?;
- write!(w, "writing to")?;
- w.reset()?;
- writeln!(w, " {}", output.display())?;
-
- writeln!(w)?;
- writeln!(w, "[{timestamp}] {}", self.message())?;
- writeln!(w)?;
-
- w.flush()
- }
-
- fn message(&self) -> String {
- match self {
- Self::Compiling => "compiling ...".into(),
- Self::Success(duration) => format!("compiled successfully in {duration:.2?}"),
- Self::Error => "compiled with errors".into(),
- }
- }
-
- fn color(&self) -> termcolor::ColorSpec {
- let styles = term::Styles::default();
- match self {
- Self::Error => styles.header_error,
- _ => styles.header_note,
- }
- }
-}