@@ -2,6 +2,7 @@ use crate::build;
22use crate :: slide:: Slide ;
33use crate :: Arguments ;
44use crate :: WatchArgs ;
5+ use ignore:: Walk ;
56use live_server:: listen;
67use notify:: recommended_watcher;
78use notify:: Event ;
@@ -139,14 +140,47 @@ fn remove_old_files(args: &Arguments, timestamp: u64) {
139140 }
140141}
141142
143+ #[ derive( Clone , Debug , PartialEq ) ]
144+ /// Status of the command.
145+ ///
146+ /// This can be used to avoid crashing the watch loop completely. Instead,
147+ /// report an error and ignore further actions until the loop is called again.
148+ /// This allows the user to fix the problem and continue without having to
149+ /// manually restart the `trv watch`.
150+ enum Status {
151+ Success ,
152+ Failure ,
153+ }
154+
155+ fn run_pre_typst ( watch_args : & WatchArgs ) -> Status {
156+ if let Some ( pre_typst) = & watch_args. pre_typst {
157+ tracing:: info!( "Running pre-typst command..." ) ;
158+ let mut cmd = std:: process:: Command :: new ( "/usr/bin/env" ) ;
159+ cmd. arg ( "bash" ) ;
160+ cmd. arg ( "-c" ) ;
161+ cmd. arg ( pre_typst) ;
162+ let output = cmd. output ( ) . expect ( "Failed to run pre-typst command" ) ;
163+ if !output. status . success ( ) {
164+ let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
165+ tracing:: error!( "pre-typst command failed: {}" , stderr. trim( ) ) ;
166+ return Status :: Failure ;
167+ }
168+ }
169+ Status :: Success
170+ }
171+
142172async fn watch_build ( watch_args : & WatchArgs , args : & Arguments ) {
143173 let release = false ;
144174 let input = watch_args. input . clone ( ) ;
145175 let audio_codec = None ;
146- let slides = build ( input. clone ( ) , args, release, audio_codec) . await ;
147- let timestamp = move_files_into_public ( args, & slides) ;
148- build_index ( args, & slides, timestamp, false ) ;
149- remove_old_files ( args, timestamp) ;
176+
177+ let status = run_pre_typst ( watch_args) ;
178+ if status == Status :: Success {
179+ let slides = build ( input. clone ( ) , args, release, audio_codec) . await ;
180+ let timestamp = move_files_into_public ( args, & slides) ;
181+ build_index ( args, & slides, timestamp, false ) ;
182+ remove_old_files ( args, timestamp) ;
183+ }
150184}
151185
152186fn spawn_server ( watch_args : & WatchArgs , args : & Arguments ) {
@@ -172,10 +206,20 @@ fn spawn_server(watch_args: &WatchArgs, args: &Arguments) {
172206pub async fn watch ( watch_args : & WatchArgs , args : & Arguments ) {
173207 let ( tx, rx) = mpsc:: channel :: < Result < Event > > ( ) ;
174208 let mut watcher = recommended_watcher ( tx) . unwrap ( ) ;
175- let input = watch_args. input . clone ( ) ;
176- watcher
177- . watch ( & input, notify:: RecursiveMode :: NonRecursive )
178- . expect ( "Failed to watch" ) ;
209+ let mode = notify:: RecursiveMode :: NonRecursive ;
210+ // Watch the current directory since that is probably the most intuitive
211+ // path to watch. It also would allow watching scripts that are in a
212+ // directory that is above the Typst file. For Typst, files have to be in
213+ // the same directory, but allowing the current directory gives more
214+ // flexibility.
215+ //
216+ // Flatten ignores the errors (e.g., permission errors).
217+ for entry in Walk :: new ( "./" ) . flatten ( ) {
218+ let path = entry. path ( ) ;
219+ if !path. is_dir ( ) {
220+ watcher. watch ( path, mode) . expect ( "Failed to watch" ) ;
221+ }
222+ }
179223
180224 let public_path = public_dir ( args) ;
181225 if !public_path. exists ( ) {
0 commit comments