/* * Copyright 2000-2011 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.navigationToolbar; import com.intellij.ide.ui.UISettings; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.LangDataKeys; import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.impl.LaterInvocator; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ModuleFileIndex; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.PsiFileSystemItemProcessor; import com.intellij.psi.util.PsiUtilBase; import com.intellij.util.PathUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import consulo.annotations.RequiredReadAction; import java.util.*; /** * @author Konstantin Bulenkov * @author Anna Kozlova */ public class NavBarModel { private List<Object> myModel = Collections.emptyList(); private int mySelectedIndex; private final Project myProject; private final NavBarModelListener myNotificator; private boolean myChanged = true; private boolean updated = false; private boolean isFixedComponent = false; public NavBarModel(final Project project) { myProject = project; myNotificator = project.getMessageBus().syncPublisher(NavBarModelListener.NAV_BAR); } public int getSelectedIndex() { return mySelectedIndex; } @Nullable public Object getSelectedValue() { return getElement(mySelectedIndex); } @Nullable public Object getElement(int index) { if (index != -1 && index < myModel.size()) { return myModel.get(index); } return null; } public int size() { return myModel.size(); } public boolean isEmpty() { return myModel.isEmpty(); } public int getIndexByModel(int index) { if (index < 0) return myModel.size() + index; if (index >= myModel.size() && myModel.size() > 0) return index % myModel.size(); return index; } protected void updateModel(DataContext dataContext) { if (LaterInvocator.isInModalContext() || (updated && !isFixedComponent)) return; if (PlatformDataKeys.CONTEXT_COMPONENT.getData(dataContext) instanceof NavBarPanel) return; PsiElement psiElement = LangDataKeys.PSI_FILE.getData(dataContext); if (psiElement == null) { psiElement = LangDataKeys.PSI_ELEMENT.getData(dataContext); } psiElement = normalize(psiElement); if (!myModel.isEmpty() && myModel.get(myModel.size() - 1).equals(psiElement) && !myChanged) return; if (psiElement != null && psiElement.isValid()) { updateModel(psiElement); } else { if (UISettings.getInstance().SHOW_NAVIGATION_BAR && !myModel.isEmpty()) return; Object moduleOrProject = LangDataKeys.MODULE.getData(dataContext); if (moduleOrProject == null) { moduleOrProject = CommonDataKeys.PROJECT.getData(dataContext); } if (moduleOrProject != null) { setModel(Collections.singletonList(moduleOrProject)); } } setChanged(false); updated = true; } protected void updateModel(final PsiElement psiElement) { final Set<VirtualFile> roots = new HashSet<VirtualFile>(); final ProjectRootManager projectRootManager = ProjectRootManager.getInstance(myProject); final ProjectFileIndex projectFileIndex = projectRootManager.getFileIndex(); for (VirtualFile root : projectRootManager.getContentRoots()) { VirtualFile parent = root.getParent(); if (parent == null || !projectFileIndex.isInContent(parent)) { roots.add(root); } } for (final NavBarModelExtension modelExtension : Extensions.getExtensions(NavBarModelExtension.EP_NAME)) { for (VirtualFile root : modelExtension.additionalRoots(psiElement.getProject())) { VirtualFile parent = root.getParent(); if (parent == null || !projectFileIndex.isInContent(parent)) { roots.add(root); } } } final List<Object> updatedModel = new ArrayList<Object>(); ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { traverseToRoot(psiElement, roots, updatedModel); } }); setModel(updatedModel); } void revalidate() { final List<Object> objects = new ArrayList<Object>(); boolean update = false; for (Object o : myModel) { if (isValid(o)) { objects.add(o); } else { update = true; break; } } if (update) { setModel(objects); } } protected void setModel(List<Object> model) { if (!model.equals(myModel)) { myModel = model; myNotificator.modelChanged(); mySelectedIndex = myModel.size() - 1; myNotificator.selectionChanged(); } } public void updateModel(final Object object) { if (object instanceof PsiElement) { updateModel((PsiElement)object); } else if (object instanceof Module) { List<Object> l = new ArrayList<Object>(); l.add(myProject); l.add(object); setModel(l); } } @RequiredReadAction private void traverseToRoot(@NotNull PsiElement psiElement, Set<VirtualFile> roots, List<Object> model) { if (!psiElement.isValid()) return; final PsiFile containingFile = psiElement.getContainingFile(); if (containingFile != null && (containingFile.getVirtualFile() == null || !containingFile.getViewProvider().isPhysical())) return; //non phisycal elements psiElement = getOriginalElement(psiElement); PsiElement resultElement = psiElement; for (final NavBarModelExtension modelExtension : Extensions.getExtensions(NavBarModelExtension.EP_NAME)) { resultElement = modelExtension.adjustElement(resultElement); } boolean foundByExtension = false; for (final NavBarModelExtension modelExtension : Extensions.getExtensions(NavBarModelExtension.EP_NAME)) { final PsiElement parent = modelExtension.getParent(resultElement); if (parent != null) { if (parent != resultElement) { // HACK is to return same element to stop traversing traverseToRoot(parent, roots, model); } foundByExtension = true; break; } } if (!foundByExtension) { if (containingFile != null) { final PsiDirectory containingDirectory = containingFile.getContainingDirectory(); if (containingDirectory != null) { traverseToRoot(containingDirectory, roots, model); } } else if (psiElement instanceof PsiDirectory) { final PsiDirectory psiDirectory = (PsiDirectory)psiElement; if (!roots.contains(psiDirectory.getVirtualFile())) { PsiDirectory parentDirectory = psiDirectory.getParentDirectory(); if (parentDirectory == null) { VirtualFile jar = PathUtil.getLocalFile(psiDirectory.getVirtualFile()); if (ProjectRootManager.getInstance(myProject).getFileIndex().isInContent(jar)) { parentDirectory = PsiManager.getInstance(myProject).findDirectory(jar.getParent()); } } if (parentDirectory != null) { traverseToRoot(parentDirectory, roots, model); } } } else if (psiElement instanceof PsiFileSystemItem) { final VirtualFile virtualFile = ((PsiFileSystemItem)psiElement).getVirtualFile(); if (virtualFile == null) return; final PsiManager psiManager = PsiManager.getInstance(myProject); if (virtualFile.isDirectory()) { resultElement = psiManager.findDirectory(virtualFile); } else { resultElement = psiManager.findFile(virtualFile); } if (resultElement == null) return; final VirtualFile parentVFile = virtualFile.getParent(); if (parentVFile != null && !roots.contains(parentVFile)) { final PsiDirectory parentDirectory = psiManager.findDirectory(parentVFile); if (parentDirectory != null) { traverseToRoot(parentDirectory, roots, model); } } } } model.add(resultElement); } private static PsiElement getOriginalElement(PsiElement psiElement) { final PsiElement originalElement = psiElement.getOriginalElement(); return !(psiElement instanceof PsiCompiledElement) && originalElement instanceof PsiCompiledElement ? psiElement : originalElement; } protected boolean hasChildren(Object object) { if (!isValid(object)) return false; return !getChildren(object).isEmpty(); } //to avoid the following situation: element was taken from NavBarPanel via data context and all left children // were truncated by traverseToRoot public void setChanged(boolean changed) { myChanged = changed; } @SuppressWarnings({"SimplifiableIfStatement"}) static boolean isValid(final Object object) { if (object instanceof Project) { return !((Project)object).isDisposed(); } if (object instanceof Module) { return !((Module)object).isDisposed(); } if (object instanceof PsiElement) { return ApplicationManager.getApplication().runReadAction( new Computable<Boolean>() { @Override public Boolean compute() { return ((PsiElement)object).isValid(); } } ).booleanValue(); } return object != null; } public static void getDirectoryChildren(final PsiDirectory psiDirectory, final Object rootElement, final List<Object> result) { final ModuleFileIndex moduleFileIndex = rootElement instanceof Module ? ModuleRootManager.getInstance((Module)rootElement).getFileIndex() : null; final PsiElement[] children = psiDirectory.getChildren(); for (PsiElement child : children) { if (child != null && child.isValid()) { if (moduleFileIndex != null) { final VirtualFile virtualFile = PsiUtilBase.getVirtualFile(child); if (virtualFile != null && !moduleFileIndex.isInContent(virtualFile)) continue; } result.add(normalize(child)); } } } @Nullable private static PsiElement normalize(PsiElement child) { if (child == null) return null; for (NavBarModelExtension modelExtension : Extensions.getExtensions(NavBarModelExtension.EP_NAME)) { child = modelExtension.adjustElement(child); if (child == null ) return null; } return child; } protected List<Object> getChildren(final Object object) { if (!isValid(object)) return new ArrayList<Object>(); final List<Object> result = new ArrayList<Object>(); final Object rootElement = size() > 1 ? getElement(1) : null; if (!(object instanceof Project) && rootElement instanceof Module && ((Module)rootElement).isDisposed()) return result; final PsiManager psiManager = PsiManager.getInstance(myProject); if (object instanceof Project) { ContainerUtil.addAll(result, ApplicationManager.getApplication().runReadAction( new Computable<Module[]>() { @Override public Module[] compute() { return ModuleManager.getInstance((Project)object).getModules(); } } )); } else if (object instanceof Module) { Module module = (Module)object; if (!module.isDisposed()) { ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module); VirtualFile[] roots = moduleRootManager.getContentRoots(); for (final VirtualFile root : roots) { final PsiDirectory psiDirectory = ApplicationManager.getApplication().runReadAction( new Computable<PsiDirectory>() { @Override public PsiDirectory compute() { return psiManager.findDirectory(root); } } ); if (psiDirectory != null) { result.add(psiDirectory); } } } } else if (object instanceof PsiDirectoryContainer) { final PsiDirectoryContainer psiPackage = (PsiDirectoryContainer)object; final PsiDirectory[] psiDirectories = ApplicationManager.getApplication().runReadAction( new Computable<PsiDirectory[]>() { @Override public PsiDirectory[] compute() { return rootElement instanceof Module ? psiPackage.getDirectories(GlobalSearchScope.moduleScope((Module)rootElement)) : psiPackage.getDirectories(); } } ); for (final PsiDirectory psiDirectory : psiDirectories) { ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run(){ getDirectoryChildren(psiDirectory, rootElement, result); } }); } } else if (object instanceof PsiDirectory) { ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run(){ getDirectoryChildren((PsiDirectory)object, rootElement, result); } }); } else if (object instanceof PsiFileSystemItem) { ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { ((PsiFileSystemItem)object).processChildren(new PsiFileSystemItemProcessor() { @Override public boolean acceptItem(String name, boolean isDirectory) { return true; } @Override public boolean execute(@NotNull PsiFileSystemItem element) { result.add(element); return true; } }); } }); } Collections.sort(result, new SiblingsComparator()); return result; } public Object get(final int index) { return myModel.get(index); } public int indexOf(Object value) { return myModel.indexOf(value); } public void setSelectedIndex(final int selectedIndex) { if (mySelectedIndex != selectedIndex) { mySelectedIndex = selectedIndex; myNotificator.selectionChanged(); } } public void setFixedComponent(boolean fixedComponent) { isFixedComponent = fixedComponent; } private static final class SiblingsComparator implements Comparator<Object> { @Override public int compare(final Object o1, final Object o2) { final Pair<Integer, String> w1 = getWeightedName(o1); final Pair<Integer, String> w2 = getWeightedName(o2); if (w1 == null) return w2 == null ? 0 : -1; if (w2 == null) return 1; if (!w1.first.equals(w2.first)) { return -w1.first.intValue() + w2.first.intValue(); } return w1.second.compareToIgnoreCase(w2.second); } @Nullable private static Pair<Integer, String> getWeightedName(Object object) { if (object instanceof Module) { return Pair.create(5, ((Module)object).getName()); } if (object instanceof PsiDirectoryContainer) { return Pair.create(4, ((PsiDirectoryContainer)object).getName()); } else if (object instanceof PsiDirectory) { return Pair.create(4, ((PsiDirectory)object).getName()); } if (object instanceof PsiFile) { return Pair.create(2, ((PsiFile)object).getName()); } if (object instanceof PsiNamedElement) { return Pair.create(3, ((PsiNamedElement)object).getName()); } return null; } } }