@Nullable AsyncFileListener.ChangeApplier prepareChange(@NotNull List<? extends @NotNull VFileEvent> events) {
    Collection<VirtualFile> files = ContainerUtil.map2SetNotNull(events, event -> {
      if (event instanceof VFileContentChangeEvent changeEvent) {
        VirtualFile file = changeEvent.getFile();
        if (changeEvent.getOldLength() == 0) {
          // when something is written to the empty file, clear the file detection-from-content cache, because the file type can change from Unknown to e.g., Text
          file.putUserData(DETECTED_FROM_CONTENT_FILE_TYPE_KEY, null);
        }
      }
      ProgressManager.checkCanceled();
      VirtualFile file = event instanceof VFileCreateEvent /* avoid expensive find child here */
                         || isReparseEvent(event)
                         || event instanceof VFileDeleteEvent // no sense in re-detecting deleted file
                         ? null : event.getFile();
      VirtualFile filtered = file != null && wasAutoDetectedBefore(file) && isDetectable(file) ? file : null;
      if (toLog()) {
        log("F: after() VFS event " + event +
            "; filtered file: " + filtered + " (file: " + file + ") " +
            "; wasAutoDetectedBefore(file): " + (file == null ? null : wasAutoDetectedBefore(file)) +
            "; isDetectable(file): " + (file == null ? null : isDetectable(file)) +
            "; file.getLength(): " + (file == null ? null : file.isDirectory() ? "-" : file.getLength()) +
            "; file.isValid(): " + (file == null ? null : file.isValid()) +
            "; file.is(VFileProperty.SPECIAL): " + (file == null ? null : file.is(VFileProperty.SPECIAL)) +
            "; packedFlags.get(id): " + (file instanceof VirtualFileWithId ? readableFlags(packedFlags.get(((VirtualFileWithId)file).getId())) : null) +
            "; file.getFileSystem():" + (file == null ? null : file.getFileSystem()) + ")");
      }
      return filtered;
    });
    if (toLog()) {
      log("F: after() VFS events: " + events + "; files: " + files);
    }
    ProgressManager.checkCanceled();
    if (!files.isEmpty() && RE_DETECT_ASYNC) {
      if (toLog()) {
        log("F: after() queued to redetect: " + files);
      }

      for (VirtualFile file : files) {
        finishRedetectionIfEnqueued(file);
      }

      return new AsyncFileListener.ChangeApplier() {
        @Override
        public void beforeVfsChange() {
          myCanUseCachedDetectedFileType = false;
        }

        @Override
        public void afterVfsChange() {
          try {
            synchronized (filesToRedetect) {
              if (filesToRedetect.addAll(files)) {
                awakeReDetectExecutor();
              }
            }
          }
          finally {
            myCanUseCachedDetectedFileType = true;
          }
        }
      };
    }
    return null;
  }
@Nullable AsyncFileListener.ChangeApplier prepareChange(@NotNull List<? extends @NotNull VFileEvent> events)