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 @@
+
+
+