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:
Simon Ogorodnik
2018-03-15 15:34:14 +03:00
committed by Mikhail Glukhikh
parent e5f0f2489f
commit 8a5a43d670
14 changed files with 422 additions and 3 deletions
+10
View File
@@ -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",
+6 -3
View File
@@ -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"
+1
View File
@@ -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"))
+1
View File
@@ -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"))
+1
View File
@@ -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"))
+1
View File
@@ -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"))
+1
View File
@@ -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"))
+1
View File
@@ -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
View File
@@ -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" {}
}
+307
View File
@@ -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("?")
}
}
}
+45
View File
@@ -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
}
}
+3
View File
@@ -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/>
+11
View File
@@ -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",
+1
View File
@@ -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",