Skip to content

Commit 39512c8

Browse files
stephan-ghDenWav
authored andcommitted
Add code folding for Mixin injection targets (#238)
1 parent 8145ea7 commit 39512c8

File tree

4 files changed

+193
-0
lines changed

4 files changed

+193
-0
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Minecraft Dev for IntelliJ
3+
*
4+
* https://minecraftdev.org
5+
*
6+
* Copyright (c) 2017 minecraft-dev
7+
*
8+
* MIT License
9+
*/
10+
11+
package com.demonwav.mcdev.platform.mixin.folding
12+
13+
import com.demonwav.mcdev.platform.mixin.MixinModuleType
14+
import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.AT
15+
import com.demonwav.mcdev.platform.mixin.util.MixinMemberReference
16+
import com.demonwav.mcdev.util.constantStringValue
17+
import com.intellij.lang.ASTNode
18+
import com.intellij.lang.folding.CustomFoldingBuilder
19+
import com.intellij.lang.folding.FoldingDescriptor
20+
import com.intellij.openapi.editor.Document
21+
import com.intellij.openapi.util.TextRange
22+
import com.intellij.psi.CommonClassNames
23+
import com.intellij.psi.JavaRecursiveElementWalkingVisitor
24+
import com.intellij.psi.PsiAnnotation
25+
import com.intellij.psi.PsiAnnotationMemberValue
26+
import com.intellij.psi.PsiClassType
27+
import com.intellij.psi.PsiElement
28+
import com.intellij.psi.PsiJavaFile
29+
import com.intellij.psi.PsiMethod
30+
import com.intellij.psi.PsiSubstitutor
31+
import com.intellij.psi.PsiTypeCastExpression
32+
import com.intellij.psi.PsiVariable
33+
import com.intellij.psi.impl.source.tree.ChildRole
34+
import com.intellij.psi.impl.source.tree.CompositeElement
35+
import com.intellij.psi.util.PsiFormatUtil
36+
import com.intellij.psi.util.PsiFormatUtilBase
37+
import com.intellij.psi.util.PsiFormatUtilBase.SHOW_CONTAINING_CLASS
38+
39+
class MixinFoldingBuilder : CustomFoldingBuilder() {
40+
41+
// I'm not dumb
42+
override fun isDumbAware() = false
43+
44+
override fun isRegionCollapsedByDefault(node: ASTNode): Boolean {
45+
val settings = MixinFoldingSettings.instance.state
46+
return when (node.psi) {
47+
is PsiTypeCastExpression -> settings.foldObjectCasts
48+
is PsiAnnotationMemberValue -> settings.foldTargetDescriptors
49+
else -> true
50+
}
51+
}
52+
53+
override fun getLanguagePlaceholderText(node: ASTNode, range: TextRange): String {
54+
val element = node.psi
55+
when (element) {
56+
is PsiTypeCastExpression -> {
57+
val castText = element.castType?.text ?: return node.text
58+
return "($castText)"
59+
}
60+
is PsiAnnotationMemberValue -> {
61+
val value = element.constantStringValue ?: return node.text
62+
val member = MixinMemberReference.parse(value)?.resolveMember(element.project, element.resolveScope) ?: return node.text
63+
return when (member) {
64+
is PsiMethod -> PsiFormatUtil.formatMethod(member, PsiSubstitutor.EMPTY,
65+
PsiFormatUtilBase.SHOW_NAME or PsiFormatUtilBase.SHOW_PARAMETERS or SHOW_CONTAINING_CLASS,
66+
PsiFormatUtilBase.SHOW_TYPE)
67+
is PsiVariable -> PsiFormatUtil.formatVariable(member, PsiFormatUtilBase.SHOW_NAME or SHOW_CONTAINING_CLASS, PsiSubstitutor.EMPTY)
68+
else -> member.presentation?.presentableText ?: node.text
69+
}
70+
}
71+
}
72+
73+
return node.text
74+
}
75+
76+
override fun buildLanguageFoldRegions(descriptors: MutableList<FoldingDescriptor>, root: PsiElement, document: Document, quick: Boolean) {
77+
if (root !is PsiJavaFile || !MixinModuleType.isInModule(root)) {
78+
return
79+
}
80+
81+
root.accept(Visitor(descriptors))
82+
}
83+
84+
private class Visitor(private val descriptors: MutableList<FoldingDescriptor>) : JavaRecursiveElementWalkingVisitor() {
85+
86+
val settings = MixinFoldingSettings.instance.state
87+
88+
override fun visitAnnotation(annotation: PsiAnnotation) {
89+
super.visitAnnotation(annotation)
90+
91+
if (!settings.foldTargetDescriptors) {
92+
return
93+
}
94+
95+
val qualifiedName = annotation.qualifiedName ?: return
96+
if (qualifiedName != AT) {
97+
return
98+
}
99+
100+
val target = annotation.findDeclaredAttributeValue("target") ?: return
101+
descriptors.add(FoldingDescriptor(target, target.textRange))
102+
}
103+
104+
override fun visitTypeCastExpression(expression: PsiTypeCastExpression) {
105+
super.visitTypeCastExpression(expression)
106+
107+
if (!settings.foldObjectCasts) {
108+
return
109+
}
110+
111+
val innerCast = expression.operand as? PsiTypeCastExpression ?: return
112+
if ((innerCast.type as? PsiClassType)?.resolve()?.qualifiedName == CommonClassNames.JAVA_LANG_OBJECT) {
113+
// Fold the two casts
114+
115+
val start = (expression as? CompositeElement)?.findChildByRole(ChildRole.LPARENTH) ?: return
116+
val end = (innerCast as? CompositeElement)?.findChildByRole(ChildRole.RPARENTH) ?: return
117+
118+
descriptors.add(FoldingDescriptor(expression, TextRange(start.startOffset, end.startOffset + end.textLength)))
119+
}
120+
}
121+
122+
}
123+
124+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Minecraft Dev for IntelliJ
3+
*
4+
* https://minecraftdev.org
5+
*
6+
* Copyright (c) 2017 minecraft-dev
7+
*
8+
* MIT License
9+
*/
10+
11+
package com.demonwav.mcdev.platform.mixin.folding
12+
13+
import com.intellij.application.options.editor.CodeFoldingOptionsProvider
14+
import com.intellij.openapi.options.BeanConfigurable
15+
16+
class MixinFoldingOptionsProvider
17+
: BeanConfigurable<MixinFoldingSettings.State>(MixinFoldingSettings.instance.state), CodeFoldingOptionsProvider {
18+
19+
init {
20+
val settings = MixinFoldingSettings.instance
21+
checkBox("Mixin: Target descriptors", { settings.state.foldTargetDescriptors }, { b -> settings.state.foldTargetDescriptors = b })
22+
checkBox("Mixin: Object casts", { settings.state.foldObjectCasts }, { b -> settings.state.foldObjectCasts = b })
23+
}
24+
25+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Minecraft Dev for IntelliJ
3+
*
4+
* https://minecraftdev.org
5+
*
6+
* Copyright (c) 2017 minecraft-dev
7+
*
8+
* MIT License
9+
*/
10+
11+
package com.demonwav.mcdev.platform.mixin.folding
12+
13+
import com.intellij.openapi.components.PersistentStateComponent
14+
import com.intellij.openapi.components.ServiceManager
15+
import com.intellij.openapi.components.State
16+
import com.intellij.openapi.components.Storage
17+
18+
@State(name = "MixinFoldingSettings", storages = arrayOf(Storage("editor.codeinsight.xml")))
19+
class MixinFoldingSettings : PersistentStateComponent<MixinFoldingSettings.State> {
20+
21+
data class State(
22+
var foldTargetDescriptors: Boolean = true,
23+
var foldObjectCasts: Boolean = false
24+
)
25+
26+
private var state = State()
27+
28+
override fun getState(): State = this.state
29+
30+
override fun loadState(state: State) {
31+
this.state = state
32+
}
33+
34+
companion object {
35+
val instance: MixinFoldingSettings
36+
get() = ServiceManager.getService(MixinFoldingSettings::class.java)
37+
}
38+
39+
}

src/main/resources/META-INF/plugin.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,11 @@
177177
<!--region MIXIN-->
178178
<!---->
179179

180+
<!-- Folding -->
181+
<applicationService serviceImplementation="com.demonwav.mcdev.platform.mixin.folding.MixinFoldingSettings" />
182+
<codeFoldingOptionsProvider instance="com.demonwav.mcdev.platform.mixin.folding.MixinFoldingOptionsProvider" />
183+
<lang.foldingBuilder language="JAVA" implementationClass="com.demonwav.mcdev.platform.mixin.folding.MixinFoldingBuilder"/>
184+
180185
<implicitUsageProvider implementation="com.demonwav.mcdev.platform.mixin.insight.MixinImplicitUsageProvider"/>
181186
<deadCode implementation="com.demonwav.mcdev.platform.mixin.insight.MixinEntryPoint"/>
182187

0 commit comments

Comments
 (0)