@@ -100,12 +100,10 @@ impl UnifiedWatcher {
100100 ) ;
101101 }
102102
103- // Watch all directories
104- for dir in new_dirs {
105- self . watch_directory ( & dir) ?;
106- }
107-
103+ // FSEvents watches roots recursively; register those first so the
104+ // per-directory batch can skip paths they already cover.
108105 self . register_handler_roots ( ) . await ;
106+ self . watch_directories ( & new_dirs) ?;
109107
110108 // Subscribe to broadcaster for IndexReloaded events
111109 let mut broadcast_rx = self . broadcaster . subscribe ( ) ;
@@ -180,36 +178,53 @@ impl UnifiedWatcher {
180178 }
181179 }
182180
183- /// Watch a directory for changes.
184- fn watch_directory ( & mut self , dir : & PathBuf ) -> Result < ( ) , WatchError > {
185- let watch_path = if dir. is_absolute ( ) {
186- dir. clone ( )
181+ /// Register a batch without restarting the platform event stream per path.
182+ fn watch_directories ( & mut self , dirs : & [ PathBuf ] ) -> Result < ( ) , WatchError > {
183+ let watch_paths: Vec < _ > = dirs
184+ . iter ( )
185+ . map ( |dir| {
186+ if dir. is_absolute ( ) {
187+ dir. clone ( )
188+ } else {
189+ self . workspace_root . join ( dir)
190+ }
191+ } )
192+ . filter ( |path| {
193+ !cfg ! ( target_os = "macos" )
194+ || !self . handler_roots . iter ( ) . any ( |root| path. starts_with ( root) )
195+ } )
196+ . collect ( ) ;
197+ if watch_paths. is_empty ( ) {
198+ return Ok ( ( ) ) ;
199+ }
200+ // FSEvents has a finite path list. Recursive roots avoid one native
201+ // watch per source directory; handlers still enforce ignore rules.
202+ let mode = if cfg ! ( target_os = "macos" ) {
203+ RecursiveMode :: Recursive
187204 } else {
188- self . workspace_root . join ( dir )
205+ RecursiveMode :: NonRecursive
189206 } ;
190-
191- match self
192- . _watcher
193- . watch ( & watch_path, RecursiveMode :: NonRecursive )
194- {
195- Ok ( _) => {
196- crate :: debug_event!(
197- "watcher" ,
198- "watching" ,
199- "{}" ,
200- crate :: parsing:: paths:: render_absolute_path( & watch_path) . display( )
201- ) ;
202- Ok ( ( ) )
203- }
204- Err ( e) => {
205- tracing:: warn!(
206- "[watcher] failed to watch {}: {e}" ,
207- crate :: parsing:: paths:: render_absolute_path( & watch_path) . display( )
208- ) ;
209- // Continue - don't fail completely
210- Ok ( ( ) )
207+ let mut paths = self . _watcher . paths_mut ( ) ;
208+ for watch_path in watch_paths {
209+ match paths. add ( & watch_path, mode) {
210+ Ok ( _) => {
211+ crate :: debug_event!(
212+ "watcher" ,
213+ "watching" ,
214+ "{}" ,
215+ crate :: parsing:: paths:: render_absolute_path( & watch_path) . display( )
216+ ) ;
217+ }
218+ Err ( e) => {
219+ tracing:: warn!(
220+ "[watcher] failed to watch {}: {e}" ,
221+ crate :: parsing:: paths:: render_absolute_path( & watch_path) . display( )
222+ ) ;
223+ }
211224 }
212225 }
226+ paths. commit ( ) ?;
227+ Ok ( ( ) )
213228 }
214229
215230 /// Handle an incoming file event.
@@ -305,12 +320,16 @@ impl UnifiedWatcher {
305320 }
306321 roots. extend ( handler_roots) ;
307322 }
308- for root in & roots {
309- if self . registry . add_watch_dir ( root. clone ( ) ) {
310- if let Err ( e) = self . watch_directory ( root) {
311- tracing:: warn!( "[watcher] failed to watch root: {e}" ) ;
312- }
313- }
323+ let new_roots: Vec < _ > = roots
324+ . iter ( )
325+ . filter ( |root| {
326+ let new_dir = self . registry . add_watch_dir ( ( * root) . clone ( ) ) ;
327+ new_dir || ( cfg ! ( target_os = "macos" ) && !self . handler_roots . contains ( root) )
328+ } )
329+ . cloned ( )
330+ . collect ( ) ;
331+ if let Err ( e) = self . watch_directories ( & new_roots) {
332+ tracing:: warn!( "[watcher] failed to watch roots: {e}" ) ;
314333 }
315334 self . handler_roots = roots;
316335 self . batch_sync_roots = sync_roots;
@@ -333,12 +352,12 @@ impl UnifiedWatcher {
333352 )
334353 } ;
335354
336- for dir in dirs {
337- if self . registry . add_watch_dir ( dir . clone ( ) ) {
338- if let Err ( e ) = self . watch_directory ( & dir) {
339- tracing :: warn! ( "[watcher] failed to watch created dir: {e}" ) ;
340- }
341- }
355+ let new_dirs : Vec < _ > = dirs
356+ . into_iter ( )
357+ . filter ( |dir| self . registry . add_watch_dir ( dir. clone ( ) ) )
358+ . collect ( ) ;
359+ if let Err ( e ) = self . watch_directories ( & new_dirs ) {
360+ tracing :: warn! ( "[watcher] failed to watch created directories: {e}" ) ;
342361 }
343362 if !files. is_empty ( ) {
344363 crate :: log_event!(
@@ -716,10 +735,8 @@ impl UnifiedWatcher {
716735 . collect ( ) ;
717736
718737 // Watch any new directories
719- for dir in dirs_to_watch {
720- if let Err ( e) = self . watch_directory ( & dir) {
721- tracing:: warn!( "[watcher] failed to watch new directory: {e}" ) ;
722- }
738+ if let Err ( e) = self . watch_directories ( & dirs_to_watch) {
739+ tracing:: warn!( "[watcher] failed to watch new directories: {e}" ) ;
723740 }
724741
725742 // Config reload can add or drop roots; re-register them.
0 commit comments