>
+ }
+ }
+ fun forLogicalModelClass(): Class
+}
+
+interface ContainerElementsProvider
: LogicalStructureElementsProvider
{
+ val containerName: String?
+}
\ No newline at end of file
diff --git a/platform/structure-view-impl/src/com/intellij/ide/logical/LogicalStructureTreeElementProvider.kt b/platform/structure-view-impl/src/com/intellij/ide/logical/LogicalStructureTreeElementProvider.kt
new file mode 100644
index 000000000000..5612e23845a0
--- /dev/null
+++ b/platform/structure-view-impl/src/com/intellij/ide/logical/LogicalStructureTreeElementProvider.kt
@@ -0,0 +1,25 @@
+// 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.ide.logical
+
+import com.intellij.ide.structureView.StructureViewTreeElement
+import com.intellij.openapi.extensions.ExtensionPointName
+
+interface LogicalStructureTreeElementProvider {
+
+ companion object {
+ private const val EP_FQN = "com.intellij.lang.logicalStructureTreeElementProvider"
+
+ val EP_NAME = ExtensionPointName.create>(EP_FQN)
+
+ fun getTreeElement(model: T): StructureViewTreeElement? {
+ val provider = EP_NAME.extensionList
+ .firstOrNull { it.getModelClass().isInstance(model) } as? LogicalStructureTreeElementProvider
+ return provider?.getTreeElement(model)
+ }
+ }
+
+ fun getModelClass(): Class
+
+ fun getTreeElement(model: T): StructureViewTreeElement
+
+}
diff --git a/platform/structure-view-impl/src/com/intellij/ide/logical/impl/LogicalGrouper.kt b/platform/structure-view-impl/src/com/intellij/ide/logical/impl/LogicalGrouper.kt
new file mode 100644
index 000000000000..14b5fca8d6d9
--- /dev/null
+++ b/platform/structure-view-impl/src/com/intellij/ide/logical/impl/LogicalGrouper.kt
@@ -0,0 +1,54 @@
+// 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.ide.logical.impl
+
+import com.intellij.ide.TypePresentationService
+import com.intellij.ide.projectView.PresentationData
+import com.intellij.ide.util.treeView.AbstractTreeNode
+import com.intellij.ide.util.treeView.smartTree.ActionPresentation
+import com.intellij.ide.util.treeView.smartTree.Group
+import com.intellij.ide.util.treeView.smartTree.Grouper
+import com.intellij.ide.util.treeView.smartTree.TreeElement
+import com.intellij.navigation.ItemPresentation
+import javax.swing.Icon
+
+class LogicalGrouper: Grouper {
+
+ private val typePresentationService = TypePresentationService.getService()
+
+ override fun getPresentation(): ActionPresentation {
+ return object : ActionPresentation {
+ override fun getText(): String = "Logical"
+
+ override fun getDescription(): String = ""
+
+ override fun getIcon(): Icon? = null
+ }
+ }
+
+ override fun getName(): String = "Logical"
+
+ override fun group(parent: AbstractTreeNode<*>, children: MutableCollection): Collection {
+ val structureElement = parent.value as? LogicalStructureViewTreeElement<*> ?: return emptyList()
+ val result = mutableListOf()
+ for (pair in structureElement.getLogicalAssembledModel().getChildrenGrouped()) {
+ result.add(object: Group {
+ override fun getPresentation(): ItemPresentation {
+ val groupingObject = pair.first
+ return PresentationData(
+ typePresentationService.getObjectName(groupingObject),
+ typePresentationService.getTypeName(groupingObject),
+ typePresentationService.getIcon(groupingObject),
+ null
+ )
+ }
+
+ override fun getChildren(): Collection {
+ return pair.second.mapNotNull { childLogical ->
+ children.firstOrNull { it is LogicalStructureViewTreeElement<*> && it.getLogicalAssembledModel().model == childLogical.model }
+ }
+ }
+ })
+ }
+ return result
+ }
+}
\ No newline at end of file
diff --git a/platform/structure-view-impl/src/com/intellij/ide/logical/impl/LogicalStructureViewService.kt b/platform/structure-view-impl/src/com/intellij/ide/logical/impl/LogicalStructureViewService.kt
new file mode 100644
index 000000000000..0c5e224853f0
--- /dev/null
+++ b/platform/structure-view-impl/src/com/intellij/ide/logical/impl/LogicalStructureViewService.kt
@@ -0,0 +1,51 @@
+// 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.ide.logical.impl
+
+import com.intellij.ide.logical.model.LogicalStructureAssembledModel
+import com.intellij.ide.structureView.*
+import com.intellij.ide.util.treeView.smartTree.Grouper
+import com.intellij.openapi.components.Service
+import com.intellij.openapi.editor.Editor
+import com.intellij.openapi.project.Project
+import com.intellij.psi.PsiFile
+
+@Service(Service.Level.PROJECT)
+class LogicalStructureViewService(
+ private val project: Project
+) {
+
+ companion object {
+ fun getInstance(project: Project): LogicalStructureViewService = project.getService(LogicalStructureViewService::class.java)
+ }
+
+ fun getLogicalStructureBuilder(psiFile: PsiFile): StructureViewBuilder? {
+ val assembledModel = LogicalStructureAssembledModel(project, psiFile)
+ if (assembledModel.getChildren().isEmpty()) return null
+ return object: TreeBasedStructureViewBuilder() {
+ override fun createStructureViewModel(editor: Editor?): StructureViewModel {
+ return LogicalStructureViewModel(psiFile, editor, assembledModel)
+ }
+
+ override fun isRootNodeShown(): Boolean {
+ return false
+ }
+ }
+ }
+
+}
+
+private class LogicalStructureViewModel(psiFile: PsiFile, editor: Editor?, assembledModel: LogicalStructureAssembledModel<*>)
+ : StructureViewModelBase(psiFile, editor, createViewTreeElement(assembledModel)), StructureViewModel.ElementInfoProvider {
+
+ override fun isAlwaysShowsPlus(element: StructureViewTreeElement?): Boolean {
+ return false
+ }
+
+ override fun isAlwaysLeaf(element: StructureViewTreeElement?): Boolean {
+ return false
+ }
+
+ override fun getGroupers(): Array {
+ return arrayOf(LogicalGrouper())
+ }
+}
\ No newline at end of file
diff --git a/platform/structure-view-impl/src/com/intellij/ide/logical/impl/LogicalStructureViewTreeElements.kt b/platform/structure-view-impl/src/com/intellij/ide/logical/impl/LogicalStructureViewTreeElements.kt
new file mode 100644
index 000000000000..ae50f6282fc3
--- /dev/null
+++ b/platform/structure-view-impl/src/com/intellij/ide/logical/impl/LogicalStructureViewTreeElements.kt
@@ -0,0 +1,84 @@
+// 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.ide.logical.impl
+
+import com.intellij.ide.TypePresentationService
+import com.intellij.ide.logical.LogicalStructureTreeElementProvider
+import com.intellij.ide.logical.model.LogicalStructureAssembledModel
+import com.intellij.ide.logical.model.LogicalStructureModel
+import com.intellij.ide.logical.model.PsiRepresentingLogicalStructureModel
+import com.intellij.ide.projectView.PresentationData
+import com.intellij.ide.structureView.StructureViewTreeElement
+import com.intellij.ide.structureView.impl.common.PsiTreeElementBase
+import com.intellij.ide.util.treeView.smartTree.TreeElement
+import com.intellij.navigation.ItemPresentation
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiTarget
+import javax.swing.Icon
+
+fun createViewTreeElement(assembledModel: LogicalStructureAssembledModel): StructureViewTreeElement {
+ val model = assembledModel.model
+ val explicitElement = LogicalStructureTreeElementProvider.getTreeElement(model)
+ if (explicitElement != null) return explicitElement
+ val psiElement: PsiElement? = when {
+ model is PsiElement -> model
+ model is PsiTarget && model.isValid() -> model.navigationElement
+ else -> null
+ }
+ if (psiElement != null) {
+ return PsiElementStructureElement(assembledModel, psiElement)
+ }
+ return OtherStructureElement(assembledModel)
+}
+
+
+interface LogicalStructureViewTreeElement: StructureViewTreeElement {
+
+ fun getLogicalAssembledModel(): LogicalStructureAssembledModel
+
+}
+
+private class PsiElementStructureElement(
+ private val assembledModel: LogicalStructureAssembledModel,
+ psiElement: PsiElement
+): PsiTreeElementBase(psiElement), LogicalStructureViewTreeElement {
+
+ private val typePresentationService = TypePresentationService.getService()
+
+ override fun getPresentableText(): String? = typePresentationService.getObjectName(assembledModel.model!!)
+
+ override fun getLocationString(): String? = typePresentationService.getTypeName(assembledModel.model!!)
+
+ override fun getIcon(open: Boolean): Icon? = typePresentationService.getIcon(assembledModel.model!!)
+
+ override fun getChildrenBase(): Collection {
+ return assembledModel.getChildren().map { createViewTreeElement(it) }
+ }
+
+ override fun getLogicalAssembledModel() = assembledModel
+
+}
+
+private class OtherStructureElement(
+ private val assembledModel: LogicalStructureAssembledModel,
+): StructureViewTreeElement, LogicalStructureViewTreeElement {
+
+ private val typePresentationService = TypePresentationService.getService()
+
+ override fun getValue(): Any = assembledModel.model ?: ""
+
+ override fun getLogicalAssembledModel() = assembledModel
+
+ override fun getPresentation(): ItemPresentation {
+ return object: ItemPresentation {
+ override fun getPresentableText(): String? = typePresentationService.getObjectName(assembledModel.model!!)
+
+ override fun getLocationString(): String? = typePresentationService.getTypeName(assembledModel.model!!)
+
+ override fun getIcon(unused: Boolean): Icon? = typePresentationService.getIcon(assembledModel.model!!)
+ }
+ }
+
+ override fun getChildren(): Array {
+ return assembledModel.getChildren().map { createViewTreeElement(it) }.toTypedArray()
+ }
+}
\ No newline at end of file
diff --git a/platform/structure-view-impl/src/com/intellij/ide/logical/model/LogicalStructureAssembledModel.kt b/platform/structure-view-impl/src/com/intellij/ide/logical/model/LogicalStructureAssembledModel.kt
new file mode 100644
index 000000000000..3fbef637d02e
--- /dev/null
+++ b/platform/structure-view-impl/src/com/intellij/ide/logical/model/LogicalStructureAssembledModel.kt
@@ -0,0 +1,36 @@
+// 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.ide.logical.model
+
+import com.intellij.ide.logical.ContainerElementsProvider
+import com.intellij.ide.logical.ConvertElementsProvider
+import com.intellij.ide.logical.LogicalStructureElementsProvider
+import com.intellij.openapi.project.Project
+
+class LogicalStructureAssembledModel(
+ val project: Project,
+ val model: T
+) {
+
+ fun getChildren(): List> {
+ return LogicalStructureElementsProvider.getProviders(model!!)
+ .flatMap { it.getElements(model) }
+ .flatMap { ConvertElementsProvider.convert(it) }
+ .map { LogicalStructureAssembledModel(project, it) }
+ .toList()
+ }
+
+ /**
+ * The grouping element in each pair - Any - can be any object, for which a PresentationProvider is registered
+ */
+ fun getChildrenGrouped(): List>>> {
+ return LogicalStructureElementsProvider.getProviders(model!!)
+ .mapNotNull { provider ->
+ if (provider !is ContainerElementsProvider) return@mapNotNull null
+ val children = provider.getElements(model)
+ .map { LogicalStructureAssembledModel(project, it) }
+ Pair(provider, children)
+ }
+ .toList()
+ }
+
+}
\ No newline at end of file
diff --git a/platform/structure-view-impl/src/com/intellij/ide/logical/model/LogicalStructureModel.kt b/platform/structure-view-impl/src/com/intellij/ide/logical/model/LogicalStructureModel.kt
new file mode 100644
index 000000000000..21a960b140a8
--- /dev/null
+++ b/platform/structure-view-impl/src/com/intellij/ide/logical/model/LogicalStructureModel.kt
@@ -0,0 +1,8 @@
+// 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.ide.logical.model
+
+interface LogicalStructureModel {
+
+ fun getName(): String
+
+}
\ No newline at end of file
diff --git a/platform/structure-view-impl/src/com/intellij/ide/logical/model/PsiRepresentingLogicalStructureModel.kt b/platform/structure-view-impl/src/com/intellij/ide/logical/model/PsiRepresentingLogicalStructureModel.kt
new file mode 100644
index 000000000000..cae49707490c
--- /dev/null
+++ b/platform/structure-view-impl/src/com/intellij/ide/logical/model/PsiRepresentingLogicalStructureModel.kt
@@ -0,0 +1,10 @@
+// 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.ide.logical.model
+
+import com.intellij.psi.PsiElement
+
+interface PsiRepresentingLogicalStructureModel: LogicalStructureModel {
+
+ fun getPsiElement(): T?
+
+}
\ No newline at end of file
diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/structureView/KotlinClassLogicalStructureTreeElementProvider.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/structureView/KotlinClassLogicalStructureTreeElementProvider.kt
new file mode 100644
index 000000000000..2e18610deb38
--- /dev/null
+++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/structureView/KotlinClassLogicalStructureTreeElementProvider.kt
@@ -0,0 +1,16 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package org.jetbrains.kotlin.idea.structureView
+
+import com.intellij.ide.logical.LogicalStructureTreeElementProvider
+import com.intellij.ide.structureView.StructureViewTreeElement
+import org.jetbrains.kotlin.asJava.classes.KtLightClass
+
+class KotlinClassLogicalStructureTreeElementProvider: LogicalStructureTreeElementProvider {
+
+ override fun getModelClass(): Class = KtLightClass::class.java
+
+ override fun getTreeElement(model: KtLightClass): StructureViewTreeElement {
+ return KotlinStructureViewElement(model.kotlinOrigin ?: model, false)
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/kotlin/plugin/k1/resources/META-INF/kotlin-core-fe10.xml b/plugins/kotlin/plugin/k1/resources/META-INF/kotlin-core-fe10.xml
index 8069744ab091..2736a52c0c39 100644
--- a/plugins/kotlin/plugin/k1/resources/META-INF/kotlin-core-fe10.xml
+++ b/plugins/kotlin/plugin/k1/resources/META-INF/kotlin-core-fe10.xml
@@ -155,6 +155,9 @@
order="first"
key="KNM"
factoryClass="org.jetbrains.kotlin.idea.structureView.KtDecompiledFileStructureViewBuilderProvider"/>
+