From 30c8a1ebe813da00c2fdb52900426970ed471a27 Mon Sep 17 00:00:00 2001 From: Sergey Malenkov Date: Wed, 11 Apr 2018 17:55:28 +0300 Subject: [PATCH] IDEA-189793 refresh files in the project view on VFS change --- .../impl/AsyncProjectViewSupport.java | 11 +++ .../ui/tree/ProjectFileChangeListener.java | 64 ++++++++++++++ .../com/intellij/ui/tree/TreeCollector.java | 86 +++++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 platform/platform-impl/src/com/intellij/ui/tree/ProjectFileChangeListener.java create mode 100644 platform/platform-impl/src/com/intellij/ui/tree/TreeCollector.java diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/AsyncProjectViewSupport.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/AsyncProjectViewSupport.java index b4943a714fff..bbd1b6b2ed2d 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/AsyncProjectViewSupport.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/AsyncProjectViewSupport.java @@ -27,7 +27,9 @@ import com.intellij.psi.PsiManager; import com.intellij.ui.tree.AsyncTreeModel; import com.intellij.ui.tree.RestoreSelectionListener; import com.intellij.ui.tree.StructureTreeModel; +import com.intellij.ui.tree.TreeCollector; import com.intellij.ui.tree.TreeVisitor; +import com.intellij.ui.tree.ProjectFileChangeListener; import com.intellij.util.SmartList; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.ui.tree.TreeUtil; @@ -41,9 +43,12 @@ import java.util.Comparator; import java.util.List; import static com.intellij.ide.util.treeView.TreeState.expand; +import static com.intellij.openapi.vfs.VirtualFileManager.VFS_CHANGES; class AsyncProjectViewSupport { private static final Logger LOG = Logger.getInstance(AsyncProjectViewSupport.class); + private final TreeCollector myFileRoots = TreeCollector.createFileRootsCollector(); + private final ProjectFileChangeListener myChangeListener; private final StructureTreeModel myStructureTreeModel; private final AsyncTreeModel myAsyncTreeModel; @@ -57,8 +62,14 @@ class AsyncProjectViewSupport { myStructureTreeModel.setComparator(comparator); myAsyncTreeModel = new AsyncTreeModel(myStructureTreeModel, true, parent); myAsyncTreeModel.setRootImmediately(myStructureTreeModel.getRootImmediately()); + myChangeListener = new ProjectFileChangeListener(project, (module, file) -> { + if (myFileRoots.add(file)) { + myFileRoots.processLater(myStructureTreeModel.getInvoker(), roots -> roots.forEach(root -> updateByFile(root, true))); + } + }); setModel(tree, myAsyncTreeModel); MessageBusConnection connection = project.getMessageBus().connect(parent); + connection.subscribe(VFS_CHANGES, myChangeListener); connection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() { @Override public void rootsChanged(ModuleRootEvent event) { diff --git a/platform/platform-impl/src/com/intellij/ui/tree/ProjectFileChangeListener.java b/platform/platform-impl/src/com/intellij/ui/tree/ProjectFileChangeListener.java new file mode 100644 index 000000000000..a5858ff0fe00 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/tree/ProjectFileChangeListener.java @@ -0,0 +1,64 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.ui.tree; + +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.newvfs.BulkFileListener; +import com.intellij.openapi.vfs.newvfs.events.VFileCopyEvent; +import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent; +import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent; +import com.intellij.openapi.vfs.newvfs.events.VFileEvent; +import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.function.BiConsumer; + +public final class ProjectFileChangeListener implements BulkFileListener { + private final BiConsumer consumer; + private final Project project; + + public ProjectFileChangeListener(@NotNull Project project, @NotNull BiConsumer consumer) { + this.consumer = consumer; + this.project = project; + } + + @Override + public void after(@NotNull List events) { + for (VFileEvent event : events) { + if (event instanceof VFileCreateEvent) { + VFileCreateEvent create = (VFileCreateEvent)event; + invalidate(create.getParent()); + } + else if (event instanceof VFileCopyEvent) { + VFileCopyEvent copy = (VFileCopyEvent)event; + invalidate(copy.getNewParent()); + } + else if (event instanceof VFileMoveEvent) { + VFileMoveEvent move = (VFileMoveEvent)event; + VirtualFile parent = move.getOldParent(); + if (parent != null) invalidate(parent); + invalidate(move.getNewParent()); + invalidate(move.getFile()); + } + else { + VirtualFile file = event.getFile(); + if (file != null) { + if (event instanceof VFileDeleteEvent) { + VirtualFile parent = file.getParent(); + if (parent != null) invalidate(parent); + } + invalidate(file); + } + } + } + } + + public void invalidate(@NotNull VirtualFile file) { + ProjectFileIndex index = project.isDisposed() ? null : ProjectFileIndex.getInstance(project); + Module module = index == null ? null : index.getModuleForFile(file); + if (module != null) consumer.accept(module, file); + } +} diff --git a/platform/platform-impl/src/com/intellij/ui/tree/TreeCollector.java b/platform/platform-impl/src/com/intellij/ui/tree/TreeCollector.java new file mode 100644 index 000000000000..902f7955d23d --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/tree/TreeCollector.java @@ -0,0 +1,86 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.ui.tree; + +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.SmartList; +import com.intellij.util.concurrency.Invoker; +import org.jetbrains.annotations.NotNull; + +import javax.swing.tree.TreePath; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiPredicate; +import java.util.function.Consumer; + +import static com.intellij.openapi.vfs.VfsUtilCore.isAncestor; + +public final class TreeCollector { + private final AtomicReference> reference = new AtomicReference<>(); + private final AtomicLong counter = new AtomicLong(); + private final BiPredicate predicate; + + private TreeCollector(@NotNull BiPredicate predicate) { + this.predicate = predicate; + } + + @NotNull + public List get() { + synchronized (reference) { + List list = reference.getAndSet(null); + return list != null ? list : Collections.emptyList(); + } + } + + public boolean add(@NotNull T object) { + synchronized (reference) { + List list = reference.get(); + if (list == null) { + reference.set(new SmartList<>(object)); + } + else { + for (T parent : list) { + if (predicate.test(parent, object)) { + return false; + } + } + Iterator iterator = list.iterator(); + while (iterator.hasNext()) { + if (predicate.test(object, iterator.next())) { + iterator.remove(); + } + } + list.add(object); + } + return true; + } + } + + public void processLater(@NotNull Invoker invoker, @NotNull Consumer> consumer) { + long count = counter.incrementAndGet(); + invoker.invokeLater(() -> { + // is this request still actual after 10 ms? + if (count == counter.get()) consumer.accept(get()); + }, 10); + } + + + public static TreeCollector createFileLeafsCollector() { + return new TreeCollector<>((child, parent) -> isAncestor(parent, child, false)); + } + + public static TreeCollector createFileRootsCollector() { + return new TreeCollector<>((parent, child) -> isAncestor(parent, child, false)); + } + + + public static TreeCollector createPathLeafsCollector() { + return new TreeCollector<>((child, parent) -> parent.isDescendant(child)); + } + + public static TreeCollector createPathRootsCollector() { + return new TreeCollector<>((parent, child) -> parent.isDescendant(child)); + } +}