IDEA-189793 refresh files in the project view on VFS change

This commit is contained in:
Sergey Malenkov
2018-04-11 18:09:15 +03:00
parent c2bcd2ad10
commit 30c8a1ebe8
3 changed files with 161 additions and 0 deletions
@@ -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<VirtualFile> 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) {
@@ -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<Module, VirtualFile> consumer;
private final Project project;
public ProjectFileChangeListener(@NotNull Project project, @NotNull BiConsumer<Module, VirtualFile> consumer) {
this.consumer = consumer;
this.project = project;
}
@Override
public void after(@NotNull List<? extends VFileEvent> 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);
}
}
@@ -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<T> {
private final AtomicReference<List<T>> reference = new AtomicReference<>();
private final AtomicLong counter = new AtomicLong();
private final BiPredicate<T, T> predicate;
private TreeCollector(@NotNull BiPredicate<T, T> predicate) {
this.predicate = predicate;
}
@NotNull
public List<T> get() {
synchronized (reference) {
List<T> list = reference.getAndSet(null);
return list != null ? list : Collections.emptyList();
}
}
public boolean add(@NotNull T object) {
synchronized (reference) {
List<T> list = reference.get();
if (list == null) {
reference.set(new SmartList<>(object));
}
else {
for (T parent : list) {
if (predicate.test(parent, object)) {
return false;
}
}
Iterator<T> 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<List<T>> 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<VirtualFile> createFileLeafsCollector() {
return new TreeCollector<>((child, parent) -> isAncestor(parent, child, false));
}
public static TreeCollector<VirtualFile> createFileRootsCollector() {
return new TreeCollector<>((parent, child) -> isAncestor(parent, child, false));
}
public static TreeCollector<TreePath> createPathLeafsCollector() {
return new TreeCollector<>((child, parent) -> parent.isDescendant(child));
}
public static TreeCollector<TreePath> createPathRootsCollector() {
return new TreeCollector<>((parent, child) -> parent.isDescendant(child));
}
}