User: kyle wood Date: 05 Aug 26 07:15 Revision: 23c36a5be58b526e5ae6fe6591cd78de5bceb6cf Summary: Implement reference provider and auto-complete for @Mixin(target) TeamCity URL: http://ci.mcdev.io:80/viewModification.html?tab=vcsModificationFiles&modId=10597&personal=false Index: src/main/kotlin/platform/mixin/completion/MixinCompletionContributor.kt =================================================================== --- src/main/kotlin/platform/mixin/completion/MixinCompletionContributor.kt (revision 6da60db01112200c2b4c73795bdf18db17aa2023) +++ src/main/kotlin/platform/mixin/completion/MixinCompletionContributor.kt (revision 23c36a5be58b526e5ae6fe6591cd78de5bceb6cf) @@ -20,26 +20,34 @@ package com.demonwav.mcdev.platform.mixin.completion +import com.demonwav.mcdev.platform.mixin.reference.MixinReferences import com.demonwav.mcdev.platform.mixin.util.findShadowTargets import com.demonwav.mcdev.platform.mixin.util.isMixin +import com.demonwav.mcdev.util.constantStringValue import com.demonwav.mcdev.util.equivalentTo import com.demonwav.mcdev.util.filter import com.demonwav.mcdev.util.findContainingClass +import com.intellij.codeInsight.completion.AllClassesGetter import com.intellij.codeInsight.completion.CompletionContributor import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.completion.CompletionType +import com.intellij.codeInsight.completion.CompletionUtil import com.intellij.codeInsight.completion.JavaCompletionContributor import com.intellij.codeInsight.completion.JavaCompletionSorting +import com.intellij.codeInsight.completion.JavaLookupElementBuilder import com.intellij.codeInsight.completion.LegacyCompletionContributor +import com.intellij.codeInsight.completion.PrioritizedLookupElement import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.PsiClassType import com.intellij.psi.PsiExpression import com.intellij.psi.PsiJavaReference +import com.intellij.psi.PsiLiteral import com.intellij.psi.PsiQualifiedReference import com.intellij.psi.PsiSuperExpression import com.intellij.psi.PsiThisExpression +import com.intellij.psi.util.PsiTreeUtil class MixinCompletionContributor : CompletionContributor() { @@ -53,6 +61,12 @@ return } + val literal = PsiTreeUtil.getParentOfType(position, PsiLiteral::class.java) + if (literal != null && MixinReferences.MIXIN_TARGETS.accepts(literal)) { + provideMixinTargetsCompletion(parameters, result, literal) + return + } + // Check if completing inside Mixin class val psiClass = position.findContainingClass() ?: return if (!psiClass.isMixin) { @@ -123,4 +137,35 @@ r.addAllElements(elements) } } + + private fun provideMixinTargetsCompletion( + parameters: CompletionParameters, + result: CompletionResultSet, + literal: PsiLiteral, + ) { + val dummy = CompletionUtil.DUMMY_IDENTIFIER + val dummyTrimmed = CompletionUtil.DUMMY_IDENTIFIER_TRIMMED + val rawText = literal.constantStringValue ?: "" + val text = rawText.removeSuffix(dummy).removeSuffix(dummyTrimmed) + + // If completing a bare class name (no package dot), run global class search + if (!text.contains('.')) { + AllClassesGetter.processJavaClasses( + parameters, + result.withPrefixMatcher(text).prefixMatcher, + true, + ) { cls -> + val fqn = cls.qualifiedName ?: return@processJavaClasses + val simpleName = cls.name ?: return@processJavaClasses + result.addElement( + PrioritizedLookupElement.withPriority( + JavaLookupElementBuilder.forClass(cls, fqn, true) + .withLookupString(fqn) + .withLookupString(simpleName), + 0.5, + ), + ) -} + } + } + } +} Index: src/main/kotlin/platform/mixin/completion/MixinTargetsTypedHandlerDelegate.kt =================================================================== --- src/main/kotlin/platform/mixin/completion/MixinTargetsTypedHandlerDelegate.kt (revision 23c36a5be58b526e5ae6fe6591cd78de5bceb6cf) +++ src/main/kotlin/platform/mixin/completion/MixinTargetsTypedHandlerDelegate.kt (revision 23c36a5be58b526e5ae6fe6591cd78de5bceb6cf) @@ -0,0 +1,45 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2026 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.completion + +import com.demonwav.mcdev.platform.mixin.reference.MixinReferences +import com.intellij.codeInsight.AutoPopupController +import com.intellij.codeInsight.editorActions.TypedHandlerDelegate +import com.intellij.lang.java.JavaLanguage +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiLiteral +import com.intellij.psi.util.PsiTreeUtil + +class MixinTargetsTypedHandlerDelegate : TypedHandlerDelegate() { + override fun checkAutoPopup(charTyped: Char, project: Project, editor: Editor, file: PsiFile): Result { + if ((charTyped == '$' || charTyped == '.') && file.language.isKindOf(JavaLanguage.INSTANCE)) { + AutoPopupController.getInstance(project).scheduleAutoPopup(editor) { f -> + val offset = editor.caretModel.offset + val element = f.findElementAt(offset) + val literal = PsiTreeUtil.getParentOfType(element, PsiLiteral::class.java) + literal != null && MixinReferences.MIXIN_TARGETS.accepts(literal) + } + } + return Result.CONTINUE + } +} Index: src/main/kotlin/platform/mixin/reference/MixinReferenceContributor.kt =================================================================== --- src/main/kotlin/platform/mixin/reference/MixinReferenceContributor.kt (revision 6da60db01112200c2b4c73795bdf18db17aa2023) +++ src/main/kotlin/platform/mixin/reference/MixinReferenceContributor.kt (revision 23c36a5be58b526e5ae6fe6591cd78de5bceb6cf) @@ -23,8 +23,20 @@ import com.demonwav.mcdev.platform.mixin.reference.target.FieldDefinitionReference import com.demonwav.mcdev.platform.mixin.reference.target.MethodDefinitionReference import com.demonwav.mcdev.platform.mixin.reference.target.TargetReference +import com.intellij.codeInsight.AutoPopupController +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiPackage +import com.intellij.psi.PsiReference import com.intellij.psi.PsiReferenceContributor import com.intellij.psi.PsiReferenceRegistrar +import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReference +import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReferenceProvider +import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReferenceSet +import com.intellij.util.PlatformIcons class MixinReferenceContributor : PsiReferenceContributor() { @@ -72,5 +84,70 @@ MethodDefinitionReference.ELEMENT_PATTERN, MethodDefinitionReference, ) + + registrar.registerReferenceProvider( + MixinReferences.MIXIN_TARGETS, + MixinTargetsClassReferenceProvider(), + ) } } + +class MixinTargetsClassReferenceProvider : JavaClassReferenceProvider() { + init { + setOption(ALLOW_DOLLAR_NAMES, true) + setOption(JVM_FORMAT, true) + setOption(ADVANCED_RESOLVE, true) + setOption(RESOLVE_QUALIFIED_CLASS_NAME, true) + } + + override fun getReferencesByString( + str: String, + position: PsiElement, + offsetInPosition: Int, + ): Array { + return object : JavaClassReferenceSet(str, position, offsetInPosition, true, this) { + override fun isAllowDollarInNames(): Boolean = true + + override fun createReference( + referenceIndex: Int, + referenceText: String, + textRange: TextRange, + staticImport: Boolean, + ): JavaClassReference { + return MixinTargetsClassReference(this, referenceIndex, referenceText, textRange, staticImport) + } + }.allReferences + } +} + +class MixinTargetsClassReference( + referenceSet: JavaClassReferenceSet, + referenceIndex: Int, + referenceText: String, + textRange: TextRange, + staticImport: Boolean, +) : JavaClassReference(referenceSet, textRange, referenceIndex, referenceText, staticImport) { + + override fun getVariants(): Array { + val original = super.getVariants() + val list = ArrayList() + for (variant in original) { + val target = if (variant is LookupElement) variant.`object` else variant + if (target is PsiPackage) { + val lookupText = if (variant is LookupElement) variant.lookupString else (target.name ?: "") + val text = if (lookupText.endsWith(".")) lookupText else "$lookupText." + list.add( + LookupElementBuilder.create(target, text) + .withPresentableText(text) + .withIcon(PlatformIcons.PACKAGE_ICON) + .withInsertHandler { context, _ -> + AutoPopupController.getInstance(context.project).scheduleAutoPopup(context.editor) + }, + ) + } else if (target is PsiClass) { + list.add(variant) + } + } + return list.toArray() + } +} Index: src/main/kotlin/platform/mixin/reference/MixinReferences.kt =================================================================== --- src/main/kotlin/platform/mixin/reference/MixinReferences.kt (revision 23c36a5be58b526e5ae6fe6591cd78de5bceb6cf) +++ src/main/kotlin/platform/mixin/reference/MixinReferences.kt (revision 23c36a5be58b526e5ae6fe6591cd78de5bceb6cf) @@ -0,0 +1,13 @@ +package com.demonwav.mcdev.platform.mixin.reference + +import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.MIXIN +import com.demonwav.mcdev.util.insideAnnotationAttribute +import com.intellij.patterns.PsiJavaElementPattern +import com.intellij.patterns.PsiJavaPatterns +import com.intellij.patterns.StandardPatterns +import com.intellij.psi.PsiLiteral + +object MixinReferences { + val MIXIN_TARGETS: PsiJavaElementPattern.Capture = PsiJavaPatterns.psiLiteral(StandardPatterns.string()) + .insideAnnotationAttribute(MIXIN, "targets") +} Index: src/main/resources/META-INF/plugin.xml =================================================================== --- src/main/resources/META-INF/plugin.xml (revision 6da60db01112200c2b4c73795bdf18db17aa2023) +++ src/main/resources/META-INF/plugin.xml (revision 23c36a5be58b526e5ae6fe6591cd78de5bceb6cf) @@ -725,7 +725,7 @@ implementation="com.demonwav.mcdev.platform.mixin.search.MixinSoftImplementMethodSuperSearcher"/> + order="first"/> @@ -747,6 +747,7 @@ implementationClass="com.demonwav.mcdev.platform.mixin.completion.MixinCompletionWeigher" id="mcdev.mixin" order="first"/> +