From ef6336ec8b27386ee46a94e98d9157e62bc4c4d4 Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Mon, 7 Jul 2025 12:26:24 +0200 Subject: [PATCH] [ByteCodeViewer] IDEA-375560 Fix showing bytecode for a class file that was built from a non-Java source GitOrigin-RevId: d8d46bb0e905bd29a8c2bb24f9c448819b47fefb --- .../messages/BytecodeViewerBundle.properties | 1 + .../byteCodeViewer/BytecodeToolWindowPanel.kt | 4 +- .../byteCodeViewer/ShowBytecodeAction.kt | 96 ++++++++++++------- 3 files changed, 66 insertions(+), 35 deletions(-) diff --git a/plugins/ByteCodeViewer/resources/messages/BytecodeViewerBundle.properties b/plugins/ByteCodeViewer/resources/messages/BytecodeViewerBundle.properties index 9e8f3b2160cf..d11fecb6346d 100644 --- a/plugins/ByteCodeViewer/resources/messages/BytecodeViewerBundle.properties +++ b/plugins/ByteCodeViewer/resources/messages/BytecodeViewerBundle.properties @@ -4,5 +4,6 @@ action.sync.with.editor.name=Sync With Editor deserialization.error=Could not read class file bytecode.not.found.title=Could not find bytecode please.build.project=Please build the project and try again. +could.not.find.class.at.cursor=Try placing the cursor in a class or method. build.project=Build Project notification.group.error.report=Shown notification about missing bytecode diff --git a/plugins/ByteCodeViewer/src/com/intellij/byteCodeViewer/BytecodeToolWindowPanel.kt b/plugins/ByteCodeViewer/src/com/intellij/byteCodeViewer/BytecodeToolWindowPanel.kt index e7a21d14291d..4574a12e6a6a 100644 --- a/plugins/ByteCodeViewer/src/com/intellij/byteCodeViewer/BytecodeToolWindowPanel.kt +++ b/plugins/ByteCodeViewer/src/com/intellij/byteCodeViewer/BytecodeToolWindowPanel.kt @@ -36,7 +36,7 @@ import kotlin.math.min internal class BytecodeToolWindowPanel( private val project: Project, - private val psiClass: PsiClass, + private val psiClass: PsiClass?, private val classFile: VirtualFile, ) : JBPanel(BorderLayout()), Disposable { private val bytecodeEditor: Editor = EditorFactory.getInstance() @@ -114,7 +114,7 @@ internal class BytecodeToolWindowPanel( get() = FileEditorManager.getInstance(project).getSelectedTextEditor()?.takeIf { editor -> val document = editor.getDocument() val virtualFile = FileDocumentManager.getInstance().getFile(document) - virtualFile == psiClass.containingFile.virtualFile + virtualFile == psiClass?.containingFile?.virtualFile } override fun dispose() { diff --git a/plugins/ByteCodeViewer/src/com/intellij/byteCodeViewer/ShowBytecodeAction.kt b/plugins/ByteCodeViewer/src/com/intellij/byteCodeViewer/ShowBytecodeAction.kt index 2479e2025f14..93aa9dd2e033 100644 --- a/plugins/ByteCodeViewer/src/com/intellij/byteCodeViewer/ShowBytecodeAction.kt +++ b/plugins/ByteCodeViewer/src/com/intellij/byteCodeViewer/ShowBytecodeAction.kt @@ -2,20 +2,26 @@ package com.intellij.byteCodeViewer import com.intellij.icons.AllIcons +import com.intellij.ide.highlighter.JavaClassFileType import com.intellij.notification.Notification import com.intellij.notification.NotificationType import com.intellij.openapi.actionSystem.* import com.intellij.openapi.fileEditor.FileEditorManagerListener +import com.intellij.openapi.fileTypes.FileTypeRegistry +import com.intellij.openapi.project.DumbAwareAction +import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.wm.IdeFocusManager import com.intellij.openapi.wm.ToolWindowAnchor import com.intellij.openapi.wm.ToolWindowManager +import com.intellij.psi.PsiClass import com.intellij.ui.content.ContentFactory +import org.jetbrains.annotations.Nls internal val JAVA_CLASS_FILE = Key.create("JAVA_CLASS_FILE") -internal class ShowBytecodeAction : AnAction() { +internal class ShowBytecodeAction : DumbAwareAction() { override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT override fun update(event: AnActionEvent) { @@ -42,43 +48,42 @@ internal class ShowBytecodeAction : AnAction() { ) 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 javaClassFile = ByteCodeViewerManager.findClassFile(psiClass) + val psiFileInEditor = event.getData(CommonDataKeys.PSI_FILE) ?: return + val virtualFileInEditor = event.getData(CommonDataKeys.VIRTUAL_FILE) ?: return + val fileTypeInEditor = FileTypeRegistry.getInstance().getFileTypeByFileName(virtualFileInEditor.nameSequence) - if (javaClassFile == null) { - val title = BytecodeViewerBundle.message("bytecode.not.found.title") - val content = BytecodeViewerBundle.message("please.build.project") - val notification = Notification("Bytecode Viewer Errors", title, content, NotificationType.WARNING).setImportant(false) - - val actionManager = ActionManager.getInstance() - val originalBuildAction = actionManager.getAction("CompileProject") - if (originalBuildAction != null) { - // Wrap the "build project" action because existing ones have various presentations problems: - // - "Compile" doesn't work - // - "CompileDirty" works but has only an ugly icon - // - "CompileProject" works fine but has the wrong text "Rebuild Project" - val buildAction = object : AnAction(BytecodeViewerBundle.message("build.project")) { - override fun actionPerformed(e: AnActionEvent) { - originalBuildAction.actionPerformed(e) - notification.expire() - } - } - - notification.addAction(buildAction) + var psiClass: PsiClass? = null + val classFile = if (fileTypeInEditor == JavaClassFileType.INSTANCE) { + // The user has a class file opened in the focused editor. + virtualFileInEditor + } + else { + // The user has a source file opened in the focused editor. + val psiElement = psiFileInEditor.findElementAt(editor.caretModel.offset) + if (psiElement == null) { + project.showErrorNotification(BytecodeViewerBundle.message("could.not.find.class.at.cursor")) + return } - - notification.notify(project) - return + psiClass = ByteCodeViewerManager.getContainingClass(psiElement) + if (psiClass == null) { + project.showErrorNotification(BytecodeViewerBundle.message("could.not.find.class.at.cursor")) + return + } + val javaClassFile = ByteCodeViewerManager.findClassFile(psiClass) + if (javaClassFile == null) { + project.showErrorNotification(BytecodeViewerBundle.message("please.build.project"), suggestBuild = true) + return + } + javaClassFile } - val panel = BytecodeToolWindowPanel(project, psiClass, javaClassFile) - val content = toolWindow.contentManager.contents.firstOrNull { it.getUserData(JAVA_CLASS_FILE) == javaClassFile } - ?: ContentFactory.getInstance().createContent(panel, javaClassFile.presentableName, false).apply { - description = javaClassFile.presentableUrl // appears on tab hover - putUserData(JAVA_CLASS_FILE, javaClassFile) + val panel = BytecodeToolWindowPanel(project, psiClass, classFile) + + val content = toolWindow.contentManager.contents.firstOrNull { it.getUserData(JAVA_CLASS_FILE) == classFile } + ?: ContentFactory.getInstance().createContent(panel, classFile.presentableName, false).apply { + description = classFile.presentableUrl // appears on tab hover + putUserData(JAVA_CLASS_FILE, classFile) } @@ -125,3 +130,28 @@ internal class ShowBytecodeAction : AnAction() { return DefaultActionGroup(showDebugAction, syncWithEditorAction) } } + +private fun Project.showErrorNotification(@Nls content: String, suggestBuild: Boolean = false) { + val title = BytecodeViewerBundle.message("bytecode.not.found.title") + val notification = Notification("Bytecode Viewer Errors", title, content, NotificationType.WARNING).setImportant(false) + + val actionManager = ActionManager.getInstance() + val originalBuildAction = actionManager.getAction("CompileProject") + if (originalBuildAction != null && suggestBuild) { + // Wrap the "build project" action because existing ones have various presentations problems: + // - "Compile" doesn't work + // - "CompileDirty" works but has only an ugly icon + // - "CompileProject" works fine but has the wrong text "Rebuild Project" + val buildAction = object : AnAction(BytecodeViewerBundle.message("build.project")) { + override fun actionPerformed(e: AnActionEvent) { + originalBuildAction.actionPerformed(e) + notification.expire() + } + } + + notification.addAction(buildAction) + } + + notification.notify(this) + return +} \ No newline at end of file