watchForFileChanges method

Future<void> watchForFileChanges()

Implementation

Future<void> watchForFileChanges() async {
  logger.detail('Watching ${root.path} for changes');

  if (_watcherSubscription != null) {
    return;
  }

  final generation = _watcherGeneration;

  // Watch the root directory
  _watcherSubscription = DirectoryWatcher(root.path).events
      .asyncMap((event) async {
        final WatchEvent(:type, :path) = event;

        // Handle immediately (not debounced) so a trailing `r` command
        // cannot swallow a preceding route-file change in the debounce
        // window.
        if (_isDevCommandPath(path)) {
          if (type != ChangeType.REMOVE) {
            await _handleDevCommandFile(path);
          }
          return null;
        }

        if (type == ChangeType.REMOVE) {
          await onFileRemove(path);
        } else {
          await onFilesChange([path]);
        }

        return event;
      })
      .where((event) => event != null)
      .cast<WatchEvent>()
      .debounce(const Duration(milliseconds: 300))
      .listen((event) {
        final WatchEvent(:type, :path) = event;

        if (_isPathExcluded(path)) {
          logger.detail('Ignoring change in excluded path: $path');
          return;
        }
        _reload(path);
      });

  _watcherSubscription
      ?.asFuture<void>()
      .then((_) async {
        if (generation != _watcherGeneration || isCompleted) {
          return;
        }
        logger.detail('Root directory watcher closed normally');
        await _cancelWatcherSubscription();
        await stop();
      })
      .catchError((Object e, StackTrace st) async {
        if (generation != _watcherGeneration || isCompleted) {
          return;
        }
        await _handleWatcherError(e, st, watcherType: 'root directory');
      })
      .ignore();

  // Watch dependency directories if available
  if (getDependencyDirectories case final getDependencyDirectories?
      when _dependencyWatchers.isEmpty) {
    final dependencyDirs = await getDependencyDirectories();
    for (final dir in dependencyDirs) {
      logger.detail('Watching dependency directory: $dir');
      final watcher = DirectoryWatcher(dir).events
          .asyncMap((event) async {
            final WatchEvent(:type, :path) = event;

            // Only watch for Dart files in dependency directories
            if (p.extension(path) == '.dart') {
              if (type == ChangeType.REMOVE) {
                await onFileRemove(path);
              } else {
                await onFilesChange([path]);
              }
            }

            return event;
          })
          .debounce(const Duration(milliseconds: 300))
          .listen((event) {
            final WatchEvent(:type, :path) = event;

            if (p.extension(path) == '.dart') {
              if (_isPathExcluded(path)) {
                logger.detail('Ignoring change in excluded path: $path');
                return;
              }
              _reload(path);
            }
          });

      _dependencyWatchers.add(watcher);

      watcher
          .asFuture<void>()
          .then((_) async {
            if (generation != _watcherGeneration || isCompleted) {
              return;
            }
            logger.detail('Dependency watcher closed: $dir');
            await _cancelWatcherSubscription();
            await stop();
          })
          .catchError((Object e, StackTrace st) async {
            if (generation != _watcherGeneration || isCompleted) {
              return;
            }
            await _handleWatcherError(e, st, watcherType: 'dependency $dir');
          })
          .ignore();
    }
  }
}