private static void registerUpdaters(@NotNull Project project, @NotNull Disposable disposable, @NotNull Runnable onUpdate) {
    VirtualFileManager.getInstance().addAsyncFileListener(events -> {
      ScratchFileService scratchFileService = ScratchFileService.getInstance();
      boolean update = JBIterable.from(events).find(e -> {
        ProgressManager.checkCanceled();
        VirtualFile parent = getNewParent(e);
        if (parent == null) return false;
        if (ScratchUtil.isScratch(parent)) return true;
        if (!isDirectory(e)) return false;
        for (RootType rootType : RootType.getAllRootTypes()) {
          if (scratchFileService.getRootPath(rootType).startsWith(parent.getPath())) return true;
        }
        return false;
      }) != null;

      return !update ? null : new AsyncFileListener.ChangeApplier() {
        @Override
        public void afterVfsChange() {
          onUpdate.run();
        }
      };
    }, disposable);
    ConcurrentMap<RootType, Disposable> disposables = ConcurrentFactoryMap.createMap(o -> Disposer.newDisposable(o.getDisplayName()));
    for (RootType rootType : RootType.getAllRootTypes()) {
      registerRootTypeUpdater(project, rootType, onUpdate, disposable, disposables);
    }
    RootType.ROOT_EP.addExtensionPointListener(new ExtensionPointListener<>() {
      @Override
      public void extensionAdded(@NotNull RootType rootType, @NotNull PluginDescriptor pluginDescriptor) {
        registerRootTypeUpdater(project, rootType, onUpdate, disposable, disposables);
      }

      @Override
      public void extensionRemoved(@NotNull RootType rootType, @NotNull PluginDescriptor pluginDescriptor) {
        Disposable rootDisposable = disposables.remove(rootType);
        if (rootDisposable != null) Disposer.dispose(rootDisposable);
      }
    }, project);
    RootType.ROOT_EP.addChangeListener(onUpdate, project);
  }
private static void registerUpdaters(@NotNull Project project, @NotNull Disposable disposable, @NotNull Runnable onUpdate)