mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[ByteCodeViewer] IDEA-373600 Gracefully handle missing bytecode
Now, if 'Show Bytecode' action is triggered for a Java file for which we can't find corresponding bytecode (as done by ByteCodeViewerManager#findClassFile), we show a message telling the user to build the project. GitOrigin-RevId: a39c0de5cdd4cb8158261f5d8ef13f08c56a6cd6
This commit is contained in:
committed by
intellij-monorepo-bot
parent
7a7c9b3f2d
commit
982c040f08
@@ -1,3 +1,7 @@
|
||||
action.BytecodeViewer.text=Show Bytecode
|
||||
action.show.debug.action.name=Show Debug Info
|
||||
deserialization.error=Could not read class file
|
||||
deserialization.error=Could not read class file
|
||||
bytecode.not.found.title=Bytecode not found
|
||||
bytecode.not.found=Bytecode not found.
|
||||
bytecode.not.found.for.class=Bytecode not found for class ''{0}''.
|
||||
please.build.project=Please build the project and try again.
|
||||
@@ -13,42 +13,83 @@ import com.intellij.openapi.editor.event.SelectionListener
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.VerticalFlowLayout
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.components.JBPanel
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.util.Textifier
|
||||
import org.jetbrains.org.objectweb.asm.util.TraceClassVisitor
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.GridLayout
|
||||
import java.io.IOException
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.SwingConstants
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Displays JVM bytecode in a tool window.
|
||||
*
|
||||
* It has 2 states:
|
||||
* - there is bytecode: display a non-editable editor with source<->bytecode line matching
|
||||
* - there is no bytecode: display a label instructing the user to build the project first.
|
||||
*/
|
||||
internal class BytecodeToolWindowPanel(
|
||||
private val project: Project,
|
||||
private val psiClass: PsiClass,
|
||||
private val classFile: VirtualFile,
|
||||
) : JPanel(BorderLayout()), Disposable {
|
||||
private val classFile: VirtualFile?,
|
||||
) : JBPanel<Nothing>(BorderLayout()), Disposable {
|
||||
private val bytecodeEditor: Editor = EditorFactory.getInstance()
|
||||
.createEditor(EditorFactory.getInstance().createDocument(""), project, JavaClassFileType.INSTANCE, true)
|
||||
|
||||
init {
|
||||
add(bytecodeEditor.getComponent())
|
||||
setBorder(null)
|
||||
if (classFile == null) {
|
||||
val labelsPanel = JPanel(GridLayout(0, 1))
|
||||
labelsPanel.setBorder(JBUI.Borders.empty())
|
||||
|
||||
setEditorText()
|
||||
EditorFactory.getInstance().getEventMulticaster().addSelectionListener(object : SelectionListener {
|
||||
override fun selectionChanged(e: SelectionEvent) {
|
||||
val sourceEditor = selectedMatchingEditor()
|
||||
if (e.editor != sourceEditor) return
|
||||
updateBytecodeSelection(sourceEditor)
|
||||
val className = psiClass.name
|
||||
val message = if (className != null) BytecodeViewerBundle.message("bytecode.not.found.for.class", className)
|
||||
else BytecodeViewerBundle.message("bytecode.not.found")
|
||||
|
||||
JBLabel(wrapWithHtml(message), SwingConstants.CENTER).apply {
|
||||
foreground = UIUtil.getLabelDisabledForeground()
|
||||
setBorder(JBUI.Borders.empty(2, 0))
|
||||
labelsPanel.add(this)
|
||||
}
|
||||
}, this@BytecodeToolWindowPanel)
|
||||
|
||||
JBLabel(wrapWithHtml(BytecodeViewerBundle.message("please.build.project")), SwingConstants.CENTER).apply {
|
||||
foreground = UIUtil.getLabelDisabledForeground()
|
||||
setBorder(JBUI.Borders.empty(2, 0))
|
||||
labelsPanel.add(this)
|
||||
}
|
||||
|
||||
val centerPanel = JPanel(VerticalFlowLayout(VerticalFlowLayout.MIDDLE))
|
||||
centerPanel.add(labelsPanel)
|
||||
|
||||
add(centerPanel, BorderLayout.CENTER)
|
||||
}
|
||||
else {
|
||||
add(bytecodeEditor.getComponent())
|
||||
setEditorText()
|
||||
EditorFactory.getInstance().getEventMulticaster().addSelectionListener(object : SelectionListener {
|
||||
override fun selectionChanged(e: SelectionEvent) {
|
||||
val sourceEditor = selectedMatchingEditor()
|
||||
if (e.editor != sourceEditor) return
|
||||
updateBytecodeSelection(sourceEditor)
|
||||
}
|
||||
}, this@BytecodeToolWindowPanel)
|
||||
}
|
||||
}
|
||||
|
||||
fun setEditorText() {
|
||||
if (classFile == null) throw IllegalStateException("Class file must not be null at this point")
|
||||
val byteCodeText = deserializeBytecode()
|
||||
bytecodeEditor.document.putUserData(BYTECODE_WITH_DEBUG_INFO, byteCodeText) // include debug info for selection matching
|
||||
runWriteAction {
|
||||
@@ -115,6 +156,7 @@ internal class BytecodeToolWindowPanel(
|
||||
}
|
||||
|
||||
private fun deserializeBytecode(): String {
|
||||
if (classFile == null) throw IllegalStateException("Class file must not be null at this point")
|
||||
try {
|
||||
val bytes = classFile.contentsToByteArray(false)
|
||||
val stringWriter = StringWriter()
|
||||
@@ -139,5 +181,13 @@ internal class BytecodeToolWindowPanel(
|
||||
private val LOG = Logger.getInstance(BytecodeToolWindowPanel::class.java)
|
||||
|
||||
private val BYTECODE_WITH_DEBUG_INFO = Key.create<String>("BYTECODE_WITH_DEBUG_INFO")
|
||||
|
||||
/**
|
||||
* Wrapping with HTML tags ensures the text wraps and is not cut off when the tool window becomes too narrow.
|
||||
*/
|
||||
@NlsContexts.Label
|
||||
private fun wrapWithHtml(@NlsContexts.Label text: String): String {
|
||||
return "<html>$text</html>"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,8 @@ package com.intellij.byteCodeViewer
|
||||
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.actionSystem.*
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.wm.ToolWindowAnchor
|
||||
import com.intellij.openapi.wm.ToolWindowManager
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.ui.content.ContentFactory
|
||||
|
||||
internal class ShowBytecodeAction : AnAction() {
|
||||
@@ -27,16 +25,25 @@ internal class ShowBytecodeAction : AnAction() {
|
||||
hideOnEmptyContent = true
|
||||
canCloseContent = true
|
||||
}
|
||||
val editor = event.getData<Editor>(CommonDataKeys.EDITOR) ?: return
|
||||
val psiFile = event.getData<PsiFile>(CommonDataKeys.PSI_FILE) ?: return
|
||||
val editor = event.getData(CommonDataKeys.EDITOR) ?: return
|
||||
val psiFile = event.getData(CommonDataKeys.PSI_FILE) ?: return
|
||||
val psiElement = psiFile.findElementAt(editor.caretModel.offset) ?: return
|
||||
val psiClass = ByteCodeViewerManager.getContainingClass(psiElement) ?: return
|
||||
val clsFile = ByteCodeViewerManager.findClassFile(psiClass) ?: return
|
||||
val clsFile = ByteCodeViewerManager.findClassFile(psiClass)
|
||||
|
||||
val panel = BytecodeToolWindowPanel(project, psiClass, clsFile)
|
||||
val content = toolWindow.contentManager.contents.firstOrNull { it.description == clsFile.presentableUrl }
|
||||
?: ContentFactory.getInstance().createContent(panel, clsFile.presentableName, false).apply {
|
||||
description = clsFile.presentableUrl
|
||||
}
|
||||
|
||||
val content = if (clsFile != null) {
|
||||
toolWindow.contentManager.contents.firstOrNull { it.description == clsFile.presentableUrl }
|
||||
?: ContentFactory.getInstance().createContent(panel, clsFile.presentableName, false).apply {
|
||||
description = clsFile.presentableUrl // appears on tab hover
|
||||
}
|
||||
}
|
||||
else {
|
||||
toolWindow.contentManager.contents.firstOrNull { it.description == null }
|
||||
?: ContentFactory.getInstance().createContent(panel, BytecodeViewerBundle.message("bytecode.not.found.title"), false)
|
||||
}
|
||||
|
||||
toolWindow.contentManager.addContent(content)
|
||||
content.setDisposer(panel)
|
||||
toolWindow.contentManager.setSelectedContent(content)
|
||||
|
||||
Reference in New Issue
Block a user