diff --git a/plugins/ByteCodeViewer/resources/messages/BytecodeViewerBundle.properties b/plugins/ByteCodeViewer/resources/messages/BytecodeViewerBundle.properties index 4fb5eee34829..331601d8abac 100644 --- a/plugins/ByteCodeViewer/resources/messages/BytecodeViewerBundle.properties +++ b/plugins/ByteCodeViewer/resources/messages/BytecodeViewerBundle.properties @@ -1,3 +1,7 @@ action.BytecodeViewer.text=Show Bytecode action.show.debug.action.name=Show Debug Info -deserialization.error=Could not read class file \ No newline at end of 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. \ No newline at end of file diff --git a/plugins/ByteCodeViewer/src/com/intellij/byteCodeViewer/BytecodeToolWindowPanel.kt b/plugins/ByteCodeViewer/src/com/intellij/byteCodeViewer/BytecodeToolWindowPanel.kt index 56f8aaa0040c..e99337f9d019 100644 --- a/plugins/ByteCodeViewer/src/com/intellij/byteCodeViewer/BytecodeToolWindowPanel.kt +++ b/plugins/ByteCodeViewer/src/com/intellij/byteCodeViewer/BytecodeToolWindowPanel.kt @@ -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(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("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 "$text" + } } } diff --git a/plugins/ByteCodeViewer/src/com/intellij/byteCodeViewer/ShowBytecodeAction.kt b/plugins/ByteCodeViewer/src/com/intellij/byteCodeViewer/ShowBytecodeAction.kt index 8ac88e0bbaee..fed2aa42cb5e 100644 --- a/plugins/ByteCodeViewer/src/com/intellij/byteCodeViewer/ShowBytecodeAction.kt +++ b/plugins/ByteCodeViewer/src/com/intellij/byteCodeViewer/ShowBytecodeAction.kt @@ -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(CommonDataKeys.EDITOR) ?: return - val psiFile = event.getData(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)