mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-370122 Logical Structure: support option "Always select opened element"
GitOrigin-RevId: 8f742fadbc74ebfdfe17dc13bc441c7b1ab1e5ce
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1159d74523
commit
3bbc30089e
+15
-1
@@ -2,11 +2,16 @@
|
||||
package com.intellij.lang.java.logical
|
||||
|
||||
import com.intellij.ide.structureView.logical.LogicalStructureElementsProvider
|
||||
import com.intellij.ide.structureView.logical.model.LogicalPsiDescription
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiClassOwner
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.uast.UClass
|
||||
import org.jetbrains.uast.toUElement
|
||||
|
||||
@ApiStatus.Internal
|
||||
class ClassOwnerLogicalStructureElementsProvider: LogicalStructureElementsProvider<PsiClassOwner, Any> {
|
||||
class ClassOwnerLogicalStructureElementsProvider: LogicalStructureElementsProvider<PsiClassOwner, Any>, LogicalPsiDescription {
|
||||
override fun getElements(parent: PsiClassOwner): List<Any> {
|
||||
val result = mutableListOf<Any>()
|
||||
var convertedAtLeastOne = false
|
||||
@@ -27,4 +32,13 @@ class ClassOwnerLogicalStructureElementsProvider: LogicalStructureElementsProvid
|
||||
if (convertedAtLeastOne) return result
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun getSuitableElement(psiElement: PsiElement): PsiElement? {
|
||||
if (psiElement is PsiClass) return psiElement
|
||||
return (psiElement.toUElement() as? UClass)?.javaPsi
|
||||
}
|
||||
|
||||
override fun isAskChildren(): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,19 +5,45 @@ import com.intellij.ide.projectView.PresentationData
|
||||
import com.intellij.ide.structureView.StructureViewTreeElement
|
||||
import com.intellij.ide.structureView.impl.common.PsiTreeElementBase
|
||||
import com.intellij.ide.structureView.logical.impl.LogicalStructureViewService
|
||||
import com.intellij.ide.structureView.newStructureView.StructureViewComponent
|
||||
import com.intellij.ide.util.treeView.PresentableNodeDescriptor
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
import junit.framework.AssertionFailedError
|
||||
import junit.framework.ComparisonFailure
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertNotNull
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import javax.swing.Icon
|
||||
|
||||
private const val MAX_DEPTH = 20
|
||||
|
||||
fun assertLogicalStructure(
|
||||
psiFile: PsiFile,
|
||||
expectedStructureInitializer: LogicalStructureNode.() -> Unit,
|
||||
) {
|
||||
assertLogicalStructure(psiFile, null, null, expectedStructureInitializer)
|
||||
}
|
||||
|
||||
fun assertLogicalStructure(
|
||||
psiFile: PsiFile,
|
||||
nodePath: String,
|
||||
expectedStructureInitializer: LogicalStructureNode.() -> Unit,
|
||||
) {
|
||||
assertLogicalStructure(psiFile, null, nodePath, expectedStructureInitializer)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param psiFile - is a file which will be created the structure for
|
||||
* @param selectedElement - selected psiElement in the file.
|
||||
* If not null then the corresponding logical element will be found and compared with expected
|
||||
* @param nodePath - path to subtree if one wants to check not the whole structure, but only some branch
|
||||
* @param expectedStructureInitializer - the builder for the expected structure
|
||||
*/
|
||||
fun assertLogicalStructure(
|
||||
psiFile: PsiFile,
|
||||
selectedElement: PsiElement? = null,
|
||||
nodePath: String? = null,
|
||||
expectedStructureInitializer: LogicalStructureNode.() -> Unit,
|
||||
) {
|
||||
@@ -48,6 +74,24 @@ fun assertLogicalStructure(
|
||||
expectedRoot.print("", false),
|
||||
actualRoot.print("", false))
|
||||
}
|
||||
if (selectedElement != null) {
|
||||
val expectedSelectedPath = expectedRoot.getSelectedNodePath().let { it.subList(1, it.size) }
|
||||
val select = (structureView as StructureViewComponent).select(selectedElement, true)
|
||||
val actualPaths = PlatformTestUtil.waitForPromise(select)!!.path.toList().let { it.subList(1, it.size) }
|
||||
val nodePaths = nodePath?.split("/") ?: emptyList()
|
||||
for ((index, any) in actualPaths.withIndex()) {
|
||||
if (index < nodePaths.size) {
|
||||
assertTrue("Selected node is different: ", any.toString().startsWith(nodePaths[index]))
|
||||
}
|
||||
else {
|
||||
val expectedNode = expectedSelectedPath.getOrNull(index - nodePaths.size)!!
|
||||
val expectedName = if (expectedNode.coloredTextElements.isNotEmpty()) {
|
||||
expectedNode.coloredTextElements.joinToString("") { it.text }
|
||||
} else expectedNode.name
|
||||
assertEquals("Selected node is different: ", expectedName, any.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createActualNode(element: StructureViewTreeElement): LogicalStructureNode {
|
||||
@@ -77,6 +121,7 @@ class LogicalStructureNode(
|
||||
private var childrenDontMatter = false
|
||||
private var childrenOrderDontMatter = false
|
||||
private var navigationElementSupplier: (() -> PsiElement?)? = null
|
||||
private var isSelected = false
|
||||
|
||||
fun subNode(subNode: LogicalStructureNode) {
|
||||
subNodes.add(subNode)
|
||||
@@ -103,6 +148,10 @@ class LogicalStructureNode(
|
||||
childrenOrderDontMatter = true
|
||||
}
|
||||
|
||||
fun selected() {
|
||||
isSelected = true
|
||||
}
|
||||
|
||||
fun navigationElement(element: PsiElement) {
|
||||
navigationElementSupplier = { element }
|
||||
}
|
||||
@@ -179,4 +228,17 @@ class LogicalStructureNode(
|
||||
return result
|
||||
}
|
||||
|
||||
fun getSelectedNodePath(): List<LogicalStructureNode> {
|
||||
if (isSelected) {
|
||||
return listOf(this)
|
||||
}
|
||||
for (subNode in subNodes) {
|
||||
val selectedPath = subNode.getSelectedNodePath()
|
||||
if (selectedPath.isNotEmpty()) {
|
||||
return listOf(this) + selectedPath
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
}
|
||||
+13
-1
@@ -10,6 +10,7 @@ import com.intellij.ide.structureView.impl.common.PsiTreeElementBase;
|
||||
import com.intellij.ide.structureView.logical.LogicalStructureDataKeys;
|
||||
import com.intellij.ide.structureView.logical.impl.LogicalStructureViewModel;
|
||||
import com.intellij.ide.structureView.logical.impl.LogicalStructureViewTreeElement;
|
||||
import com.intellij.ide.structureView.logical.model.LogicalPsiDescription;
|
||||
import com.intellij.ide.structureView.symbol.DelegatingPsiElementWithSymbolPointer;
|
||||
import com.intellij.ide.ui.UISettingsListener;
|
||||
import com.intellij.ide.ui.customization.CustomizationUtil;
|
||||
@@ -451,6 +452,9 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre
|
||||
AsyncPromise<TreePath> result = myCurrentFocusPromise = new AsyncPromise<>();
|
||||
var state = new StructureViewSelectVisitorState();
|
||||
TreeVisitor visitor = new TreeVisitor() {
|
||||
|
||||
private Set<LogicalPsiDescription> psiDescriptions = null;
|
||||
|
||||
@Override
|
||||
public @NotNull TreeVisitor.VisitThread visitThread() {
|
||||
return VisitThread.BGT;
|
||||
@@ -462,6 +466,14 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre
|
||||
result.setError("rejected");
|
||||
return TreeVisitor.Action.INTERRUPT;
|
||||
}
|
||||
if (myTreeModel instanceof LogicalStructureViewModel logicalStructureViewModel) {
|
||||
StructureViewTreeElement treeElement = getStructureTreeElement(path.getLastPathComponent());
|
||||
if (treeElement == null) return TreeVisitor.Action.CONTINUE;
|
||||
if (psiDescriptions == null) {
|
||||
psiDescriptions = logicalStructureViewModel.getAssembledModel().getLogicalPsiDescriptions();
|
||||
}
|
||||
return logicalStructureViewModel.visitPathForLogicalElementSelection(treeElement, element, psiDescriptions);
|
||||
}
|
||||
return visitPathForElementSelection(path, element, editorOffset, state);
|
||||
}
|
||||
};
|
||||
@@ -560,7 +572,7 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre
|
||||
return;
|
||||
}
|
||||
|
||||
if (!getSettings().AUTOSCROLL_FROM_SOURCE) {
|
||||
if (!isShowing() || !getSettings().AUTOSCROLL_FROM_SOURCE) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,14 @@
|
||||
- isAutoExpand(java.lang.Object):Z
|
||||
*f:com.intellij.ide.structureView.logical.model.LogicalModelPresentationProvider$Companion
|
||||
- f:getForObject(java.lang.Object):com.intellij.ide.structureView.logical.model.LogicalModelPresentationProvider
|
||||
*:com.intellij.ide.structureView.logical.model.LogicalPsiDescription
|
||||
- a:getSuitableElement(com.intellij.psi.PsiElement):com.intellij.psi.PsiElement
|
||||
- isAskChildren():Z
|
||||
*f:com.intellij.ide.structureView.logical.model.LogicalStructureAssembledModel
|
||||
- *sf:Companion:com.intellij.ide.structureView.logical.model.LogicalStructureAssembledModel$Companion
|
||||
- equals(java.lang.Object):Z
|
||||
- f:getChildren():java.util.List
|
||||
- f:getLogicalPsiDescriptions():java.util.Set
|
||||
- f:getModel():java.lang.Object
|
||||
- f:getParent():com.intellij.ide.structureView.logical.model.LogicalStructureAssembledModel
|
||||
- f:getProject():com.intellij.openapi.project.Project
|
||||
|
||||
+40
-11
@@ -3,21 +3,13 @@ package com.intellij.ide.structureView.logical.impl
|
||||
|
||||
import com.intellij.ide.TypePresentationService
|
||||
import com.intellij.ide.projectView.PresentationData
|
||||
import com.intellij.ide.structureView.StructureViewClickEvent
|
||||
import com.intellij.ide.structureView.StructureViewEventsCollector
|
||||
import com.intellij.ide.structureView.StructureViewModel
|
||||
import com.intellij.ide.structureView.StructureViewModelBase
|
||||
import com.intellij.ide.structureView.StructureViewTreeElement
|
||||
import com.intellij.ide.structureView.*
|
||||
import com.intellij.ide.structureView.impl.common.PsiTreeElementBase
|
||||
import com.intellij.ide.structureView.logical.ContainerElementsProvider
|
||||
import com.intellij.ide.structureView.logical.ExternalElementsProvider
|
||||
import com.intellij.ide.structureView.logical.LogicalStructureTreeElementProvider
|
||||
import com.intellij.ide.structureView.logical.PropertyElementProvider
|
||||
import com.intellij.ide.structureView.logical.model.LogicalContainerPresentationProvider
|
||||
import com.intellij.ide.structureView.logical.model.LogicalModelPresentationProvider
|
||||
import com.intellij.ide.structureView.logical.model.LogicalContainer
|
||||
import com.intellij.ide.structureView.logical.model.LogicalStructureAssembledModel
|
||||
import com.intellij.ide.structureView.logical.model.ProvidedLogicalContainer
|
||||
import com.intellij.ide.structureView.logical.model.*
|
||||
import com.intellij.ide.util.treeView.smartTree.TreeElement
|
||||
import com.intellij.navigation.ItemPresentation
|
||||
import com.intellij.openapi.editor.Editor
|
||||
@@ -25,13 +17,14 @@ import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiTarget
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
import com.intellij.ui.tree.TreeVisitor
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.swing.Icon
|
||||
|
||||
@ApiStatus.Internal
|
||||
class LogicalStructureViewModel private constructor(psiFile: PsiFile, editor: Editor?, assembledModel: LogicalStructureAssembledModel<*>, elementBuilder: ElementsBuilder)
|
||||
class LogicalStructureViewModel private constructor(psiFile: PsiFile, editor: Editor?, val assembledModel: LogicalStructureAssembledModel<*>, elementBuilder: ElementsBuilder)
|
||||
: StructureViewModelBase(psiFile, editor, elementBuilder.createViewTreeElement(assembledModel)),
|
||||
StructureViewModel.ElementInfoProvider, StructureViewModel.ExpandInfoProvider, StructureViewModel.ClickHandler {
|
||||
|
||||
@@ -68,6 +61,42 @@ class LogicalStructureViewModel private constructor(psiFile: PsiFile, editor: Ed
|
||||
}
|
||||
}
|
||||
|
||||
override fun findAcceptableElement(element: PsiElement?): Any? {
|
||||
var elementTmp = element ?: return null
|
||||
val psiDescriptions = assembledModel.getLogicalPsiDescriptions()
|
||||
while (elementTmp !is PsiFile) {
|
||||
for (description in psiDescriptions) {
|
||||
val suitableElement = description.getSuitableElement(elementTmp)
|
||||
if (suitableElement != null) return suitableElement
|
||||
}
|
||||
elementTmp = elementTmp.getParent() ?: return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun visitPathForLogicalElementSelection(treeElement: StructureViewTreeElement, element: Any?, psiDescriptions: Set<LogicalPsiDescription>): TreeVisitor.Action {
|
||||
if (element !is PsiElement) return TreeVisitor.Action.SKIP_CHILDREN
|
||||
if (treeElement is ElementsBuilder.LogicalGroupStructureElement<*>) {
|
||||
if (treeElement.grouper is ExternalElementsProvider<*, *>) {
|
||||
return TreeVisitor.Action.SKIP_CHILDREN
|
||||
}
|
||||
return TreeVisitor.Action.CONTINUE
|
||||
}
|
||||
val targetElement = psiDescriptions.firstNotNullOfOrNull {
|
||||
it.getSuitableElement(element)
|
||||
} ?: return TreeVisitor.Action.SKIP_CHILDREN
|
||||
if (treeElement is ElementsBuilder.PsiElementStructureElement<*>) {
|
||||
if (treeElement.element == targetElement) {
|
||||
return TreeVisitor.Action.INTERRUPT
|
||||
}
|
||||
else if (treeElement.element?.containingFile != targetElement.containingFile) {
|
||||
return TreeVisitor.Action.SKIP_CHILDREN
|
||||
}
|
||||
return TreeVisitor.Action.CONTINUE
|
||||
}
|
||||
return TreeVisitor.Action.SKIP_CHILDREN
|
||||
}
|
||||
|
||||
private fun getModel(element: StructureViewTreeElement): Any? {
|
||||
return when (element) {
|
||||
is ElementsBuilder.LogicalGroupStructureElement<*> -> element.grouper
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// 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.logical.model
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
/**
|
||||
* Interface for logical elements which allow them provide information which psi elements they can represent
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
interface LogicalPsiDescription {
|
||||
|
||||
/**
|
||||
* psi element which can be represented by this model or its children
|
||||
* @return null - if elements with this type are not supported
|
||||
*/
|
||||
fun getSuitableElement(psiElement: PsiElement): PsiElement?
|
||||
|
||||
/**
|
||||
* false - if this logical element can provide info about its children
|
||||
* true - if one needs to go deeper to collect full information
|
||||
*/
|
||||
fun isAskChildren(): Boolean = false
|
||||
|
||||
}
|
||||
+31
@@ -43,6 +43,37 @@ class LogicalStructureAssembledModel<T> private constructor(
|
||||
return result
|
||||
}
|
||||
|
||||
fun getLogicalPsiDescriptions(): Set<LogicalPsiDescription> {
|
||||
return model?.let { getLogicalPsiDescriptions(it) } ?: emptySet()
|
||||
}
|
||||
|
||||
private fun getLogicalPsiDescriptions(model: Any): Set<LogicalPsiDescription> {
|
||||
val result = mutableSetOf<LogicalPsiDescription>()
|
||||
if (model is LogicalPsiDescription) {
|
||||
if (!model.isAskChildren()) {
|
||||
return setOf(model)
|
||||
}
|
||||
else {
|
||||
result.add(model)
|
||||
}
|
||||
}
|
||||
for (provider in LogicalStructureElementsProvider.getProviders(model)) {
|
||||
if (provider is LogicalPsiDescription) {
|
||||
if (!provider.isAskChildren()) {
|
||||
return setOf(provider)
|
||||
}
|
||||
else {
|
||||
result.add(provider)
|
||||
}
|
||||
}
|
||||
if (provider is ExternalElementsProvider<*, *>) continue
|
||||
provider.getElements(model).forEach { child ->
|
||||
result.addAll(getLogicalPsiDescriptions(child))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
internal fun hasSameModelParent(): Boolean {
|
||||
var parentTmp = parent
|
||||
while (parentTmp != null) {
|
||||
|
||||
Reference in New Issue
Block a user