Skip to content

Commit 2d660a2

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

13 files changed

+433
-63
lines changed

build.gradle.kts

+1-1
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

+7
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

+23-9
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
}
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

-42
This file was deleted.
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+
}

0 commit comments

Comments
 (0)