2020
2121package com.demonwav.mcdev.platform.fabric.reference
2222
23+ import com.demonwav.mcdev.platform.fabric.util.FabricConstants
24+ import com.demonwav.mcdev.util.fullQualifiedName
2325import com.demonwav.mcdev.util.manipulator
2426import com.demonwav.mcdev.util.reference.InspectionReference
27+ import com.intellij.codeInsight.completion.JavaLookupElementBuilder
2528import com.intellij.json.psi.JsonStringLiteral
2629import com.intellij.openapi.util.TextRange
2730import com.intellij.psi.JavaPsiFacade
2831import com.intellij.psi.PsiClass
32+ import com.intellij.psi.PsiClassType
2933import com.intellij.psi.PsiElement
3034import com.intellij.psi.PsiElementResolveResult
35+ import com.intellij.psi.PsiField
3136import com.intellij.psi.PsiMethod
3237import com.intellij.psi.PsiModifier
3338import com.intellij.psi.PsiPolyVariantReference
3439import com.intellij.psi.PsiReference
3540import com.intellij.psi.PsiReferenceBase
3641import com.intellij.psi.PsiReferenceProvider
3742import com.intellij.psi.ResolveResult
43+ import com.intellij.psi.search.searches.ClassInheritorsSearch
44+ import com.intellij.util.ArrayUtil
3845import com.intellij.util.IncorrectOperationException
3946import com.intellij.util.ProcessingContext
4047
@@ -46,8 +53,8 @@ object EntryPointReference : PsiReferenceProvider() {
4653 val manipulator = element.manipulator ? : return PsiReference .EMPTY_ARRAY
4754 val range = manipulator.getRangeInElement(element)
4855 val text = element.text.substring(range.startOffset, range.endOffset)
49- val methodParts = text.split(" ::" , limit = 2 )
50- val clazzParts = methodParts [0 ].split(" $" , limit = 0 )
56+ val memberParts = text.split(" ::" , limit = 2 )
57+ val clazzParts = memberParts [0 ].split(" $" , limit = 0 )
5158 val references = mutableListOf<Reference >()
5259 var cursor = - 1
5360 var innerClassDepth = - 1
@@ -64,12 +71,12 @@ object EntryPointReference : PsiReferenceProvider() {
6471 )
6572 cursor + = clazzPart.length
6673 }
67- if (methodParts .size == 2 ) {
74+ if (memberParts .size == 2 ) {
6875 cursor + = 2
6976 references.add(
7077 Reference (
7178 element,
72- range.cutOut(TextRange .from(cursor, methodParts [1 ].length)),
79+ range.cutOut(TextRange .from(cursor, memberParts [1 ].length)),
7380 innerClassDepth,
7481 true ,
7582 ),
@@ -81,16 +88,16 @@ object EntryPointReference : PsiReferenceProvider() {
8188 private fun resolveReference (
8289 element : JsonStringLiteral ,
8390 innerClassDepth : Int ,
84- isMethodReference : Boolean ,
91+ isMemberReference : Boolean ,
8592 ): Array <PsiElement > {
8693 val strReference = element.value
87- val methodParts = strReference.split(" ::" , limit = 2 )
94+ val memberParts = strReference.split(" ::" , limit = 2 )
8895 // split at dollar sign for inner class evaluation
89- val clazzParts = methodParts [0 ].split(" $" , limit = 0 )
96+ val clazzParts = memberParts [0 ].split(" $" , limit = 0 )
9097 // this case should only happen if someone misuses the method, better protect against it anyways
9198 if (innerClassDepth >= clazzParts.size ||
9299 innerClassDepth + 1 < clazzParts.size &&
93- isMethodReference
100+ isMemberReference
94101 ) {
95102 throw IncorrectOperationException (" Invalid reference" )
96103 }
@@ -104,27 +111,63 @@ object EntryPointReference : PsiReferenceProvider() {
104111 if (inner.contains(' .' )) return PsiElement .EMPTY_ARRAY
105112 clazz = clazz.findInnerClassByName(inner, false ) ? : return PsiElement .EMPTY_ARRAY
106113 }
107- return if (isMethodReference ) {
108- if (methodParts .size == 1 ) {
114+ return if (isMemberReference ) {
115+ if (memberParts .size == 1 ) {
109116 throw IncorrectOperationException (" Invalid reference" )
110117 }
111- clazz.methods.filter { method ->
112- method.name == methodParts[1 ] &&
113- method.hasModifierProperty(PsiModifier .PUBLIC ) &&
114- method.hasModifierProperty(PsiModifier .STATIC )
115- }.toTypedArray()
118+
119+ val members = mutableListOf<PsiElement >()
120+ clazz.fields.filterTo(members) { field ->
121+ field.name == memberParts[1 ] &&
122+ field.hasModifierProperty(PsiModifier .PUBLIC ) &&
123+ field.hasModifierProperty(PsiModifier .STATIC )
124+ }
125+
126+ clazz.methods.filterTo(members) { method ->
127+ method.name == memberParts[1 ] &&
128+ method.hasModifierProperty(PsiModifier .PUBLIC )
129+ }
130+
131+ members.toTypedArray()
116132 } else {
117133 arrayOf(clazz)
118134 }
119135 }
120136
121137 fun isEntryPointReference (reference : PsiReference ) = reference is Reference
122138
123- private class Reference (
139+ fun isValidEntrypointClass (element : PsiClass ): Boolean {
140+ val psiFacade = JavaPsiFacade .getInstance(element.project)
141+ var inheritsEntrypointInterface = false
142+ for (entrypoint in FabricConstants .ENTRYPOINTS ) {
143+ val entrypointClass = psiFacade.findClass(entrypoint, element.resolveScope)
144+ ? : continue
145+ if (element.isInheritor(entrypointClass, true )) {
146+ inheritsEntrypointInterface = true
147+ break
148+ }
149+ }
150+ return inheritsEntrypointInterface
151+ }
152+
153+ fun isValidEntrypointField (field : PsiField ): Boolean {
154+ if (! field.hasModifierProperty(PsiModifier .PUBLIC ) || ! field.hasModifierProperty(PsiModifier .STATIC )) {
155+ return false
156+ }
157+
158+ val fieldTypeClass = (field.type as ? PsiClassType )?.resolve()
159+ return fieldTypeClass != null && isValidEntrypointClass(fieldTypeClass)
160+ }
161+
162+ fun isValidEntrypointMethod (method : PsiMethod ): Boolean {
163+ return method.hasModifierProperty(PsiModifier .PUBLIC ) && ! method.hasParameters()
164+ }
165+
166+ class Reference (
124167 element : JsonStringLiteral ,
125168 range : TextRange ,
126169 private val innerClassDepth : Int ,
127- private val isMethodReference : Boolean ,
170+ private val isMemberReference : Boolean ,
128171 ) :
129172 PsiReferenceBase <JsonStringLiteral >(element, range),
130173 PsiPolyVariantReference ,
@@ -134,7 +177,7 @@ object EntryPointReference : PsiReferenceProvider() {
134177 override val unresolved = resolve() == null
135178
136179 override fun multiResolve (incompleteCode : Boolean ): Array <ResolveResult > {
137- return resolveReference(element, innerClassDepth, isMethodReference )
180+ return resolveReference(element, innerClassDepth, isMemberReference )
138181 .map { PsiElementResolveResult (it) }.toTypedArray()
139182 }
140183
@@ -154,14 +197,17 @@ object EntryPointReference : PsiReferenceProvider() {
154197 val text = element.text.substring(range.startOffset, range.endOffset)
155198 val parts = text.split(" ::" , limit = 2 )
156199
157- if (isMethodReference) {
158- val targetMethod = newTarget as ? PsiMethod
159- ? : throw IncorrectOperationException (" Cannot target $newTarget " )
200+ if (isMemberReference) {
201+ val newTargetName = when (newTarget) {
202+ is PsiMethod -> newTarget.name
203+ is PsiField -> newTarget.name
204+ else -> throw IncorrectOperationException (" Cannot target $newTarget " )
205+ }
160206 if (parts.size == 1 ) {
161207 throw IncorrectOperationException (" Invalid reference" )
162208 }
163- val methodRange = range.cutOut(TextRange .from(parts[0 ].length + 2 , parts[1 ].length))
164- return manipulator.handleContentChange(element, methodRange, targetMethod.name )
209+ val memberRange = range.cutOut(TextRange .from(parts[0 ].length + 2 , parts[1 ].length))
210+ return manipulator.handleContentChange(element, memberRange, newTargetName )
165211 } else {
166212 val targetClass = newTarget as ? PsiClass
167213 ? : throw IncorrectOperationException (" Cannot target $newTarget " )
@@ -173,5 +219,39 @@ object EntryPointReference : PsiReferenceProvider() {
173219 return manipulator.handleContentChange(element, classRange, targetClass.qualifiedName)
174220 }
175221 }
222+
223+ override fun getVariants (): Array <Any > {
224+ val manipulator = element.manipulator
225+ ? : return ArrayUtil .EMPTY_OBJECT_ARRAY
226+
227+ val range = manipulator.getRangeInElement(element)
228+ val text = element.text.substring(range.startOffset, range.endOffset)
229+ val parts = text.split(" ::" , limit = 2 )
230+
231+ val variants = mutableListOf<Any >()
232+ if (! isMemberReference) {
233+ val psiFacade = JavaPsiFacade .getInstance(element.project)
234+ for (entrypoint in FabricConstants .ENTRYPOINTS ) {
235+ val entrypointClass = psiFacade.findClass(entrypoint, element.resolveScope)
236+ ? : continue
237+ ClassInheritorsSearch .search(entrypointClass, true )
238+ .mapNotNullTo(variants) {
239+ val shortName = it.name ? : return @mapNotNullTo null
240+ val fqName = it.fullQualifiedName ? : return @mapNotNullTo null
241+ JavaLookupElementBuilder .forClass(it, fqName, true ).withPresentableText(shortName)
242+ }
243+ }
244+ } else if (parts.size >= 2 ) {
245+ val psiFacade = JavaPsiFacade .getInstance(element.project)
246+ val className = parts[0 ].replace(' $' , ' .' )
247+ val clazz = psiFacade.findClass(className, element.resolveScope)
248+ if (clazz != null ) {
249+ clazz.fields.filterTo(variants, ::isValidEntrypointField)
250+ clazz.methods.filterTo(variants, ::isValidEntrypointMethod)
251+ }
252+ }
253+
254+ return variants.toTypedArray()
255+ }
176256 }
177257}
0 commit comments