Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 2d660a2

Browse files
committedSep 6, 2020
Add dialog to automatically add mcdev annotations library if not present
1 parent 95e914a commit 2d660a2

File tree

13 files changed

+433
-63
lines changed

13 files changed

+433
-63
lines changed
 

‎build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ intellij {
109109
version = ideaVersion
110110
// Bundled plugin dependencies
111111
setPlugins(
112-
"java", "maven", "gradle", "Groovy",
112+
"java", "maven", "gradle", "Groovy", "Kotlin",
113113
// needed dependencies for unit tests
114114
"properties", "junit",
115115
// useful to have when running for mods.toml

‎src/main/kotlin/MinecraftSettings.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class MinecraftSettings : PersistentStateComponent<MinecraftSettings.State> {
2424
var isShowEventListenerGutterIcons: Boolean = true,
2525
var isShowChatColorGutterIcons: Boolean = true,
2626
var isShowChatColorUnderlines: Boolean = false,
27+
var isShowSideOnlyGutterIcons: Boolean = true,
2728
var underlineType: MinecraftSettings.UnderlineType = MinecraftSettings.UnderlineType.DOTTED
2829
)
2930

@@ -62,6 +63,12 @@ class MinecraftSettings : PersistentStateComponent<MinecraftSettings.State> {
6263
state.isShowChatColorUnderlines = showChatColorUnderlines
6364
}
6465

66+
var isShowSideOnlyGutterIcons: Boolean
67+
get() = state.isShowSideOnlyGutterIcons
68+
set(showSideOnlyGutterIcons) {
69+
state.isShowSideOnlyGutterIcons = showSideOnlyGutterIcons
70+
}
71+
6572
var underlineType: UnderlineType
6673
get() = state.underlineType
6774
set(underlineType) {

‎src/main/kotlin/sideonly/HardSideOnlyUsageInspection.kt

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010

1111
package com.demonwav.mcdev.sideonly
1212

13+
import com.demonwav.mcdev.util.findModule
14+
import com.demonwav.mcdev.util.runInSmartModeFromReadAction
1315
import com.intellij.codeInspection.ProblemDescriptor
16+
import com.intellij.openapi.command.WriteCommandAction
17+
import com.intellij.openapi.progress.util.ProgressWindow
1418
import com.intellij.openapi.project.Project
1519
import com.intellij.psi.JavaPsiFacade
1620
import com.intellij.psi.PsiAnnotation
@@ -57,19 +61,29 @@ class HardSideOnlyUsageInspection : BaseInspection() {
5761
private class Fix(private val annotation: SmartPsiElementPointer<PsiAnnotation>) : InspectionGadgetsFix() {
5862
override fun doFix(project: Project, descriptor: ProblemDescriptor) {
5963
val annotation = this.annotation.element ?: return
60-
val oldSide = SideOnlyUtil.getAnnotationSide(annotation, SideHardness.HARD)
61-
val newAnnotation = JavaPsiFacade.getElementFactory(project).createAnnotationFromText(
62-
"@${SideOnlyUtil.MCDEV_SIDEONLY_ANNOTATION}(${SideOnlyUtil.MCDEV_SIDE}.$oldSide)",
63-
annotation
64-
)
65-
val createdAnnotation = annotation.replace(newAnnotation)
66-
val codeStyleManager = JavaCodeStyleManager.getInstance(project)
67-
codeStyleManager.shortenClassReferences(createdAnnotation)
68-
createdAnnotation.containingFile?.let { codeStyleManager.optimizeImports(it) }
64+
val module = annotation.findModule() ?: return
65+
if (!SideOnlyUtil.ensureMcdevDependencyPresent(project, module, name, annotation.resolveScope)) {
66+
return
67+
}
68+
project.runInSmartModeFromReadAction(ProgressWindow(true, project)) {
69+
val oldSide = SideOnlyUtil.getAnnotationSide(annotation, SideHardness.HARD)
70+
val newAnnotation = JavaPsiFacade.getElementFactory(project).createAnnotationFromText(
71+
"@${SideOnlyUtil.MCDEV_SIDEONLY_ANNOTATION}(${SideOnlyUtil.MCDEV_SIDE}.$oldSide)",
72+
annotation
73+
)
74+
WriteCommandAction.runWriteCommandAction(project) {
75+
val createdAnnotation = annotation.replace(newAnnotation)
76+
val codeStyleManager = JavaCodeStyleManager.getInstance(project)
77+
codeStyleManager.shortenClassReferences(createdAnnotation)
78+
createdAnnotation.containingFile?.let { codeStyleManager.optimizeImports(it) }
79+
}
80+
}
6981
}
7082

7183
override fun getName() = "Replace with @CheckEnv"
7284

7385
override fun getFamilyName() = name
86+
87+
override fun startInWriteAction() = false
7488
}
7589
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Minecraft Dev for IntelliJ
3+
*
4+
* https://minecraftdev.org
5+
*
6+
* Copyright (c) 2020 minecraft-dev
7+
*
8+
* MIT License
9+
*/
10+
11+
package com.demonwav.mcdev.sideonly
12+
13+
import com.demonwav.mcdev.util.findModule
14+
import com.intellij.codeInsight.FileModificationService
15+
import com.intellij.codeInsight.intention.impl.BaseIntentionAction
16+
import com.intellij.lang.java.JavaLanguage
17+
import com.intellij.openapi.command.WriteCommandAction
18+
import com.intellij.openapi.editor.Editor
19+
import com.intellij.openapi.project.DumbService
20+
import com.intellij.openapi.project.Project
21+
import com.intellij.psi.JavaPsiFacade
22+
import com.intellij.psi.PsiCompiledElement
23+
import com.intellij.psi.PsiFile
24+
import com.intellij.psi.PsiModifierListOwner
25+
import com.intellij.psi.codeStyle.JavaCodeStyleManager
26+
import com.intellij.psi.util.PsiUtilCore
27+
28+
class MakeInferredMcdevAnnotationExplicit : BaseIntentionAction() {
29+
override fun getFamilyName() = "Make Inferred MinecraftDev Annotations Explicit"
30+
31+
override fun getText() = "Make Inferred MinecraftDev Annotations Explicit"
32+
33+
override fun isAvailable(project: Project, editor: Editor, file: PsiFile): Boolean {
34+
val leaf = file.findElementAt(editor.caretModel.offset) ?: return false
35+
val owner = leaf.parent as? PsiModifierListOwner
36+
return isAvailable(file, owner)
37+
}
38+
39+
fun isAvailable(file: PsiFile, owner: PsiModifierListOwner?): Boolean {
40+
if (owner != null &&
41+
owner.language.isKindOf(JavaLanguage.INSTANCE) &&
42+
isWritable(owner) &&
43+
file.findModule() != null
44+
) {
45+
val annotation = SideOnlyUtil.getInferredAnnotationOnly(owner, SideHardness.HARD)
46+
?: SideOnlyUtil.getInferredAnnotationOnly(owner, SideHardness.SOFT)
47+
if (annotation != null) {
48+
text = "Insert '@CheckEnv(Env.${annotation.side})'"
49+
return true
50+
}
51+
}
52+
return false
53+
}
54+
55+
private fun isWritable(owner: PsiModifierListOwner): Boolean {
56+
if (owner is PsiCompiledElement) return false
57+
val vFile = PsiUtilCore.getVirtualFile(owner)
58+
return vFile != null && vFile.isInLocalFileSystem
59+
}
60+
61+
override fun invoke(project: Project, editor: Editor, file: PsiFile) {
62+
val leaf = file.findElementAt(editor.caretModel.offset) ?: return
63+
val owner = leaf.parent as? PsiModifierListOwner ?: return
64+
makeAnnotationExplicit(project, file, owner)
65+
}
66+
67+
fun makeAnnotationExplicit(project: Project, file: PsiFile, owner: PsiModifierListOwner) {
68+
val modifierList = owner.modifierList ?: return
69+
val module = file.findModule() ?: return
70+
if (!SideOnlyUtil.ensureMcdevDependencyPresent(project, module, familyName, file.resolveScope)) {
71+
return
72+
}
73+
if (!FileModificationService.getInstance().preparePsiElementForWrite(owner)) return
74+
val facade = JavaPsiFacade.getInstance(project)
75+
val inferredSide = SideOnlyUtil.getInferredAnnotationOnly(owner, SideHardness.HARD)
76+
?: SideOnlyUtil.getInferredAnnotationOnly(owner, SideHardness.SOFT) ?: return
77+
val inferred = facade.elementFactory.createAnnotationFromText(
78+
"@${SideOnlyUtil.MCDEV_SIDEONLY_ANNOTATION}(${SideOnlyUtil.MCDEV_SIDE}.${inferredSide.side})",
79+
owner
80+
)
81+
WriteCommandAction.runWriteCommandAction(project) {
82+
DumbService.getInstance(project).withAlternativeResolveEnabled {
83+
JavaCodeStyleManager.getInstance(project)
84+
.shortenClassReferences(modifierList.addAfter(inferred, null))
85+
}
86+
}
87+
}
88+
89+
override fun startInWriteAction() = false
90+
}

‎src/main/kotlin/sideonly/SideOnlyInferredAnnotationProvider.kt

Lines changed: 0 additions & 42 deletions
This file was deleted.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Minecraft Dev for IntelliJ
3+
*
4+
* https://minecraftdev.org
5+
*
6+
* Copyright (c) 2020 minecraft-dev
7+
*
8+
* MIT License
9+
*/
10+
11+
package com.demonwav.mcdev.sideonly
12+
13+
import com.demonwav.mcdev.MinecraftSettings
14+
import com.intellij.codeInsight.daemon.LineMarkerInfo
15+
import com.intellij.codeInsight.daemon.LineMarkerProvider
16+
import com.intellij.icons.AllIcons
17+
import com.intellij.ide.actions.ApplyIntentionAction
18+
import com.intellij.openapi.actionSystem.DefaultActionGroup
19+
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
20+
import com.intellij.openapi.editor.Editor
21+
import com.intellij.openapi.editor.markup.GutterIconRenderer
22+
import com.intellij.openapi.fileEditor.FileEditorManager
23+
import com.intellij.openapi.project.Project
24+
import com.intellij.openapi.ui.popup.JBPopup
25+
import com.intellij.openapi.ui.popup.JBPopupFactory
26+
import com.intellij.psi.PsiDocumentManager
27+
import com.intellij.psi.PsiElement
28+
import com.intellij.psi.PsiFile
29+
import com.intellij.psi.PsiIdentifier
30+
import com.intellij.psi.PsiModifierListOwner
31+
import com.intellij.psi.util.PsiUtilCore
32+
import com.intellij.ui.awt.RelativePoint
33+
import java.awt.event.MouseEvent
34+
35+
class SideOnlyLineMarkerProvider : LineMarkerProvider {
36+
override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? {
37+
if (!MinecraftSettings.instance.isShowSideOnlyGutterIcons) {
38+
return null
39+
}
40+
if (element !is PsiIdentifier) {
41+
return null
42+
}
43+
val listOwner = element.parent as? PsiModifierListOwner ?: return null
44+
val implicitHard = SideOnlyUtil.getInferredAnnotationOnly(listOwner, SideHardness.HARD)
45+
val implicitSoft = SideOnlyUtil.getInferredAnnotationOnly(listOwner, SideHardness.SOFT)
46+
val implicitAnnotation = implicitHard ?: implicitSoft ?: return null
47+
48+
var message = "Implicit "
49+
message += if (implicitHard == null) {
50+
"soft"
51+
} else {
52+
"hard"
53+
}
54+
message += "-sided annotation available: " + implicitAnnotation.reason
55+
return LineMarkerInfo(
56+
element,
57+
element.textRange,
58+
AllIcons.Gutter.ExtAnnotation,
59+
{ message },
60+
this::navigate,
61+
GutterIconRenderer.Alignment.RIGHT
62+
)
63+
}
64+
65+
private fun navigate(event: MouseEvent, element: PsiElement) {
66+
val listOwner = element.parent
67+
val containingFile = listOwner.containingFile
68+
val virtualFile = PsiUtilCore.getVirtualFile(listOwner)
69+
70+
if (virtualFile != null && containingFile != null) {
71+
val project = listOwner.project
72+
val editor = FileEditorManager.getInstance(project).selectedTextEditor
73+
if (editor != null) {
74+
editor.caretModel.moveToOffset(element.textOffset)
75+
val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document)
76+
if (file != null && virtualFile == file.virtualFile) {
77+
val popup = createActionGroupPopup(containingFile, project, editor)
78+
popup?.show(RelativePoint(event))
79+
}
80+
}
81+
}
82+
}
83+
84+
private fun createActionGroupPopup(file: PsiFile, project: Project, editor: Editor): JBPopup? {
85+
val intention = MakeInferredMcdevAnnotationExplicit()
86+
val action = ApplyIntentionAction(intention, intention.text, editor, file)
87+
val group = DefaultActionGroup(action)
88+
val context = SimpleDataContext.getProjectContext(null)
89+
return JBPopupFactory.getInstance()
90+
.createActionGroupPopup(null, group, context, JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, true)
91+
}
92+
}

‎src/main/kotlin/sideonly/SideOnlyUtil.kt

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,18 @@ import com.demonwav.mcdev.platform.fabric.util.FabricConstants
1616
import com.demonwav.mcdev.platform.forge.util.ForgeConstants
1717
import com.demonwav.mcdev.platform.mixin.util.isMixin
1818
import com.demonwav.mcdev.platform.mixin.util.mixinTargets
19+
import com.demonwav.mcdev.util.SemanticVersion
20+
import com.demonwav.mcdev.util.addGradleDependency
1921
import com.demonwav.mcdev.util.findModule
2022
import com.demonwav.mcdev.util.packageName
2123
import com.intellij.codeInspection.ProblemsHolder
2224
import com.intellij.codeInspection.dataFlow.StandardDataFlowRunner
2325
import com.intellij.json.psi.JsonFile
2426
import com.intellij.json.psi.JsonObject
2527
import com.intellij.json.psi.JsonStringLiteral
28+
import com.intellij.openapi.module.Module
29+
import com.intellij.openapi.project.Project
30+
import com.intellij.openapi.ui.Messages
2631
import com.intellij.psi.JavaPsiFacade
2732
import com.intellij.psi.PsiAnnotation
2833
import com.intellij.psi.PsiArrayType
@@ -43,13 +48,63 @@ import com.intellij.psi.PsiPackage
4348
import com.intellij.psi.PsiReferenceExpression
4449
import com.intellij.psi.PsiResolveHelper
4550
import com.intellij.psi.PsiType
51+
import com.intellij.psi.search.GlobalSearchScope
4652
import com.intellij.psi.util.parentOfType
4753

4854
object SideOnlyUtil {
4955
const val MCDEV_SIDEONLY_ANNOTATION = "com.demonwav.mcdev.annotations.CheckEnv"
5056
const val MCDEV_SIDE = "com.demonwav.mcdev.annotations.Env"
5157

52-
internal fun getAnnotationSide(annotation: PsiAnnotation, hardness: SideHardness): Side {
58+
fun ensureMcdevDependencyPresent(
59+
project: Project,
60+
module: Module,
61+
title: String,
62+
scope: GlobalSearchScope
63+
): Boolean {
64+
if (JavaPsiFacade.getInstance(project).findClass(MCDEV_SIDEONLY_ANNOTATION, scope) != null) {
65+
return true
66+
}
67+
68+
if (addMcdevAnnotationsDependency(project, module, title)) {
69+
return true
70+
}
71+
72+
return false
73+
}
74+
75+
private fun addMcdevAnnotationsDependency(project: Project, module: Module, title: String): Boolean {
76+
val message = "MinecraftDev annotations library is missing. " +
77+
"Without the library, MinecraftDev cannot run the analysis.\n" +
78+
"Would you like to add it to the project buildscript automatically?"
79+
if (Messages.showOkCancelDialog(
80+
project,
81+
message,
82+
title,
83+
Messages.OK_BUTTON,
84+
Messages.CANCEL_BUTTON,
85+
Messages.getErrorIcon()
86+
) == Messages.OK
87+
) {
88+
// TODO: fetch the latest version
89+
if (addGradleDependency(
90+
project,
91+
module,
92+
"com.demonwav.mcdev",
93+
"annotations",
94+
SemanticVersion.release(1, 0)
95+
)
96+
) {
97+
return true
98+
} else {
99+
val errorMessage = "Failed to add MinecraftDev annotations library automatically. " +
100+
"Please add it to your buildscript manually."
101+
Messages.showMessageDialog(project, errorMessage, title, Messages.getErrorIcon())
102+
}
103+
}
104+
return false
105+
}
106+
107+
fun getAnnotationSide(annotation: PsiAnnotation, hardness: SideHardness): Side {
53108
var isSideAnnotation = false
54109

55110
if (hardness != SideHardness.HARD) {
@@ -83,7 +138,7 @@ object SideOnlyUtil {
83138
}
84139
}
85140

86-
internal fun getExplicitAnnotation(element: PsiModifierListOwner, hardness: SideHardness): SideInstance? {
141+
fun getExplicitAnnotation(element: PsiModifierListOwner, hardness: SideHardness): SideInstance? {
87142
return element.annotations.asSequence()
88143
.map { it to getAnnotationSide(it, hardness) }
89144
.firstOrNull { it.second != Side.BOTH }
@@ -98,19 +153,23 @@ object SideOnlyUtil {
98153
}
99154
}
100155

101-
internal fun getExplicitOrInferredAnnotation(element: PsiModifierListOwner, hardness: SideHardness): SideInstance? {
102-
val explicitAnnotation = getExplicitAnnotation(element, hardness)
103-
if (explicitAnnotation != null) {
104-
return explicitAnnotation
105-
}
106-
156+
private fun getInferredAnnotation(element: PsiModifierListOwner, hardness: SideHardness): SideInstance? {
107157
return when (element) {
108158
is PsiClass -> getInferredClassAnnotation(element, hardness)
109159
is PsiMethod -> getInferredMethodAnnotation(element, hardness)
110160
else -> null
111161
}
112162
}
113163

164+
fun getExplicitOrInferredAnnotation(element: PsiModifierListOwner, hardness: SideHardness): SideInstance? {
165+
return getExplicitAnnotation(element, hardness) ?: getInferredAnnotation(element, hardness)
166+
}
167+
168+
fun getInferredAnnotationOnly(element: PsiModifierListOwner, hardness: SideHardness): SideInstance? {
169+
if (getExplicitAnnotation(element, hardness) != null) return null
170+
return getInferredAnnotation(element, hardness)
171+
}
172+
114173
private fun getInferredClassAnnotation(cls: PsiClass, hardness: SideHardness): SideInstance? {
115174
// If mixing into a client class, the mixin is also clientside
116175
if (cls.isMixin) {

‎src/main/kotlin/util/gradle-util.kt

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,35 @@
1111
package com.demonwav.mcdev.util
1212

1313
import com.intellij.execution.executors.DefaultRunExecutor
14+
import com.intellij.openapi.command.WriteCommandAction
15+
import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder
1416
import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings
1517
import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode
1618
import com.intellij.openapi.externalSystem.task.TaskCallback
1719
import com.intellij.openapi.externalSystem.util.ExternalSystemUtil
20+
import com.intellij.openapi.fileEditor.FileDocumentManager
21+
import com.intellij.openapi.module.Module
1822
import com.intellij.openapi.project.Project
23+
import com.intellij.openapi.project.rootManager
24+
import com.intellij.openapi.roots.ProjectRootManager
25+
import com.intellij.psi.PsiManager
26+
import com.intellij.psi.codeStyle.CodeStyleManager
1927
import java.nio.file.Path
2028
import java.util.concurrent.locks.Condition
2129
import java.util.concurrent.locks.ReentrantLock
2230
import kotlin.concurrent.withLock
31+
import org.jetbrains.kotlin.psi.KtCallElement
32+
import org.jetbrains.kotlin.psi.KtFile
33+
import org.jetbrains.kotlin.psi.KtLambdaExpression
34+
import org.jetbrains.kotlin.psi.KtPsiFactory
35+
import org.jetbrains.kotlin.psi.KtScriptInitializer
36+
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
2337
import org.jetbrains.plugins.gradle.util.GradleConstants
38+
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile
39+
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory
40+
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall
41+
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression
42+
import org.jetbrains.plugins.groovy.lang.psi.util.childrenOfType
2443

2544
inline fun runGradleTask(
2645
project: Project,
@@ -51,6 +70,81 @@ inline fun runGradleTask(
5170
}
5271
}
5372

73+
fun addGradleDependency(
74+
project: Project,
75+
module: Module,
76+
group: String,
77+
artifact: String,
78+
version: SemanticVersion
79+
): Boolean {
80+
val buildGradleFile = module.rootManager.contentRoots.mapFirstNotNull { moduleFile ->
81+
moduleFile.findChild("build.gradle") ?: moduleFile.findChild("build.gradle.kts")
82+
} ?: ProjectRootManager.getInstance(project).contentRoots.mapFirstNotNull { projectFile ->
83+
projectFile.findChild("build.gradle") ?: projectFile.findChild("build.gradle.kts")
84+
} ?: return false
85+
val psiFile = PsiManager.getInstance(project).findFile(buildGradleFile) ?: return false
86+
val success = when (psiFile) {
87+
is GroovyFile -> addGroovyGradleDependency(project, psiFile, group, artifact, version)
88+
is KtFile -> addKotlinGradleDependency(project, psiFile, group, artifact, version)
89+
else -> false
90+
}
91+
if (success) {
92+
FileDocumentManager.getInstance().saveAllDocuments()
93+
ExternalSystemUtil.refreshProjects(
94+
ImportSpecBuilder(project, GradleConstants.SYSTEM_ID).use(ProgressExecutionMode.MODAL_SYNC)
95+
)
96+
}
97+
return success
98+
}
99+
100+
private fun addGroovyGradleDependency(
101+
project: Project,
102+
file: GroovyFile,
103+
group: String,
104+
artifact: String,
105+
version: SemanticVersion
106+
): Boolean {
107+
val dependenciesCall = file.childrenOfType<GrMethodCall>().firstOrNull { methodCall ->
108+
(methodCall.invokedExpression as? GrReferenceExpression)?.referenceName == "dependencies"
109+
} ?: return false
110+
val dependenciesClosure = dependenciesCall.closureArguments.firstOrNull() ?: return false
111+
val toInsert = GroovyPsiElementFactory.getInstance(project).createStatementFromText(
112+
"implementation '$group:$artifact:$version'",
113+
dependenciesClosure
114+
)
115+
WriteCommandAction.runWriteCommandAction(project) {
116+
dependenciesClosure.addStatementBefore(toInsert, null)
117+
CodeStyleManager.getInstance(project).reformat(dependenciesClosure)
118+
}
119+
return true
120+
}
121+
122+
private fun addKotlinGradleDependency(
123+
project: Project,
124+
file: KtFile,
125+
group: String,
126+
artifact: String,
127+
version: SemanticVersion
128+
): Boolean {
129+
val scriptBlock = file.script?.blockExpression ?: return false
130+
val dependenciesCall = scriptBlock.statements.asSequence()
131+
.mapNotNull { it as? KtScriptInitializer }
132+
.mapNotNull { it.body as? KtCallElement }
133+
.firstOrNull { (it.calleeExpression as? KtSimpleNameExpression)?.getReferencedName() == "dependencies" }
134+
?: return false
135+
val dependenciesLambda = dependenciesCall.lambdaArguments.firstOrNull()?.getArgumentExpression()
136+
as? KtLambdaExpression ?: return false
137+
val dependenciesBlock = dependenciesLambda.functionLiteral.bodyBlockExpression ?: return false
138+
val factory = KtPsiFactory(project)
139+
val toInsert = factory.createExpression("implementation(\"$group:$artifact:$version\")")
140+
WriteCommandAction.runWriteCommandAction(project) {
141+
dependenciesBlock.addBefore(factory.createNewLine(), dependenciesBlock.rBrace)
142+
dependenciesBlock.addBefore(toInsert, dependenciesBlock.rBrace)
143+
CodeStyleManager.getInstance(project).reformat(dependenciesBlock)
144+
}
145+
return true
146+
}
147+
54148
class GradleCallback(private val lock: ReentrantLock, private val condition: Condition) : TaskCallback {
55149

56150
override fun onSuccess() = resume()

‎src/main/kotlin/util/utils.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import com.intellij.openapi.fileEditor.FileDocumentManager
2020
import com.intellij.openapi.module.Module
2121
import com.intellij.openapi.module.ModuleManager
2222
import com.intellij.openapi.progress.ProcessCanceledException
23+
import com.intellij.openapi.progress.ProgressIndicator
24+
import com.intellij.openapi.progress.ProgressManager
2325
import com.intellij.openapi.project.DumbService
2426
import com.intellij.openapi.project.Project
2527
import com.intellij.openapi.project.ProjectManager
@@ -67,6 +69,29 @@ inline fun <T : Any?> Project.runWriteTaskInSmartMode(crossinline func: () -> T)
6769
return ref.get()
6870
}
6971

72+
fun Project.runInSmartModeFromReadAction(
73+
progressIndicator: ProgressIndicator? = null,
74+
func: () -> Unit
75+
) {
76+
val dumbService = DumbService.getInstance(this)
77+
if (dumbService.isDumb) {
78+
ApplicationManager.getApplication().executeOnPooledThread {
79+
ProgressManager.getInstance().runProcess(
80+
{
81+
ProgressManager.progress("Indexing")
82+
dumbService.runReadActionInSmartMode {
83+
ProgressManager.checkCanceled()
84+
invokeLater(func)
85+
}
86+
},
87+
progressIndicator
88+
)
89+
}
90+
} else {
91+
func()
92+
}
93+
}
94+
7095
fun <T : Any?> invokeAndWait(func: () -> T): T {
7196
val ref = Ref<T>()
7297
ApplicationManager.getApplication().invokeAndWait({ ref.set(func()) }, ModalityState.defaultModalityState())

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<depends>org.jetbrains.idea.maven</depends>
1515
<depends>com.intellij.gradle</depends>
1616
<depends>org.intellij.groovy</depends>
17+
<depends>org.jetbrains.kotlin</depends>
1718

1819
<id>com.demonwav.minecraft-dev</id>
1920
<name>Minecraft Development</name>
@@ -57,10 +58,18 @@
5758
<!-- File templates are for project creations -->
5859
<fileTemplateGroup implementation="com.demonwav.mcdev.util.MinecraftTemplates"/>
5960

61+
<!-- Project-independent intention actions -->
62+
<intentionAction>
63+
<className>com.demonwav.mcdev.sideonly.MakeInferredMcdevAnnotationExplicit</className>
64+
<category>Minecraft</category>
65+
<descriptionDirectoryName>MakeInferredMcdevAnnotationExplicit</descriptionDirectoryName>
66+
</intentionAction>
67+
6068
<!-- Project-independent Line Marker Providers -->
6169
<codeInsight.lineMarkerProvider language="" implementationClass="com.demonwav.mcdev.insight.ListenerLineMarkerProvider"/>
6270
<codeInsight.lineMarkerProvider language="" implementationClass="com.demonwav.mcdev.insight.ColorLineMarkerProvider"/>
6371
<codeInsight.lineMarkerProvider language="JAVA" implementationClass="com.demonwav.mcdev.insight.PluginLineMarkerProvider"/>
72+
<codeInsight.lineMarkerProvider language="JAVA" implementationClass="com.demonwav.mcdev.sideonly.SideOnlyLineMarkerProvider"/>
6473

6574
<!-- Project-independent Annotators-->
6675
<annotator language="JAVA" implementationClass="com.demonwav.mcdev.insight.ListenerEventAnnotator"/>
@@ -83,9 +92,6 @@
8392
parentId="Settings.Minecraft"
8493
instance="com.demonwav.mcdev.translations.sorting.TranslationTemplateConfigurable"/>
8594

86-
<!-- Project-independent Inferred Annotation Providers -->
87-
<lang.inferredAnnotationProvider implementation="com.demonwav.mcdev.sideonly.SideOnlyInferredAnnotationProvider" />
88-
8995
<!-- NBT -->
9096
<fileType name="NBT" implementationClass="com.demonwav.mcdev.nbt.filetype.NbtFileType" fieldName="INSTANCE" extensions="nbt" />
9197
<fileTypeDetector implementation="com.demonwav.mcdev.nbt.filetype.NbtFileTypeDetector"/>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import com.demonwav.mcdev.annotations.CheckEnv;
2+
import com.demonwav.mcdev.annotations.Env;
3+
4+
<spot>@CheckEnv(Env.CLIENT)</spot>
5+
class A extends ClientOnlyClass {
6+
// stuff
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class A extends ClientOnlyClass {
2+
// stuff
3+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!--
2+
Minecraft Dev for IntelliJ
3+
4+
https://minecraftdev.org
5+
6+
Copyright (c) 2020 minecraft-dev
7+
8+
MIT License
9+
-->
10+
11+
<html>
12+
<body>
13+
This intention adds annotations inferred by MinecraftDev (@CheckEnv) to the code explicitly.
14+
</body>
15+
</html>

0 commit comments

Comments
 (0)
Please sign in to comment.