diff --git a/java/java-tests/testData/projectView/hideEmptyMiddlePackages/src/com/company/name/I.java b/java/java-tests/testData/projectView/hideEmptyMiddlePackages/src/com/company/name/I.java new file mode 100644 index 000000000000..703f2f7005c2 --- /dev/null +++ b/java/java-tests/testData/projectView/hideEmptyMiddlePackages/src/com/company/name/I.java @@ -0,0 +1,5 @@ +package com.company.name; + +interface I { + void m(); +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/projectView/ProjectViewUpdatingTest.java b/java/java-tests/testSrc/com/intellij/projectView/ProjectViewUpdatingTest.java index a402a9a2e9d1..2744682d1351 100644 --- a/java/java-tests/testSrc/com/intellij/projectView/ProjectViewUpdatingTest.java +++ b/java/java-tests/testSrc/com/intellij/projectView/ProjectViewUpdatingTest.java @@ -16,6 +16,7 @@ import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; import com.intellij.psi.*; import com.intellij.refactoring.rename.RenameProcessor; import com.intellij.testFramework.PlatformTestUtil; @@ -420,4 +421,70 @@ public class ProjectViewUpdatingTest extends BaseProjectViewTestCase { }; } + public void testHideEmptyMiddlePackages() { + myStructure.setProviders(new ClassesTreeStructureProvider(myProject)); + myStructure.setHideEmptyMiddlePackages(true); + myStructure.setShowMembers(true); + myStructure.setShowLibraryContents(false); + + PsiDirectory directory = getPackageDirectory("com/company"); + AbstractProjectViewPSIPane pane = myStructure.createPane(); + JTree tree = pane.getTree(); + + assertTreeEqual(tree, " +PsiDirectory: hideEmptyMiddlePackages\n"); + + TreeUtil.promiseExpandAll(tree); + + assertTreeEqual(tree, " -PsiDirectory: hideEmptyMiddlePackages\n" + + " -PsiDirectory: src\n" + + " -PsiDirectory: name\n" + // com.company.name + " -I\n" + + " m():void\n"); + + directory = createSubdirectory(directory, "a"); + TreeUtil.promiseExpandAll(tree); + + assertTreeEqual(tree, " -PsiDirectory: hideEmptyMiddlePackages\n" + + " -PsiDirectory: src\n" + + " -PsiDirectory: company\n" + // com.company + " PsiDirectory: a\n" + + " -PsiDirectory: name\n" + + " -I\n" + + " m():void\n"); + + directory = createSubdirectory(directory, "b"); + + assertTreeEqual(tree, " -PsiDirectory: hideEmptyMiddlePackages\n" + + " -PsiDirectory: src\n" + + " -PsiDirectory: company\n" + // com.company + " PsiDirectory: b\n" + // a.b + " -PsiDirectory: name\n" + + " -I\n" + + " m():void\n"); + + createSubdirectory(directory, "z"); + + assertTreeEqual(tree, " -PsiDirectory: hideEmptyMiddlePackages\n" + + " -PsiDirectory: src\n" + + " -PsiDirectory: company\n" + // com.company + " PsiDirectory: z\n" + // a.b.z + " -PsiDirectory: name\n" + + " -I\n" + + " m():void\n"); + } + + private void assertTreeEqual(@NotNull JTree tree, @NotNull String expected) { + PlatformTestUtil.waitWhileBusy(tree); + PlatformTestUtil.assertTreeEqual(tree, "-Project\n" + expected + getRootFiles()); + } + + private static PsiDirectory createSubdirectory(@NotNull PsiDirectory directory, @NotNull String name) { + return computeAndWaitFor(100, directory.getProject(), () -> directory.createSubdirectory(name)); + } + + private static T computeAndWaitFor(int millis, @NotNull Project project, @NotNull Computable computable) { + T result = WriteCommandAction.runWriteCommandAction(project, computable); + PlatformTestUtil.waitFor(millis); + return result; + } } diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/ProjectViewDirectoryHelper.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/ProjectViewDirectoryHelper.java index 4197e15f0feb..a4d96c597805 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/ProjectViewDirectoryHelper.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/ProjectViewDirectoryHelper.java @@ -40,6 +40,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.pointers.VirtualFilePointer; import com.intellij.psi.*; import com.intellij.psi.search.PsiElementProcessor; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.FontUtil; import com.intellij.util.containers.ContainerUtil; @@ -150,6 +151,57 @@ public class ProjectViewDirectoryHelper { return false; } + public boolean canRepresent(Object element, PsiDirectory directory, Object owner, ViewSettings settings) { + if (directory != null) { + if (canRepresent(element, directory)) return true; + if (settings == null) return false; // unexpected + if (!settings.isFlattenPackages() && settings.isHideEmptyMiddlePackages()) { + if (element instanceof PsiDirectory) { + for (PsiDirectory dir : collectParents(directory, owner)) { + if (Comparing.equal(element, dir)) return true; + } + } + else if (element instanceof VirtualFile) { + for (PsiDirectory dir : collectParents(directory, owner)) { + if (Comparing.equal(element, dir.getVirtualFile())) return true; + } + } + } + } + return false; + } + + public boolean isValidDirectory(PsiDirectory directory, Object owner, ViewSettings settings, PsiFileSystemItemFilter filter) { + if (directory == null || !directory.isValid()) return false; + if (settings == null) return true; // unexpected + if (!settings.isFlattenPackages() && settings.isHideEmptyMiddlePackages()) { + PsiDirectory parent = directory.getParent(); + if (parent == null || skipDirectory(parent)) return true; + if (isEmptyMiddleDirectory(directory, true, filter)) return false; + for (PsiDirectory dir : collectParents(directory, owner)) { + if (!dir.isValid()) return false; + parent = dir.getParent(); + if (parent == null || skipDirectory(parent)) return false; + if (!isEmptyMiddleDirectory(dir, true, filter)) return false; + } + } + return true; + } + + @NotNull + private static List collectParents(PsiDirectory directory, Object owner) { + if (directory != null && owner instanceof PsiDirectory && PsiTreeUtil.isAncestor((PsiDirectory)owner, directory, true)) { + List list = ContainerUtil.newSmartList(); + while (true) { + directory = directory.getParent(); + if (directory == null) break; // unexpected + if (directory.equals(owner)) return list; + list.add(directory); + } + } + return Collections.emptyList(); + } + @NotNull public Collection getDirectoryChildren(final PsiDirectory psiDirectory, final ViewSettings settings, diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/PsiDirectoryNode.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/PsiDirectoryNode.java index cc4b8176dd65..dba0b0499c39 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/PsiDirectoryNode.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/PsiDirectoryNode.java @@ -237,9 +237,15 @@ public class PsiDirectoryNode extends BasePsiNode implements Navig @Override public boolean canRepresent(final Object element) { if (super.canRepresent(element)) return true; - PsiDirectory directory = getValue(); - if (directory == null) return false; - return ProjectViewDirectoryHelper.getInstance(getProject()).canRepresent(element, directory); + return ProjectViewDirectoryHelper.getInstance(getProject()) + .canRepresent(element, getValue(), getParentValue(), getSettings()); + } + + @Override + public boolean isValid() { + if (!super.isValid()) return false; + return ProjectViewDirectoryHelper.getInstance(getProject()) + .isValidDirectory(getValue(), getParentValue(), getSettings(), getFilter()); } @Override