/* * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.ide.projectView.impl; import com.intellij.ProjectTopics; import com.intellij.ide.CopyPasteUtil; import com.intellij.ide.bookmarks.Bookmark; import com.intellij.ide.bookmarks.BookmarksListener; import com.intellij.ide.projectView.BaseProjectTreeBuilder; import com.intellij.ide.projectView.ProjectViewNode; import com.intellij.ide.projectView.ProjectViewPsiTreeChangeListener; import com.intellij.ide.util.treeView.AbstractTreeStructure; import com.intellij.ide.util.treeView.AbstractTreeUpdater; import com.intellij.ide.util.treeView.NodeDescriptor; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.ide.CopyPasteManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ModuleRootEvent; import com.intellij.openapi.roots.ModuleRootListener; import com.intellij.openapi.vcs.FileStatusListener; import com.intellij.openapi.vcs.FileStatusManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.problems.WolfTheProblemSolver; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiManager; import com.intellij.util.Alarm; import com.intellij.util.SmartList; import com.intellij.util.messages.MessageBusConnection; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import java.util.Collection; import java.util.Comparator; import java.util.Enumeration; import java.util.Set; public class ProjectTreeBuilder extends BaseProjectTreeBuilder { public ProjectTreeBuilder(@NotNull Project project, @NotNull JTree tree, @NotNull DefaultTreeModel treeModel, @Nullable Comparator<NodeDescriptor> comparator, @NotNull ProjectAbstractTreeStructureBase treeStructure) { super(project, tree, treeModel, treeStructure, comparator); final MessageBusConnection connection = project.getMessageBus().connect(this); connection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() { @Override public void rootsChanged(ModuleRootEvent event) { queueUpdate(); } }); connection.subscribe(BookmarksListener.TOPIC, new MyBookmarksListener()); PsiManager.getInstance(project).addPsiTreeChangeListener(createPsiTreeChangeListener(project), this); FileStatusManager.getInstance(project).addFileStatusListener(new MyFileStatusListener(), this); CopyPasteManager.getInstance().addContentChangedListener(new CopyPasteUtil.DefaultCopyPasteListener(getUpdater()), this); WolfTheProblemSolver.getInstance(project).addProblemListener(new MyProblemListener(), this); setCanYieldUpdate(true); initRootNode(); } /** * Creates psi tree changes listener. This method will be invoked in constructor of ProjectTreeBuilder * thus builder object will be not completely initialized * @param project Project * @return Listener */ protected ProjectViewPsiTreeChangeListener createPsiTreeChangeListener(final Project project) { return new ProjectTreeBuilderPsiListener(project); } protected class ProjectTreeBuilderPsiListener extends ProjectViewPsiTreeChangeListener { public ProjectTreeBuilderPsiListener(final Project project) { super(project); } @Override protected DefaultMutableTreeNode getRootNode(){ return ProjectTreeBuilder.this.getRootNode(); } @Override protected AbstractTreeUpdater getUpdater() { return ProjectTreeBuilder.this.getUpdater(); } @Override protected boolean isFlattenPackages(){ AbstractTreeStructure structure = getTreeStructure(); return structure instanceof AbstractProjectTreeStructure && ((AbstractProjectTreeStructure)structure).isFlattenPackages(); } } private final class MyBookmarksListener implements BookmarksListener { @Override public void bookmarkAdded(@NotNull Bookmark b) { updateForFile(b.getFile()); } @Override public void bookmarkRemoved(@NotNull Bookmark b) { updateForFile(b.getFile()); } @Override public void bookmarkChanged(@NotNull Bookmark b) { updateForFile(b.getFile()); } @Override public void bookmarksOrderChanged() { //do nothing } private void updateForFile(@NotNull VirtualFile file) { PsiElement element = findPsi(file); if (element != null) { queueUpdateFrom(element, false); } } } private final class MyFileStatusListener implements FileStatusListener { @Override public void fileStatusesChanged() { queueUpdate(false); } @Override public void fileStatusChanged(@NotNull VirtualFile vFile) { queueUpdate(false); } } private PsiElement findPsi(@NotNull VirtualFile vFile) { if (!vFile.isValid()) return null; PsiManager psiManager = PsiManager.getInstance(myProject); return vFile.isDirectory() ? psiManager.findDirectory(vFile) : psiManager.findFile(vFile); } private class MyProblemListener extends WolfTheProblemSolver.ProblemListener { private final Alarm myUpdateProblemAlarm = new Alarm(); private final Collection<VirtualFile> myFilesToRefresh = new THashSet<>(); @Override public void problemsAppeared(@NotNull VirtualFile file) { queueUpdate(file); } @Override public void problemsDisappeared(@NotNull VirtualFile file) { queueUpdate(file); } private void queueUpdate(@NotNull VirtualFile fileToRefresh) { synchronized (myFilesToRefresh) { if (myFilesToRefresh.add(fileToRefresh)) { myUpdateProblemAlarm.cancelAllRequests(); myUpdateProblemAlarm.addRequest(() -> { if (!myProject.isOpen()) return; Set<VirtualFile> filesToRefresh; synchronized (myFilesToRefresh) { filesToRefresh = new THashSet<>(myFilesToRefresh); } final DefaultMutableTreeNode rootNode = getRootNode(); if (rootNode != null) { updateNodesContaining(filesToRefresh, rootNode); } synchronized (myFilesToRefresh) { myFilesToRefresh.removeAll(filesToRefresh); } }, 200, ModalityState.NON_MODAL); } } } } private void updateNodesContaining(@NotNull Collection<VirtualFile> filesToRefresh, @NotNull DefaultMutableTreeNode rootNode) { if (!(rootNode.getUserObject() instanceof ProjectViewNode)) return; ProjectViewNode node = (ProjectViewNode)rootNode.getUserObject(); Collection<VirtualFile> containingFiles = null; for (VirtualFile virtualFile : filesToRefresh) { if (!virtualFile.isValid()) { addSubtreeToUpdate(rootNode); // file must be deleted return; } if (node.contains(virtualFile)) { if (containingFiles == null) containingFiles = new SmartList<>(); containingFiles.add(virtualFile); } } if (containingFiles != null) { updateNode(rootNode); Enumeration children = rootNode.children(); while (children.hasMoreElements()) { DefaultMutableTreeNode child = (DefaultMutableTreeNode)children.nextElement(); updateNodesContaining(containingFiles, child); } } } }