Java: make Sort by Kind visible in Structure View (IDEA-154161)

GitOrigin-RevId: 97c1a3a6a9b59b787930947b09508fa56de997c4
This commit is contained in:
Bas Leijdekkers
2025-10-11 23:30:37 +00:00
committed by intellij-monorepo-bot
parent 8820210fa6
commit 2c8e2f3b05
11 changed files with 185 additions and 170 deletions
@@ -1,10 +1,11 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.structureView.impl.java;
import com.intellij.ide.structureView.StructureViewTreeElement;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.light.LightElement;
import com.siyeh.ig.psiutils.PsiElementOrderComparator;
import org.jetbrains.annotations.NotNull;
import java.util.*;
@@ -26,22 +27,18 @@ public class JavaClassTreeElement extends JavaClassTreeElementBase<PsiClass> {
static Collection<StructureViewTreeElement> getClassChildren(PsiClass aClass) {
if (aClass == null) return Collections.emptyList();
LinkedHashSet<PsiElement> members = getOwnChildren(aClass);
List<PsiElement> members = new ArrayList<>(getOwnChildren(aClass));
members.sort(PsiElementOrderComparator.getInstance());
List<StructureViewTreeElement> children = new ArrayList<>(members.size());
for (PsiElement child : members) {
if (!child.isValid()) continue;
if (child instanceof PsiClass) {
children.add(new JavaClassTreeElement((PsiClass)child, false));
}
else if (child instanceof PsiField) {
children.add(new PsiFieldTreeElement((PsiField)child, false));
}
else if (child instanceof PsiMethod) {
children.add(new PsiMethodTreeElement((PsiMethod)child, false));
}
else if (child instanceof PsiClassInitializer) {
children.add(new ClassInitializerTreeElement((PsiClassInitializer)child));
switch (child) {
case PsiClass c -> children.add(new JavaClassTreeElement(c, false));
case PsiField f -> children.add(new PsiFieldTreeElement(f, false));
case PsiMethod m -> children.add(new PsiMethodTreeElement(m, false));
case PsiClassInitializer i -> children.add(new ClassInitializerTreeElement(i));
default -> {}
}
}
PsiRecordHeader header = aClass.getRecordHeader();
@@ -53,8 +50,8 @@ public class JavaClassTreeElement extends JavaClassTreeElementBase<PsiClass> {
return children;
}
static @NotNull LinkedHashSet<PsiElement> getOwnChildren(@NotNull PsiClass aClass) {
LinkedHashSet<PsiElement> members = new LinkedHashSet<>();
static @NotNull Set<PsiElement> getOwnChildren(@NotNull PsiClass aClass) {
HashSet<PsiElement> members = new HashSet<>();
addPhysicalElements(aClass.getFields(), members, aClass);
addPhysicalElements(aClass.getMethods(), members, aClass);
addPhysicalElements(aClass.getInnerClasses(), members, aClass);
@@ -66,7 +63,7 @@ public class JavaClassTreeElement extends JavaClassTreeElementBase<PsiClass> {
for (PsiMember element : elements) {
PsiElement mirror = PsiImplUtil.handleMirror(element);
if (mirror instanceof LightElement) continue;
if (mirror instanceof PsiMember && aClass.equals(((PsiMember)mirror).getContainingClass())) {
if (mirror instanceof PsiMember member && aClass.equals(member.getContainingClass())) {
to.add(mirror);
}
}
@@ -1,15 +1,19 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.structureView.impl.java;
import com.intellij.icons.AllIcons;
import com.intellij.ide.structureView.StructureViewBundle;
import com.intellij.ide.util.treeView.smartTree.ActionPresentation;
import com.intellij.ide.util.treeView.smartTree.ActionPresentationData;
import com.intellij.ide.util.treeView.smartTree.Sorter;
import com.intellij.ide.util.treeView.smartTree.TreeActionWithDefaultState;
import com.intellij.psi.PsiMethod;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.Comparator;
public class KindSorter implements Sorter {
public class KindSorter implements Sorter, TreeActionWithDefaultState {
public static final Sorter INSTANCE = new KindSorter(false);
public static final Sorter POPUP_INSTANCE = new KindSorter(true);
@@ -61,12 +65,18 @@ public class KindSorter implements Sorter {
@Override
public boolean isVisible() {
return false;
return true;
}
@Override
public boolean isEnabledByDefault() {
return true;
}
@Override
public @NotNull ActionPresentation getPresentation() {
throw new IllegalStateException();
return new ActionPresentationData(
StructureViewBundle.message("action.structureview.sort.by.kind"), null, AllIcons.ObjectBrowser.SortByType);
}
@Override
@@ -458,6 +458,7 @@ public class JavaStructureViewTest extends LightJavaStructureViewTestCaseBase {
public void testVisibilitySorterComparingEqualKnown() {
init();
myFixture.testStructureView(svc -> {
svc.setActionActive(KindSorter.ID, true);
svc.setActionActive(InheritedMembersNodeProvider.ID, true);
PlatformTestUtil.assertTreeEqual(svc.getTree(),
"""
@@ -522,6 +523,7 @@ public class JavaStructureViewTest extends LightJavaStructureViewTestCaseBase {
public void testVisibilitySorterCompareInheritanceGroups() {
init();
myFixture.testStructureView(svc -> {
svc.setActionActive(KindSorter.ID, true);
svc.setActionActive(PropertiesGrouper.ID, true);
svc.setActionActive(SuperTypesGrouper.ID, true);
svc.setActionActive(InheritedMembersNodeProvider.ID, true);
@@ -560,6 +562,7 @@ public class JavaStructureViewTest extends LightJavaStructureViewTestCaseBase {
myFixture.configureByText("Derived.java", DERIVED);
myFixture.testStructureView(svc -> {
svc.setActionActive(KindSorter.ID, true);
svc.setActionActive(FieldsFilter.ID, true);
svc.setActionActive(Sorter.getAlphaSorterId(), true);
@@ -587,6 +590,7 @@ public class JavaStructureViewTest extends LightJavaStructureViewTestCaseBase {
init();
myFixture.testStructureView(svc -> {
svc.setActionActive(KindSorter.ID, true);
svc.setActionActive(InheritedMembersNodeProvider.ID, true);
svc.setActionActive(PropertiesGrouper.ID, true);
svc.setActionActive(SuperTypesGrouper.ID, true);
@@ -613,6 +617,7 @@ public class JavaStructureViewTest extends LightJavaStructureViewTestCaseBase {
init();
myFixture.testStructureView(svc -> {
svc.setActionActive(KindSorter.ID, true);
svc.setActionActive(InheritedMembersNodeProvider.ID, true);
svc.setActionActive(PropertiesGrouper.ID, false);
svc.setActionActive(SuperTypesGrouper.ID, true);
@@ -671,6 +676,7 @@ public class JavaStructureViewTest extends LightJavaStructureViewTestCaseBase {
init();
myFixture.testStructureView(svc -> {
svc.setActionActive(KindSorter.ID, true);
svc.setActionActive(InheritedMembersNodeProvider.ID, true);
svc.setActionActive(PropertiesGrouper.ID, false);
svc.setActionActive(SuperTypesGrouper.ID, true);
@@ -722,6 +728,7 @@ public class JavaStructureViewTest extends LightJavaStructureViewTestCaseBase {
myFixture.configureByText("a.java", "enum F { A,B,C}");
myFixture.testStructureView(svc -> {
svc.setActionActive(KindSorter.ID, true);
svc.setActionActive(InheritedMembersNodeProvider.ID, true);
svc.setActionActive(PropertiesGrouper.ID, false);
svc.setActionActive(SuperTypesGrouper.ID, true);
@@ -777,6 +784,7 @@ public class JavaStructureViewTest extends LightJavaStructureViewTestCaseBase {
boolean showInherited) {
myFixture.configureByText("Test.java", classText);
myFixture.testStructureView(svc -> {
svc.setActionActive(KindSorter.ID, true);
svc.setActionActive(SuperTypesGrouper.ID, showInterfaces);
svc.setActionActive(PropertiesGrouper.ID, showProperties);
svc.setActionActive(InheritedMembersNodeProvider.ID, showInherited);
@@ -1,4 +1,4 @@
// 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.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.structureView;
import com.intellij.JavaTestUtil;
@@ -24,176 +24,164 @@ public class JavaTreeStructureTest extends TestSourceBasedTestCase {
}
public void testJavaClassStructure() {
doTest(new CheckAction() {
@Override
public void testClassStructure(StructureViewComponent svc) {
svc.setActionActive(InheritedMembersNodeProvider.ID, true);
doTest(svc -> {
svc.setActionActive(InheritedMembersNodeProvider.ID, true);
PlatformTestUtil.assertTreeEqual(
svc.getTree(),
"""
-Class1.java
-Class1
getValue(): int
getClass(): Class<?>
hashCode(): int
equals(Object): boolean
clone(): Object
toString(): String
notify(): void
notifyAll(): void
wait(long): void
wait(long, int): void
wait(): void
finalize(): void
myField1: boolean
myField2: boolean
""");
PlatformTestUtil.assertTreeEqual(
svc.getTree(),
"""
-Class1.java
-Class1
myField1: boolean
myField2: boolean
getValue(): int
getClass(): Class<?>
hashCode(): int
equals(Object): boolean
clone(): Object
toString(): String
notify(): void
notifyAll(): void
wait(long): void
wait(long, int): void
wait(): void
finalize(): void
""");
svc.setActionActive(InheritedMembersNodeProvider.ID, false);
svc.setActionActive(InheritedMembersNodeProvider.ID, true);
svc.setActionActive(InheritedMembersNodeProvider.ID, false);
svc.setActionActive(InheritedMembersNodeProvider.ID, false);
svc.setActionActive(InheritedMembersNodeProvider.ID, true);
svc.setActionActive(InheritedMembersNodeProvider.ID, false);
PlatformTestUtil.assertTreeEqual(
svc.getTree(),
"""
-Class1.java
-Class1
getValue(): int
myField1: boolean
myField2: boolean
""");
PlatformTestUtil.assertTreeEqual(
svc.getTree(),
"""
-Class1.java
-Class1
myField1: boolean
myField2: boolean
getValue(): int
""");
svc.setActionActive(PublicElementsFilter.ID, true);
svc.setActionActive(PublicElementsFilter.ID, true);
PlatformTestUtil.assertTreeEqual(
svc.getTree(),
"""
-Class1.java
-Class1
getValue(): int
myField1: boolean
myField2: boolean
""");
PlatformTestUtil.assertTreeEqual(
svc.getTree(),
"""
-Class1.java
-Class1
myField1: boolean
myField2: boolean
getValue(): int
""");
svc.setActionActive(PublicElementsFilter.ID, false);
svc.setActionActive(PublicElementsFilter.ID, false);
PlatformTestUtil.assertTreeEqual(
svc.getTree(),
"""
-Class1.java
-Class1
getValue(): int
myField1: boolean
myField2: boolean
""");
}
PlatformTestUtil.assertTreeEqual(
svc.getTree(),
"""
-Class1.java
-Class1
myField1: boolean
myField2: boolean
getValue(): int
""");
});
}
public void testShowClassMembers() {
doTest(new CheckAction() {
@Override
public void testClassStructure(StructureViewComponent svc) {
svc.setActionActive(InheritedMembersNodeProvider.ID, true);
doTest(svc -> {
svc.setActionActive(InheritedMembersNodeProvider.ID, true);
JTree tree = svc.getTree();
tree.collapseRow(2);
PlatformTestUtil.assertTreeEqual(
tree,
"""
-Class2.java
-Class2
+InnerClass1
+InnerClass2
getValue(): int
getClass(): Class<?>
hashCode(): int
equals(Object): boolean
clone(): Object
toString(): String
notify(): void
notifyAll(): void
wait(long): void
wait(long, int): void
wait(): void
finalize(): void
myField1: boolean
myField2: boolean
myField3: boolean
myField4: boolean
""");
JTree tree = svc.getTree();
tree.collapseRow(2);
PlatformTestUtil.assertTreeEqual(
tree,
"""
-Class2.java
-Class2
myField1: boolean
myField2: boolean
myField3: boolean
myField4: boolean
getValue(): int
+InnerClass1
+InnerClass2
getClass(): Class<?>
hashCode(): int
equals(Object): boolean
clone(): Object
toString(): String
notify(): void
notifyAll(): void
wait(long): void
wait(long, int): void
wait(): void
finalize(): void
""");
svc.setActionActive(InheritedMembersNodeProvider.ID, false);
svc.setActionActive(InheritedMembersNodeProvider.ID, false);
PlatformTestUtil.assertTreeEqual(
svc.getTree(),
"""
-Class2.java
-Class2
+InnerClass1
+InnerClass2
getValue(): int
myField1: boolean
myField2: boolean
myField3: boolean
myField4: boolean
""");
}
PlatformTestUtil.assertTreeEqual(
svc.getTree(),
"""
-Class2.java
-Class2
myField1: boolean
myField2: boolean
myField3: boolean
myField4: boolean
getValue(): int
+InnerClass1
+InnerClass2
""");
});
}
public void testVisibilitySorter() {
doTest(new CheckAction() {
@Override
public void testClassStructure(StructureViewComponent svc) {
svc.setActionActive(InheritedMembersNodeProvider.ID, false);
doTest(svc -> {
svc.setActionActive(InheritedMembersNodeProvider.ID, false);
PlatformTestUtil.assertTreeEqual(
svc.getTree(),
"""
-Class2.java
-Class2
__myPrivateFiield: int
_myProtectedField: int
myPublicField: int
""");
PlatformTestUtil.assertTreeEqual(
svc.getTree(),
"""
-Class2.java
-Class2
__myPrivateFiield: int
_myProtectedField: int
myPublicField: int
""");
svc.setActionActive(VisibilitySorter.ID, true);
svc.setActionActive(VisibilitySorter.ID, true);
PlatformTestUtil.assertTreeEqual(
svc.getTree(),
"""
-Class2.java
-Class2
myPublicField: int
_myProtectedField: int
__myPrivateFiield: int
""");
}
PlatformTestUtil.assertTreeEqual(
svc.getTree(),
"""
-Class2.java
-Class2
myPublicField: int
_myProtectedField: int
__myPrivateFiield: int
""");
});
}
public void testMembersOrder() {
doTest(new CheckAction() {
@Override
public void testClassStructure(StructureViewComponent svc) {
svc.setActionActive(InheritedMembersNodeProvider.ID, false);
doTest(svc -> {
svc.setActionActive(InheritedMembersNodeProvider.ID, false);
PlatformTestUtil.assertTreeEqual(
svc.getTree(),
PlatformTestUtil.assertTreeEqual(
svc.getTree(),
"""
-Class2.java
-Class2
ab: int
z: int
af(): void
zf(): void
Class2()
"""
-Class2.java
-Class2
Class2()
af(): void
zf(): void
ab: int
z: int
"""
);
}
);
});
}
@@ -1,10 +1,7 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.structureView;
import com.intellij.ide.structureView.impl.java.FieldsFilter;
import com.intellij.ide.structureView.impl.java.PropertiesGrouper;
import com.intellij.ide.structureView.impl.java.PropertyGroup;
import com.intellij.ide.structureView.impl.java.PublicElementsFilter;
import com.intellij.ide.structureView.impl.java.*;
import com.intellij.ide.structureView.newStructureView.StructureViewComponent;
import com.intellij.ide.util.InheritedMembersNodeProvider;
import com.intellij.ide.util.treeView.smartTree.GroupWrapper;
@@ -128,6 +125,7 @@ public class PropertyElementTest extends LightJavaStructureViewTestCaseBase {
init();
myFixture.testStructureView(component -> {
component.setActionActive(KindSorter.ID, true);
component.setActionActive(PropertiesGrouper.ID, true);
component.setActionActive(Sorter.getAlphaSorterId(), true);
JTree tree = component.getTree();
@@ -181,6 +179,7 @@ public class PropertyElementTest extends LightJavaStructureViewTestCaseBase {
myFixture.configureByText("A.java", NOT_STATIC_GETTERS);
myFixture.testStructureView(component -> {
component.setActionActive(KindSorter.ID, true);
component.setActionActive(PropertiesGrouper.ID, true);
component.setActionActive(Sorter.getAlphaSorterId(), true);
component.setActionActive(InheritedMembersNodeProvider.ID, false);
@@ -2,6 +2,7 @@
package com.intellij.java.structureView;
import com.intellij.JavaTestUtil;
import com.intellij.ide.structureView.impl.java.KindSorter;
import com.intellij.ide.structureView.newStructureView.StructureViewComponent;
import com.intellij.ide.util.InheritedMembersNodeProvider;
import com.intellij.openapi.command.CommandProcessor;
@@ -44,6 +45,7 @@ public class StructureViewUpdatingTest extends TestSourceBasedTestCase {
Disposer.register(getTestRootDisposable(), svc);
fileEditorManager.closeFile(virtualFile);
Document document = PsiDocumentManager.getInstance(myProject).getDocument(psiClass.getContainingFile());
svc.setActionActive(KindSorter.ID, true);
svc.setActionActive(InheritedMembersNodeProvider.ID, true);
PlatformTestUtil.assertTreeEqual(
svc.getTree(),
@@ -106,6 +108,7 @@ public class StructureViewUpdatingTest extends TestSourceBasedTestCase {
FileEditor fileEditor = fileEditors[0];
StructureViewComponent svc = (StructureViewComponent)fileEditor.getStructureViewBuilder()
.createStructureView(fileEditor, myProject);
svc.setActionActive(KindSorter.ID, true);
Disposer.register(getTestRootDisposable(), svc);
fileEditorManager.closeFile(virtualFile);
PlatformTestUtil.waitForPromise(svc.rebuildAndUpdate());
@@ -3,6 +3,7 @@ package com.intellij.projectView;
import com.intellij.ide.projectView.impl.AbstractProjectViewPane;
import com.intellij.ide.structureView.impl.java.JavaInheritedMembersNodeProvider;
import com.intellij.ide.structureView.impl.java.KindSorter;
import com.intellij.ide.structureView.newStructureView.StructureViewComponent;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
@@ -92,6 +93,7 @@ public class ProjectTreeBuilderTest extends BaseProjectViewTestCase {
FileEditor[] fileEditors = fileEditorManager.openFile(virtualFile, false);
StructureViewComponent svc = (StructureViewComponent)fileEditors[0].getStructureViewBuilder()
.createStructureView(fileEditors[0], myProject);
svc.setActionActive(KindSorter.ID, true);
Disposer.register(getTestRootDisposable(), svc);
TreeUtil.collapseAll(svc.getTree(), -1);
fileEditorManager.closeFile(virtualFile);
@@ -3,13 +3,13 @@ action.structureview.group.methods.by.defining.type=Members by Defining Type
action.structureview.show.fields=Fields
action.structureview.show.inherited=Inherited
action.structureview.sort.by.visibility=By Visibility
action.structureview.sort.by.kind=By Kind
file.structure.toggle.show.inherited=Inherited members
node.structureview.invalid=<invalid>
node.structureview.empty=<empty>
structureview.subgroup.sort=Sort
structureview.subgroup.filter=Show
structureview.subgroup.group=Group
structureview.action.show.logical=Logical Structure
action.logical.structure.dump.yaml.text=Dump Logical Structure
structureview.tab.logical=Logical
structureview.tab.physical=Physical
@@ -1,6 +1,7 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.groovy.lang;
import com.intellij.ide.structureView.impl.java.KindSorter;
import com.intellij.ide.util.InheritedMembersNodeProvider;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
@@ -17,6 +18,7 @@ public class GroovyStructureViewTest extends LightJavaCodeInsightFixtureTestCase
}
""");
myFixture.testStructureView(component -> {
component.setActionActive(KindSorter.ID, true);
component.setActionActive(InheritedMembersNodeProvider.ID, false);
PlatformTestUtil.assertTreeEqual(component.getTree(), """
-a.groovy
@@ -40,6 +42,7 @@ public class GroovyStructureViewTest extends LightJavaCodeInsightFixtureTestCase
}
""");
myFixture.testStructureView(component -> {
component.setActionActive(KindSorter.ID, true);
component.setActionActive(InheritedMembersNodeProvider.ID, true);
PlatformTestUtil.assertTreeEqual(component.getTree(), """
-a.groovy
@@ -87,6 +90,7 @@ public class GroovyStructureViewTest extends LightJavaCodeInsightFixtureTestCase
}
""");
myFixture.testStructureView(component -> {
component.setActionActive(KindSorter.ID, true);
component.setActionActive(InheritedMembersNodeProvider.ID, false);
PlatformTestUtil.assertTreeEqual(component.getTree(), """
-a.groovy
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.decompiler
import com.intellij.JavaTestUtil
@@ -11,6 +11,7 @@ import com.intellij.ide.highlighter.ArchiveFileType
import com.intellij.ide.highlighter.JavaClassFileType
import com.intellij.ide.structureView.StructureViewBuilder
import com.intellij.ide.structureView.impl.java.JavaAnonymousClassesNodeProvider
import com.intellij.ide.structureView.impl.java.KindSorter
import com.intellij.ide.structureView.newStructureView.StructureViewComponent
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.application.PluginPathManager
@@ -321,6 +322,7 @@ class IdeaDecompilerTest : LightJavaCodeInsightFixtureTestCase() {
val builder = StructureViewBuilder.getProvider().getStructureViewBuilder(JavaClassFileType.INSTANCE, file, project)!!
val svc = builder.createStructureView(editor, project) as StructureViewComponent
Disposer.register(myFixture.testRootDisposable, svc)
svc.setActionActive(KindSorter.ID, true)
svc.setActionActive(JavaAnonymousClassesNodeProvider.ID, true)
PlatformTestUtil.expandAll(svc.tree)
PlatformTestUtil.assertTreeEqual(svc.tree, s.trimIndent())
@@ -1,6 +1,7 @@
package de.plushnikov.intellij.plugin.extension;
import com.intellij.ide.structureView.impl.java.JavaAnonymousClassesNodeProvider;
import com.intellij.ide.structureView.impl.java.KindSorter;
import com.intellij.ide.structureView.impl.java.PropertiesGrouper;
import com.intellij.ide.structureView.impl.java.SuperTypesGrouper;
import com.intellij.testFramework.PlatformTestUtil;
@@ -68,6 +69,7 @@ public class LombokStructureViewExtensionTest extends AbstractLombokLightCodeIns
svc.setActionActive(SuperTypesGrouper.ID, showInterfaces);
svc.setActionActive(PropertiesGrouper.ID, showProperties);
svc.setActionActive(JavaAnonymousClassesNodeProvider.ID, true);
svc.setActionActive(KindSorter.ID, true);
JTree tree = svc.getTree();
PlatformTestUtil.waitWhileBusy(tree);
PlatformTestUtil.expandAll(tree);