void (@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); }