User: joe Date: 18 Feb 25 22:18 Revision: 260390eee9f1f7d87486d1d9781f5a90e3213014 Summary: Add code vision to show how many mixins target a class TeamCity URL: https://ci.mcdev.io/viewModification.html?tab=vcsModificationFiles&modId=9824&personal=false Index: src/main/kotlin/platform/mixin/action/FindMixinsAction.kt =================================================================== --- src/main/kotlin/platform/mixin/action/FindMixinsAction.kt (revision db033d69cffdb1dd68c8d1a7a22e8c76b843e193) +++ src/main/kotlin/platform/mixin/action/FindMixinsAction.kt (revision 260390eee9f1f7d87486d1d9781f5a90e3213014) @@ -33,7 +33,9 @@ import com.intellij.openapi.actionSystem.CommonDataKeys.EDITOR import com.intellij.openapi.actionSystem.CommonDataKeys.PROJECT import com.intellij.openapi.actionSystem.CommonDataKeys.PSI_FILE +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.editor.Editor import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.runBackgroundableTask import com.intellij.openapi.project.Project @@ -42,6 +44,7 @@ import com.intellij.openapi.wm.ToolWindowManager import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiClass +import com.intellij.psi.PsiFile import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.searches.AnnotatedElementsSearch import com.intellij.psi.util.PsiModificationTracker @@ -87,27 +90,26 @@ classes } } - } - override fun actionPerformed(e: AnActionEvent) { - val project = e.getData(PROJECT) ?: return - val file = e.getData(PSI_FILE) ?: return - val caret = e.getData(CARET) ?: return - val editor = e.getData(EDITOR) ?: return + fun openFindMixinsUI( + project: Project, + editor: Editor, + file: PsiFile, + targetClass: PsiClass, + filter: (PsiClass) -> Boolean = { true } + ) { + ApplicationManager.getApplication().assertIsDispatchThread() - val element = file.findElementAt(caret.offset) ?: return - val classOfElement = element.findReferencedClass() ?: return - - invokeLater { runBackgroundableTask("Searching for Mixins", project, true) run@{ indicator -> indicator.isIndeterminate = true val classes = runReadAction { - if (!classOfElement.isValid) { + if (!targetClass.isValid) { return@runReadAction null } - val classes = findMixins(classOfElement, project, indicator) ?: return@runReadAction null + val classes = findMixins(targetClass, project, indicator)?.filter(filter) + ?: return@runReadAction null when (classes.size) { 0 -> null @@ -126,7 +128,7 @@ val window = twManager.getToolWindow(TOOL_WINDOW_ID)!! val component = FindMixinsComponent(classes) val content = ContentFactory.getInstance().createContent(component.panel, null, false) - content.displayName = classOfElement.qualifiedName ?: classOfElement.name + content.displayName = targetClass.qualifiedName ?: targetClass.name window.contentManager.addContent(content) window.activate(null) @@ -135,4 +137,18 @@ } } } + + override fun actionPerformed(e: AnActionEvent) { + val project = e.getData(PROJECT) ?: return + val file = e.getData(PSI_FILE) ?: return + val caret = e.getData(CARET) ?: return + val editor = e.getData(EDITOR) ?: return + + val element = file.findElementAt(caret.offset) ?: return + val classOfElement = element.findReferencedClass() ?: return + + invokeLater { + openFindMixinsUI(project, editor, file, classOfElement) -} + } + } +} Index: src/main/kotlin/platform/mixin/insight/target/AbstractMixinTargetCodeVisionProvider.kt =================================================================== --- src/main/kotlin/platform/mixin/insight/target/AbstractMixinTargetCodeVisionProvider.kt (revision 260390eee9f1f7d87486d1d9781f5a90e3213014) +++ src/main/kotlin/platform/mixin/insight/target/AbstractMixinTargetCodeVisionProvider.kt (revision 260390eee9f1f7d87486d1d9781f5a90e3213014) @@ -0,0 +1,97 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2025 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.insight.target + +import com.intellij.codeInsight.codeVision.CodeVisionEntry +import com.intellij.codeInsight.codeVision.CodeVisionHost +import com.intellij.codeInsight.codeVision.CodeVisionRelativeOrdering +import com.intellij.codeInsight.codeVision.ui.model.ClickableTextCodeVisionEntry +import com.intellij.codeInsight.hints.InlayHintsUtils +import com.intellij.codeInsight.hints.codeVision.CodeVisionProviderBase +import com.intellij.codeInsight.hints.settings.language.isInlaySettingsEditor +import com.intellij.lang.java.JavaLanguage +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.SmartPointerManager +import com.intellij.psi.SyntaxTraverser +import java.awt.event.MouseEvent + +abstract class AbstractMixinTargetCodeVisionProvider : CodeVisionProviderBase() { + override val relativeOrderings: List + get() = listOf( + CodeVisionRelativeOrdering.CodeVisionRelativeOrderingBefore("java.inheritors"), + CodeVisionRelativeOrdering.CodeVisionRelativeOrderingBefore("java.references"), + CodeVisionRelativeOrdering.CodeVisionRelativeOrderingBefore("vcs.code.vision"), + ) + + override fun computeForEditor(editor: Editor, file: PsiFile): List> { + // copied from superclass implementation, except without the check for libraries + + if (file.project.isDefault) { + return emptyList() + } + if (!acceptsFile(file)) { + return emptyList() + } + + // we want to let this provider work only in tests dedicated for code vision, otherwise they harm performance + if (ApplicationManager.getApplication().isUnitTestMode && !CodeVisionHost.isCodeLensTest()) { + return emptyList() + } + + val lenses = ArrayList>() + val traverser = SyntaxTraverser.psiTraverser(file) + for (element in traverser) { + if (!acceptsElement(element)) { + continue + } + if (!InlayHintsUtils.isFirstInLine(element)) { + continue + } + val hint = getHint(element, file) ?: continue + val handler = ClickHandler(element, hint) + val range = InlayHintsUtils.getTextRangeWithoutLeadingCommentsAndWhitespaces(element) + lenses.add(range to ClickableTextCodeVisionEntry(hint, id, handler)) + } + return lenses + } + + override fun acceptsFile(file: PsiFile) = file.language == JavaLanguage.INSTANCE + + private inner class ClickHandler( + element: PsiElement, + private val hint: String, + ) : (MouseEvent?, Editor) -> Unit { + private val elementPointer = SmartPointerManager.createPointer(element) + + override fun invoke(event: MouseEvent?, editor: Editor) { + if (isInlaySettingsEditor(editor)) { + return + } + val element = elementPointer.element ?: return + logClickToFUS(element, hint) + handleClick(editor, element, event) + } + } +} Index: src/main/kotlin/platform/mixin/insight/target/AccessorTargetCodeVisionProvider.kt =================================================================== --- src/main/kotlin/platform/mixin/insight/target/AccessorTargetCodeVisionProvider.kt (revision 260390eee9f1f7d87486d1d9781f5a90e3213014) +++ src/main/kotlin/platform/mixin/insight/target/AccessorTargetCodeVisionProvider.kt (revision 260390eee9f1f7d87486d1d9781f5a90e3213014) @@ -0,0 +1,60 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2025 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.insight.target + +import com.demonwav.mcdev.asset.MCDevBundle +import com.demonwav.mcdev.platform.mixin.action.FindMixinsAction +import com.demonwav.mcdev.platform.mixin.util.isAccessorMixin +import com.demonwav.mcdev.util.findReferencedClass +import com.intellij.codeInsight.codeVision.CodeVisionRelativeOrdering +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import java.awt.event.MouseEvent + +class AccessorTargetCodeVisionProvider : AbstractMixinTargetCodeVisionProvider() { + override val id = "mcdev.mixin.target.accessor" + override val name: String + get() = MCDevBundle("mixin.codeVision.target.accessor.name") + override val relativeOrderings: List + get() = super.relativeOrderings + + CodeVisionRelativeOrdering.CodeVisionRelativeOrderingAfter(MixinTargetCodeVisionProvider.ID) + + override fun acceptsElement(element: PsiElement) = element is PsiClass + + override fun getHint(element: PsiElement, file: PsiFile): String? { + val targetClass = element as? PsiClass ?: return null + val numberOfMixins = FindMixinsAction.findMixins(targetClass, element.project)?.count { it.isAccessorMixin } + ?: return null + if (numberOfMixins == 0) { + return null + } + return MCDevBundle("mixin.codeVision.target.accessor.hint", numberOfMixins) + } + + override fun handleClick(editor: Editor, element: PsiElement, event: MouseEvent?) { + val project = editor.project ?: return + val file = element.containingFile ?: return + val targetClass = element.findReferencedClass() ?: return + FindMixinsAction.openFindMixinsUI(project, editor, file, targetClass) { it.isAccessorMixin } + } +} Index: src/main/kotlin/platform/mixin/insight/target/MixinTargetCodeVisionProvider.kt =================================================================== --- src/main/kotlin/platform/mixin/insight/target/MixinTargetCodeVisionProvider.kt (revision 260390eee9f1f7d87486d1d9781f5a90e3213014) +++ src/main/kotlin/platform/mixin/insight/target/MixinTargetCodeVisionProvider.kt (revision 260390eee9f1f7d87486d1d9781f5a90e3213014) @@ -0,0 +1,60 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2025 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.insight.target + +import com.demonwav.mcdev.asset.MCDevBundle +import com.demonwav.mcdev.platform.mixin.action.FindMixinsAction +import com.demonwav.mcdev.platform.mixin.util.isAccessorMixin +import com.demonwav.mcdev.util.findReferencedClass +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import java.awt.event.MouseEvent + +class MixinTargetCodeVisionProvider : AbstractMixinTargetCodeVisionProvider() { + companion object { + const val ID = "mcdev.mixin.target" + } + + override val id = ID + override val name: String + get() = MCDevBundle("mixin.codeVision.target.name") + + override fun acceptsElement(element: PsiElement) = element is PsiClass + + override fun getHint(element: PsiElement, file: PsiFile): String? { + val targetClass = element as? PsiClass ?: return null + val numberOfMixins = FindMixinsAction.findMixins(targetClass, element.project)?.count { !it.isAccessorMixin } + ?: return null + if (numberOfMixins == 0) { + return null + } + return MCDevBundle("mixin.codeVision.target.hint", numberOfMixins) + } + + override fun handleClick(editor: Editor, element: PsiElement, event: MouseEvent?) { + val project = editor.project ?: return + val file = element.containingFile ?: return + val targetClass = element.findReferencedClass() ?: return + FindMixinsAction.openFindMixinsUI(project, editor, file, targetClass) { !it.isAccessorMixin } + } +} Index: src/main/resources/META-INF/plugin.xml =================================================================== --- src/main/resources/META-INF/plugin.xml (revision db033d69cffdb1dd68c8d1a7a22e8c76b843e193) +++ src/main/resources/META-INF/plugin.xml (revision 260390eee9f1f7d87486d1d9781f5a90e3213014) @@ -742,7 +742,10 @@ + + +