Add IDEA action to show raw FIR of currently opened file
This action works correctly with fir.enabled=true in Gradle properties
This commit is contained in:
committed by
Mikhail Glukhikh
parent
e5f0f2489f
commit
8a5a43d670
@@ -183,6 +183,16 @@ extra["compilerModules"] = arrayOf(
|
||||
":compiler:resolution",
|
||||
":compiler:serialization",
|
||||
":compiler:psi",
|
||||
*if (project.findProperty("fir.enabled") == "true") {
|
||||
arrayOf(
|
||||
":compiler:fir:cones",
|
||||
":compiler:fir:resolve",
|
||||
":compiler:fir:tree",
|
||||
":compiler:fir:psi2fir"
|
||||
)
|
||||
} else {
|
||||
emptyArray()
|
||||
},
|
||||
":compiler:frontend",
|
||||
":compiler:frontend.java",
|
||||
":compiler:frontend.script",
|
||||
|
||||
@@ -10,12 +10,9 @@ plugins {
|
||||
|
||||
jvmTarget = "1.6"
|
||||
|
||||
val generatorClasspath by configurations.creating
|
||||
|
||||
dependencies {
|
||||
compile(project(":core:descriptors"))
|
||||
compile(project(":compiler:fir:cones"))
|
||||
generatorClasspath(project("visitors-generator"))
|
||||
// Necessary only to store bound PsiElement inside FirElement
|
||||
compileOnly(intellijCoreDep()) { includeJars("intellij-core", "annotations") }
|
||||
}
|
||||
@@ -28,6 +25,12 @@ sourceSets {
|
||||
"test" {}
|
||||
}
|
||||
|
||||
val generatorClasspath by configurations.creating
|
||||
|
||||
dependencies {
|
||||
generatorClasspath(project("visitors-generator"))
|
||||
}
|
||||
|
||||
val generateVisitors by tasks.creating(JavaExec::class) {
|
||||
val generationRoot = "$projectDir/src/org/jetbrains/kotlin/fir/"
|
||||
val output = "$projectDir/visitors"
|
||||
|
||||
@@ -33,6 +33,7 @@ dependencies {
|
||||
compile(project(":eval4j"))
|
||||
compile(project(":j2k"))
|
||||
compile(project(":idea:formatter"))
|
||||
compile(project(":idea:fir-view"))
|
||||
compile(project(":idea:idea-core"))
|
||||
compile(project(":idea:ide-common"))
|
||||
compile(project(":idea:idea-jps-common"))
|
||||
|
||||
@@ -33,6 +33,7 @@ dependencies {
|
||||
compile(project(":eval4j"))
|
||||
compile(project(":j2k"))
|
||||
compile(project(":idea:formatter"))
|
||||
compile(project(":idea:fir-view"))
|
||||
compile(project(":idea:idea-core"))
|
||||
compile(project(":idea:ide-common"))
|
||||
compile(project(":idea:idea-jps-common"))
|
||||
|
||||
@@ -33,6 +33,7 @@ dependencies {
|
||||
compile(project(":eval4j"))
|
||||
compile(project(":j2k"))
|
||||
compile(project(":idea:formatter"))
|
||||
compile(project(":idea:fir-view"))
|
||||
compile(project(":idea:idea-core"))
|
||||
compile(project(":idea:ide-common"))
|
||||
compile(project(":idea:idea-jps-common"))
|
||||
|
||||
@@ -33,6 +33,7 @@ dependencies {
|
||||
compile(project(":eval4j"))
|
||||
compile(project(":j2k"))
|
||||
compile(project(":idea:formatter"))
|
||||
compile(project(":idea:fir-view"))
|
||||
compile(project(":idea:idea-core"))
|
||||
compile(project(":idea:ide-common"))
|
||||
compile(project(":idea:idea-jps-common"))
|
||||
|
||||
@@ -33,6 +33,7 @@ dependencies {
|
||||
compile(project(":eval4j"))
|
||||
compile(project(":j2k"))
|
||||
compile(project(":idea:formatter"))
|
||||
compile(project(":idea:fir-view"))
|
||||
compile(project(":idea:idea-core"))
|
||||
compile(project(":idea:ide-common"))
|
||||
compile(project(":idea:idea-jps-common"))
|
||||
|
||||
@@ -33,6 +33,7 @@ dependencies {
|
||||
compile(project(":eval4j"))
|
||||
compile(project(":j2k"))
|
||||
compile(project(":idea:formatter"))
|
||||
compile(project(":idea:fir-view"))
|
||||
compile(project(":idea:idea-core"))
|
||||
compile(project(":idea:ide-common"))
|
||||
compile(project(":idea:idea-jps-common"))
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
|
||||
apply { plugin("kotlin") }
|
||||
apply { plugin("jps-compatible") }
|
||||
|
||||
dependencies {
|
||||
val compile by configurations
|
||||
val compileOnly by configurations
|
||||
|
||||
|
||||
compile(projectDist(":kotlin-stdlib"))
|
||||
compileOnly(project(":kotlin-reflect-api"))
|
||||
compile(project(":core:descriptors"))
|
||||
compile(project(":compiler:fir:tree"))
|
||||
compile(project(":compiler:fir:psi2fir"))
|
||||
|
||||
compile(project(":idea:idea-core"))
|
||||
|
||||
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
|
||||
compileOnly(intellijDep()) { includeJars("util", "openapi", "idea", "asm-all", "jdom", "annotations", "trove4j", "guava", rootProject = rootProject) }
|
||||
compileOnly(intellijPluginDep("gradle")) //{ includeJars("gradle-tooling-api", "gradle", rootProject = rootProject) }
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" {
|
||||
projectDefault()
|
||||
}
|
||||
"test" {}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* 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/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.actions.internal
|
||||
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ide.projectView.PresentationData
|
||||
import com.intellij.ide.util.treeView.IndexComparator
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.runInEdt
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.markup.*
|
||||
import com.intellij.openapi.fileEditor.*
|
||||
import com.intellij.openapi.progress.EmptyProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.Task
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.wm.ToolWindow
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.ui.JBColor
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
import com.intellij.ui.treeStructure.SimpleNode
|
||||
import com.intellij.ui.treeStructure.SimpleTreeBuilder
|
||||
import com.intellij.ui.treeStructure.SimpleTreeStructure
|
||||
import com.intellij.ui.treeStructure.Tree
|
||||
import org.jetbrains.kotlin.fir.FirElement
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.FirSessionBase
|
||||
import org.jetbrains.kotlin.fir.builder.RawFirBuilder
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import java.awt.BorderLayout
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.tree.DefaultMutableTreeNode
|
||||
import javax.swing.tree.DefaultTreeModel
|
||||
import kotlin.math.min
|
||||
import kotlin.reflect.*
|
||||
import kotlin.reflect.full.*
|
||||
import kotlin.reflect.jvm.isAccessible
|
||||
|
||||
class FirExplorerToolWindow(private val project: Project, private val toolWindow: ToolWindow) : JPanel(BorderLayout()), Disposable {
|
||||
|
||||
|
||||
private val tree = Tree()
|
||||
private val treeStructure = FirExplorerTreeStructure()
|
||||
private val builder = SimpleTreeBuilder(tree, DefaultTreeModel(DefaultMutableTreeNode()), treeStructure, IndexComparator.INSTANCE)
|
||||
|
||||
private val level = HighlighterLayer.SELECTION - 100
|
||||
private val highlightingAttributes = TextAttributes().apply {
|
||||
effectType = EffectType.ROUNDED_BOX
|
||||
effectColor = JBColor.RED
|
||||
}
|
||||
|
||||
|
||||
private val rangeHighlightMarkers = mutableListOf<RangeHighlighter>()
|
||||
|
||||
private var currentEditor: Editor? = FileEditorManager.getInstance(project).selectedTextEditor
|
||||
|
||||
fun clearHighlighting(editor: Editor) {
|
||||
rangeHighlightMarkers.forEach { editor.markupModel.removeHighlighter(it) }
|
||||
rangeHighlightMarkers.clear()
|
||||
}
|
||||
|
||||
fun postponeTreeRebuild(editor: Editor, init: Boolean) {
|
||||
ProgressManager.getInstance().runProcessWithProgressAsynchronously(
|
||||
object : Task.Backgroundable(project, "") {
|
||||
override fun run(indicator: ProgressIndicator) {
|
||||
val psiDocumentManager = PsiDocumentManager.getInstance(project)
|
||||
val file = runReadAction { psiDocumentManager.getPsiFile(editor.document) as? KtFile }
|
||||
if (file != null) {
|
||||
val firFile = runReadAction { RawFirBuilder(object : FirSessionBase() {}).buildFirFile(file) }
|
||||
runInEdt {
|
||||
treeStructure.root = FirExplorerTreeNode("root = ", firFile, null)
|
||||
builder.updateFromRoot(!init)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
EmptyProgressIndicator()
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
init {
|
||||
|
||||
|
||||
project.messageBus.connect(this).subscribe(
|
||||
FileEditorManagerListener.FILE_EDITOR_MANAGER,
|
||||
object : FileEditorManagerListener {
|
||||
override fun selectionChanged(event: FileEditorManagerEvent) {
|
||||
runInEdt {
|
||||
val oldEditor = (event.oldEditor as? TextEditor)
|
||||
val textEditor = (event.newEditor as? TextEditor)
|
||||
if (oldEditor != null && oldEditor.editor == currentEditor) {
|
||||
clearHighlighting(currentEditor!!)
|
||||
currentEditor = null
|
||||
}
|
||||
|
||||
if (textEditor != null) {
|
||||
currentEditor = textEditor.editor
|
||||
postponeTreeRebuild(currentEditor!!, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
tree.addTreeSelectionListener { event ->
|
||||
val currentEditor = FileEditorManager.getInstance(project).selectedTextEditor
|
||||
if (currentEditor != null) {
|
||||
runReadAction {
|
||||
clearHighlighting(currentEditor)
|
||||
event.paths.filter { event.isAddedPath(it) }.mapNotNull {
|
||||
val treeNode = it.lastPathComponent
|
||||
if (treeNode is DefaultMutableTreeNode) {
|
||||
val userObject = treeNode.userObject
|
||||
if (userObject is FirExplorerTreeNode) {
|
||||
|
||||
val data = userObject.data
|
||||
val psi = (data as? FirElement)?.psi
|
||||
if (data is FirElement && psi != null) {
|
||||
if (FileDocumentManager.getInstance().getFile(currentEditor.document) == psi.containingFile.virtualFile) {
|
||||
|
||||
rangeHighlightMarkers += currentEditor.markupModel.addRangeHighlighter(
|
||||
psi.startOffset,
|
||||
min(currentEditor.document.textLength, psi.endOffset),
|
||||
level,
|
||||
highlightingAttributes,
|
||||
HighlighterTargetArea.EXACT_RANGE
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
add(tree)
|
||||
builder.initRoot()
|
||||
postponeTreeRebuild(currentEditor!!, true)
|
||||
}
|
||||
|
||||
abstract class AbstractFirExplorerTreeNode(val propertyName: String, parent: SimpleNode?) : SimpleNode(parent) {
|
||||
|
||||
init {
|
||||
templatePresentation.addText(propertyName, SimpleTextAttributes.REGULAR_ATTRIBUTES)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FirExplorerTreeNode(propertyName: String, val data: Any?, parent: SimpleNode?) :
|
||||
AbstractFirExplorerTreeNode(propertyName, parent) {
|
||||
|
||||
init {
|
||||
templatePresentation.setIcon(AllIcons.Nodes.Property)
|
||||
if (data != null) {
|
||||
templatePresentation.addText(data::class.java.simpleName, SimpleTextAttributes.REGULAR_ATTRIBUTES)
|
||||
templatePresentation.addText("(toString = $data)", SimpleTextAttributes.REGULAR_ATTRIBUTES)
|
||||
} else {
|
||||
templatePresentation.addText("null", SimpleTextAttributes.GRAY_ATTRIBUTES)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun KClass<*>.getMemberProperties(): Collection<KProperty<*>> {
|
||||
return try {
|
||||
memberProperties
|
||||
} catch (e: Throwable) {
|
||||
try {
|
||||
declaredMemberProperties
|
||||
} catch (e: Throwable) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getChildren(): Array<SimpleNode> {
|
||||
if (data == null) {
|
||||
return SimpleNode.NO_CHILDREN
|
||||
} else {
|
||||
val classOfData = data::class
|
||||
|
||||
val members = classOfData.getMemberProperties()
|
||||
|
||||
return members.filter {
|
||||
it.visibility == null || it.visibility!! <= KVisibility.PROTECTED
|
||||
}.filter {
|
||||
it.instanceParameter != null
|
||||
}.map {
|
||||
it.getter.isAccessible = true
|
||||
val value = it.getter.call(data)
|
||||
val notNullValueType = it.returnType.withNullability(false)
|
||||
|
||||
val namePrefix = "${it.name}: ${it.returnType.renderSimple()} = "
|
||||
|
||||
when {
|
||||
notNullValueType.isSubtypeOf(firElementType) -> {
|
||||
FirExplorerTreeNode(namePrefix, value as FirElement?, this)
|
||||
}
|
||||
notNullValueType.isSubtypeOf(listElementType) -> {
|
||||
FirExplorerTreeListNode(namePrefix, value as List<*>?, this)
|
||||
}
|
||||
else -> {
|
||||
FirExplorerTreeNode(namePrefix, value, this)
|
||||
}
|
||||
}
|
||||
}.toTypedArray()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val firElementKlass = FirElement::class
|
||||
val firElementType = firElementKlass.starProjectedType
|
||||
val listElementType = List::class.starProjectedType
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FirExplorerTreeListNode(propertyName: String, val data: List<Any?>?, parent: SimpleNode?) :
|
||||
AbstractFirExplorerTreeNode(propertyName, parent) {
|
||||
|
||||
init {
|
||||
if (data != null) {
|
||||
templatePresentation.addText(data::class.simpleName, SimpleTextAttributes.REGULAR_ATTRIBUTES)
|
||||
templatePresentation.addText("(size = ${data.size})", SimpleTextAttributes.REGULAR_ATTRIBUTES)
|
||||
} else {
|
||||
templatePresentation.addText("null", SimpleTextAttributes.REGULAR_ATTRIBUTES)
|
||||
}
|
||||
}
|
||||
|
||||
override fun update(presentation: PresentationData) {
|
||||
super.update(presentation)
|
||||
presentation.setIcon(AllIcons.General.Recursive)
|
||||
}
|
||||
|
||||
override fun getChildren(): Array<SimpleNode> {
|
||||
if (data == null) return SimpleNode.NO_CHILDREN
|
||||
|
||||
|
||||
return data.mapIndexed { index, any ->
|
||||
val propName = "[$index] = "
|
||||
if (any is List<*>) {
|
||||
FirExplorerTreeListNode(propName, any, this)
|
||||
} else {
|
||||
FirExplorerTreeNode(propName, any, this)
|
||||
}
|
||||
}.toTypedArray()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
inner class FirExplorerTreeStructure : SimpleTreeStructure() {
|
||||
var root: SimpleNode = FirExplorerTreeListNode("root = ", null, null)
|
||||
|
||||
override fun getRootElement(): Any {
|
||||
return root
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun KType.renderSimple(): String {
|
||||
fun KVariance?.render(): String {
|
||||
return when (this) {
|
||||
KVariance.IN -> "in "
|
||||
KVariance.OUT -> "out "
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
return buildString {
|
||||
val classifier = classifier
|
||||
when (classifier) {
|
||||
is KClass<*> -> append(classifier.simpleName)
|
||||
is KTypeParameter -> append(classifier.name)
|
||||
}
|
||||
if (arguments.isNotEmpty()) {
|
||||
append("<")
|
||||
arguments.joinTo(this) {
|
||||
it.variance.render() + it.type?.renderSimple()
|
||||
}
|
||||
append(">")
|
||||
}
|
||||
if (isMarkedNullable) {
|
||||
append("?")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.actions.internal
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.wm.ToolWindowAnchor
|
||||
import com.intellij.openapi.wm.ToolWindowManager
|
||||
import com.intellij.ui.content.ContentFactory
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.KotlinIcons
|
||||
|
||||
class ShowFirAction : AnAction() {
|
||||
|
||||
val TOOLWINDOW_ID = "FIR Explorer"
|
||||
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val project = e.project ?: return
|
||||
val toolWindowManager = ToolWindowManager.getInstance(project)
|
||||
|
||||
var toolWindow = toolWindowManager.getToolWindow(TOOLWINDOW_ID)
|
||||
if (toolWindow == null) {
|
||||
toolWindow = toolWindowManager.registerToolWindow(TOOLWINDOW_ID, false, ToolWindowAnchor.RIGHT)
|
||||
toolWindow.icon = KotlinIcons.SMALL_LOGO_13
|
||||
|
||||
val contentManager = toolWindow.contentManager
|
||||
val contentFactory = ContentFactory.SERVICE.getInstance()
|
||||
contentManager.addContent(contentFactory.createContent(FirExplorerToolWindow(project, toolWindow), "", false))
|
||||
}
|
||||
toolWindow.activate(null)
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val file = e.getData(CommonDataKeys.PSI_FILE)
|
||||
e.presentation.isVisible = ApplicationManager.getApplication().isInternal
|
||||
e.presentation.isEnabled = e.project != null && file?.fileType == KotlinFileType.INSTANCE
|
||||
}
|
||||
|
||||
}
|
||||
@@ -131,6 +131,9 @@
|
||||
|
||||
<action id="KotlinFormattingSettingsStatusAction" class="org.jetbrains.kotlin.idea.actions.internal.KotlinFormattingSettingsStatusAction"
|
||||
text="Formatter settings info"/>
|
||||
|
||||
<action id="ShowFirAction" class="org.jetbrains.kotlin.idea.actions.internal.ShowFirAction"
|
||||
text="Show RAW FIR Explorer"/>
|
||||
</group>
|
||||
<separator/>
|
||||
|
||||
|
||||
@@ -29,12 +29,23 @@ val projectsToShadow by extra(listOf(
|
||||
":eval4j",
|
||||
":idea:formatter",
|
||||
":compiler:psi",
|
||||
*if (project.findProperty("fir.enabled") == "true") {
|
||||
arrayOf(
|
||||
":compiler:fir:cones",
|
||||
":compiler:fir:resolve",
|
||||
":compiler:fir:tree",
|
||||
":compiler:fir:psi2fir"
|
||||
)
|
||||
} else {
|
||||
emptyArray()
|
||||
},
|
||||
":compiler:frontend",
|
||||
":compiler:frontend.java",
|
||||
":compiler:frontend.script",
|
||||
":idea:ide-common",
|
||||
":idea",
|
||||
":idea:idea-native",
|
||||
":idea:fir-view",
|
||||
":idea:idea-core",
|
||||
":idea:idea-gradle",
|
||||
":idea:idea-gradle-native",
|
||||
|
||||
@@ -74,6 +74,7 @@ include ":kotlin-build-common",
|
||||
":custom-dependencies:protobuf-relocated",
|
||||
":custom-dependencies:protobuf-lite",
|
||||
":custom-dependencies:android-sdk",
|
||||
":idea:fir-view",
|
||||
":idea:idea-jvm",
|
||||
":idea:idea-maven",
|
||||
":idea:idea-gradle",
|
||||
|
||||
Reference in New Issue
Block a user