diff --git a/src/main/kotlin/platform/mixin/completion/MixinCompletionContributor.kt b/src/main/kotlin/platform/mixin/completion/MixinCompletionContributor.kt
index f6e7c3522..ed5801390 100644
--- a/src/main/kotlin/platform/mixin/completion/MixinCompletionContributor.kt
+++ b/src/main/kotlin/platform/mixin/completion/MixinCompletionContributor.kt
@@ -20,8 +20,10 @@
package com.demonwav.mcdev.platform.mixin.completion
+import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.MIXIN
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
@@ -29,17 +31,29 @@ 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.LegacyCompletionContributor
+import com.intellij.codeInsight.completion.PrioritizedLookupElement
+import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.util.text.StringUtil
+import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiClass
import com.intellij.psi.PsiClassType
+import com.intellij.psi.PsiElement
import com.intellij.psi.PsiExpression
import com.intellij.psi.PsiJavaReference
+import com.intellij.psi.PsiLiteral
+import com.intellij.psi.PsiNameValuePair
import com.intellij.psi.PsiQualifiedReference
import com.intellij.psi.PsiSuperExpression
import com.intellij.psi.PsiThisExpression
+import com.intellij.psi.search.PsiShortNamesCache
+import com.intellij.psi.util.PsiTreeUtil
+import com.intellij.util.PlatformIcons
class MixinCompletionContributor : CompletionContributor() {
@@ -59,6 +73,12 @@ class MixinCompletionContributor : CompletionContributor() {
return
}
+ // Check if completing inside @Mixin targets attribute
+ if (isInsideMixinTargets(position)) {
+ provideMixinTargetsCompletion(position, result)
+ return
+ }
+
// Run all the other contributors first
result.runRemainingContributors(parameters, result::passResult)
@@ -123,4 +143,86 @@ class MixinCompletionContributor : CompletionContributor() {
r.addAllElements(elements)
}
}
+
+ private fun isInsideMixinTargets(position: PsiElement): Boolean {
+ val literal = PsiTreeUtil.getParentOfType(position, PsiLiteral::class.java) ?: return false
+ if (literal.value !is String) return false
+ var current = literal.parent
+ while (current != null) {
+ when (current) {
+ is PsiNameValuePair -> {
+ return current.attributeName == "targets" &&
+ current.parent?.parent is PsiAnnotation &&
+ (current.parent.parent as PsiAnnotation).qualifiedName == MIXIN
+ }
+
+ is PsiAnnotation -> return false
+ is PsiClass -> return false
+ }
+ current = current.parent
+ }
+ return false
+ }
+
+ private fun provideMixinTargetsCompletion(position: PsiElement, result: CompletionResultSet) {
+ val literal = PsiTreeUtil.getParentOfType(position, PsiLiteral::class.java) ?: return
+ val text = literal.constantStringValue?.removeSuffix(CompletionUtil.DUMMY_IDENTIFIER) ?: ""
+
+ val project = position.project
+ val scope = position.resolveScope
+ val facade = JavaPsiFacade.getInstance(project)
+
+ val parts = text.split('.').dropLast(1)
+ val packageName = parts.joinToString(".")
+
+ // Show packages and classes from the parent package
+ val pkg = facade.findPackage(packageName.ifEmpty { "" })
+ if (pkg != null) {
+ for (subPkg in pkg.getSubPackages(scope)) {
+ val subPkgName = subPkg.qualifiedName
+ val subPkgSimpleName = subPkg.name ?: continue
+ result.addElement(
+ PrioritizedLookupElement.withPriority(
+ LookupElementBuilder.create(subPkgName)
+ .withPresentableText(subPkgSimpleName)
+ .withIcon(PlatformIcons.PACKAGE_ICON),
+ 1.0,
+ ),
+ )
+ }
+ for (cls in pkg.getClasses(scope)) {
+ val fqn = cls.qualifiedName ?: continue
+ val simpleName = fqn.substringAfterLast('.')
+ result.addElement(
+ PrioritizedLookupElement.withPriority(
+ LookupElementBuilder.create(fqn)
+ .withPresentableText(simpleName)
+ .withLookupString(simpleName)
+ .withIcon(cls.getIcon(0)),
+ 0.5,
+ ),
+ )
+ }
+ }
+
+ // When input is empty, also show all class names (limited)
+ val cache = PsiShortNamesCache.getInstance(project)
+ if (packageName.isEmpty() && text.firstOrNull()?.isUpperCase() == true) {
+ for (className in cache.allClassNames) {
+ for (cls in cache.getClassesByName(className, scope)) {
+ val fqn = cls.qualifiedName ?: continue
+ val simpleName = fqn.substringAfterLast('.')
+ result.addElement(
+ PrioritizedLookupElement.withPriority(
+ LookupElementBuilder.create(fqn)
+ .withPresentableText(simpleName)
+ .withLookupString(simpleName)
+ .withIcon(cls.getIcon(0)),
+ 0.5,
+ ),
+ )
+ }
+ }
+ }
+ }
}
diff --git a/src/main/kotlin/platform/mixin/reference/MixinReferenceContributor.kt b/src/main/kotlin/platform/mixin/reference/MixinReferenceContributor.kt
index 5bef6a319..1dea1f4af 100644
--- a/src/main/kotlin/platform/mixin/reference/MixinReferenceContributor.kt
+++ b/src/main/kotlin/platform/mixin/reference/MixinReferenceContributor.kt
@@ -72,5 +72,11 @@ class MixinReferenceContributor : PsiReferenceContributor() {
MethodDefinitionReference.ELEMENT_PATTERN,
MethodDefinitionReference,
)
+
+ // Mixin targets references
+ registrar.registerReferenceProvider(
+ MixinTargetsReference.ELEMENT_PATTERN,
+ MixinTargetsReference,
+ )
}
}
diff --git a/src/main/kotlin/platform/mixin/reference/MixinTargetsReference.kt b/src/main/kotlin/platform/mixin/reference/MixinTargetsReference.kt
new file mode 100644
index 000000000..e4bf7dbea
--- /dev/null
+++ b/src/main/kotlin/platform/mixin/reference/MixinTargetsReference.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.reference
+
+import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.MIXIN
+import com.demonwav.mcdev.util.findQualifiedClass
+import com.demonwav.mcdev.util.insideAnnotationAttribute
+import com.demonwav.mcdev.util.reference.PolyReferenceResolver
+import com.intellij.patterns.ElementPattern
+import com.intellij.patterns.PsiJavaPatterns
+import com.intellij.patterns.StandardPatterns
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiElementResolveResult
+import com.intellij.psi.PsiLiteral
+import com.intellij.psi.ResolveResult
+import com.intellij.util.ArrayUtilRt
+
+/**
+ * Provides reference resolution for the `targets` attribute of the `@Mixin` annotation.
+ *
+ * Resolves fully qualified class name strings to their corresponding PsiClass for navigation (Ctrl+Click).
+ */
+object MixinTargetsReference : PolyReferenceResolver() {
+
+ val ELEMENT_PATTERN: ElementPattern = PsiJavaPatterns.psiLiteral(StandardPatterns.string())
+ .insideAnnotationAttribute(MIXIN, "targets")
+
+ override fun resolveReference(context: PsiElement): Array {
+ val fqn = (context as? PsiLiteral)?.value as? String ?: return ResolveResult.EMPTY_ARRAY
+ val psiClass = findQualifiedClass(context.project, fqn, context.resolveScope)
+ ?: return ResolveResult.EMPTY_ARRAY
+ return arrayOf(PsiElementResolveResult(psiClass))
+ }
+
+ override fun collectVariants(context: PsiElement): Array = ArrayUtilRt.EMPTY_OBJECT_ARRAY
+}
\ No newline at end of file