// The `watch` command (upstream `typst-cli/src/watch.rs`; its `Status` is in
// `watch_status.mbt`), on the targets that can watch files (native, wasm).
///|
/// Execute a watching compilation command.
async fn watch(command : WatchCommand) -> Unit raise HintedFailure {
// (Upstream's `Timer`: `--timings` is not supported by this port, see
// `CompileConfig::new_impl`.)
let config = CompileConfig::watching(command)
guard config.output is Path(output) else {
raise bail("cannot write document to stdout in watch mode")
}
// Not upstream: the watcher gets its events from tasks of the CLI's
// event loop (notify uses a thread of its own).
let result : Ref[Result[Unit, HintedFailure]] = { val: Ok(()), }
@watcher.run(group => {
watching(command, config, output, group) catch {
err => result.val = Err(err)
}
}) catch {
err => raise bail(err.to_string())
}
match result.val {
Ok(_) => ()
Err(err) => raise err
}
}
///|
/// The body of [`watch`].
async fn watching(
command : WatchCommand,
config : CompileConfig,
output : String,
group : @watcher.Tasks,
) -> Unit raise HintedFailure {
// Create a file system watcher.
let watcher = @watcher.Watcher::new(Some(output), group) catch {
@kit.StrError(msg) => raise bail(msg)
}
// Create the world that serves sources, files, and fonts.
// Additionally, if any files do not exist, wait until they do.
let world = for ;; {
try
SystemWorld::new(
Some(command.args.input),
command.args.world,
command.args.process,
)
catch {
InputNotFound(path) | RootNotFound(path) as err => {
watcher.update([path]) catch {
@kit.StrError(msg) => raise bail(msg)
}
Status::Error.print(config)
print_error("\{err}")
watcher.wait() catch {
@kit.StrError(msg) => raise bail(msg)
}
}
err => raise bail("\{err}")
} noraise {
world => break world
}
}
// World construction has already awaited font discovery, outside the
// displayed compilation time.
// Perform initial compilation.
compile_once(world, config)
// Print warning when trying to watch stdin.
if config.input is Stdin {
warn_watching_std(world, config)
}
// Recompile whenever something relevant happens.
for ;; {
// Watch all dependencies of the most recent compilation.
watcher.update(world.dependencies()) catch {
@kit.StrError(msg) => raise bail(msg)
}
// Wait until anything relevant happens.
watcher.wait() catch {
@kit.StrError(msg) => raise bail(msg)
}
// Reset all dependencies.
world.reset()
// Recompile.
compile_once(world, config)
// Evict the cache.
@library.evict(10)
}
}
///|
/// Emits a warning when trying to watch stdin.
fn warn_watching_std(
world : SystemWorld,
config : CompileConfig,
) -> Unit raise HintedFailure {
let warning = @library.SourceDiagnostic::warning(
@syntax.DiagSpan::detached(),
"cannot watch changes for stdin",
)
.with_hint("to recompile on changes, watch a regular file instead")
.with_hint("to compile once and exit, please use `typst compile` instead")
print_diagnostics(world, [], [warning], config.diagnostic_format) catch {
err => raise bail("failed to print diagnostics (\{err})")
}
}