diff --git a/build.gradle.kts b/build.gradle.kts index e2df6cb4a..95485abf2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -109,7 +109,7 @@ intellij { version = ideaVersion // Bundled plugin dependencies setPlugins( - "java", "maven", "gradle", "Groovy", + "java", "maven", "gradle", "Groovy", "Kotlin", // needed dependencies for unit tests "properties", "junit", // useful to have when running for mods.toml diff --git a/src/main/kotlin/MinecraftSettings.kt b/src/main/kotlin/MinecraftSettings.kt index 91d2c0c75..64575cdce 100644 --- a/src/main/kotlin/MinecraftSettings.kt +++ b/src/main/kotlin/MinecraftSettings.kt @@ -24,6 +24,7 @@ class MinecraftSettings : PersistentStateComponent { var isShowEventListenerGutterIcons: Boolean = true, var isShowChatColorGutterIcons: Boolean = true, var isShowChatColorUnderlines: Boolean = false, + var isShowSideOnlyGutterIcons: Boolean = true, var underlineType: MinecraftSettings.UnderlineType = MinecraftSettings.UnderlineType.DOTTED ) @@ -62,6 +63,12 @@ class MinecraftSettings : PersistentStateComponent { state.isShowChatColorUnderlines = showChatColorUnderlines } + var isShowSideOnlyGutterIcons: Boolean + get() = state.isShowSideOnlyGutterIcons + set(showSideOnlyGutterIcons) { + state.isShowSideOnlyGutterIcons = showSideOnlyGutterIcons + } + var underlineType: UnderlineType get() = state.underlineType set(underlineType) { diff --git a/src/main/kotlin/sideonly/HardSideOnlyUsageInspection.kt b/src/main/kotlin/sideonly/HardSideOnlyUsageInspection.kt index b97a7def8..fc7c8f7e9 100644 --- a/src/main/kotlin/sideonly/HardSideOnlyUsageInspection.kt +++ b/src/main/kotlin/sideonly/HardSideOnlyUsageInspection.kt @@ -10,7 +10,11 @@ package com.demonwav.mcdev.sideonly +import com.demonwav.mcdev.util.findModule +import com.demonwav.mcdev.util.runInSmartModeFromReadAction import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.progress.util.ProgressWindow import com.intellij.openapi.project.Project import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation @@ -57,19 +61,29 @@ class HardSideOnlyUsageInspection : BaseInspection() { private class Fix(private val annotation: SmartPsiElementPointer) : InspectionGadgetsFix() { override fun doFix(project: Project, descriptor: ProblemDescriptor) { val annotation = this.annotation.element ?: return - val oldSide = SideOnlyUtil.getAnnotationSide(annotation, SideHardness.HARD) - val newAnnotation = JavaPsiFacade.getElementFactory(project).createAnnotationFromText( - "@${SideOnlyUtil.MCDEV_SIDEONLY_ANNOTATION}(${SideOnlyUtil.MCDEV_SIDE}.$oldSide)", - annotation - ) - val createdAnnotation = annotation.replace(newAnnotation) - val codeStyleManager = JavaCodeStyleManager.getInstance(project) - codeStyleManager.shortenClassReferences(createdAnnotation) - createdAnnotation.containingFile?.let { codeStyleManager.optimizeImports(it) } + val module = annotation.findModule() ?: return + if (!SideOnlyUtil.ensureMcdevDependencyPresent(project, module, name, annotation.resolveScope)) { + return + } + project.runInSmartModeFromReadAction(ProgressWindow(true, project)) { + val oldSide = SideOnlyUtil.getAnnotationSide(annotation, SideHardness.HARD) + val newAnnotation = JavaPsiFacade.getElementFactory(project).createAnnotationFromText( + "@${SideOnlyUtil.MCDEV_SIDEONLY_ANNOTATION}(${SideOnlyUtil.MCDEV_SIDE}.$oldSide)", + annotation + ) + WriteCommandAction.runWriteCommandAction(project) { + val createdAnnotation = annotation.replace(newAnnotation) + val codeStyleManager = JavaCodeStyleManager.getInstance(project) + codeStyleManager.shortenClassReferences(createdAnnotation) + createdAnnotation.containingFile?.let { codeStyleManager.optimizeImports(it) } + } + } } override fun getName() = "Replace with @CheckEnv" override fun getFamilyName() = name + + override fun startInWriteAction() = false } } diff --git a/src/main/kotlin/sideonly/MakeInferredMcdevAnnotationExplicit.kt b/src/main/kotlin/sideonly/MakeInferredMcdevAnnotationExplicit.kt new file mode 100644 index 000000000..bfe824335 --- /dev/null +++ b/src/main/kotlin/sideonly/MakeInferredMcdevAnnotationExplicit.kt @@ -0,0 +1,90 @@ +/* + * Minecraft Dev for IntelliJ + * + * https://minecraftdev.org + * + * Copyright (c) 2020 minecraft-dev + * + * MIT License + */ + +package com.demonwav.mcdev.sideonly + +import com.demonwav.mcdev.util.findModule +import com.intellij.codeInsight.FileModificationService +import com.intellij.codeInsight.intention.impl.BaseIntentionAction +import com.intellij.lang.java.JavaLanguage +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.DumbService +import com.intellij.openapi.project.Project +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiCompiledElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiModifierListOwner +import com.intellij.psi.codeStyle.JavaCodeStyleManager +import com.intellij.psi.util.PsiUtilCore + +class MakeInferredMcdevAnnotationExplicit : BaseIntentionAction() { + override fun getFamilyName() = "Make Inferred MinecraftDev Annotations Explicit" + + override fun getText() = "Make Inferred MinecraftDev Annotations Explicit" + + override fun isAvailable(project: Project, editor: Editor, file: PsiFile): Boolean { + val leaf = file.findElementAt(editor.caretModel.offset) ?: return false + val owner = leaf.parent as? PsiModifierListOwner + return isAvailable(file, owner) + } + + fun isAvailable(file: PsiFile, owner: PsiModifierListOwner?): Boolean { + if (owner != null && + owner.language.isKindOf(JavaLanguage.INSTANCE) && + isWritable(owner) && + file.findModule() != null + ) { + val annotation = SideOnlyUtil.getInferredAnnotationOnly(owner, SideHardness.HARD) + ?: SideOnlyUtil.getInferredAnnotationOnly(owner, SideHardness.SOFT) + if (annotation != null) { + text = "Insert '@CheckEnv(Env.${annotation.side})'" + return true + } + } + return false + } + + private fun isWritable(owner: PsiModifierListOwner): Boolean { + if (owner is PsiCompiledElement) return false + val vFile = PsiUtilCore.getVirtualFile(owner) + return vFile != null && vFile.isInLocalFileSystem + } + + override fun invoke(project: Project, editor: Editor, file: PsiFile) { + val leaf = file.findElementAt(editor.caretModel.offset) ?: return + val owner = leaf.parent as? PsiModifierListOwner ?: return + makeAnnotationExplicit(project, file, owner) + } + + fun makeAnnotationExplicit(project: Project, file: PsiFile, owner: PsiModifierListOwner) { + val modifierList = owner.modifierList ?: return + val module = file.findModule() ?: return + if (!SideOnlyUtil.ensureMcdevDependencyPresent(project, module, familyName, file.resolveScope)) { + return + } + if (!FileModificationService.getInstance().preparePsiElementForWrite(owner)) return + val facade = JavaPsiFacade.getInstance(project) + val inferredSide = SideOnlyUtil.getInferredAnnotationOnly(owner, SideHardness.HARD) + ?: SideOnlyUtil.getInferredAnnotationOnly(owner, SideHardness.SOFT) ?: return + val inferred = facade.elementFactory.createAnnotationFromText( + "@${SideOnlyUtil.MCDEV_SIDEONLY_ANNOTATION}(${SideOnlyUtil.MCDEV_SIDE}.${inferredSide.side})", + owner + ) + WriteCommandAction.runWriteCommandAction(project) { + DumbService.getInstance(project).withAlternativeResolveEnabled { + JavaCodeStyleManager.getInstance(project) + .shortenClassReferences(modifierList.addAfter(inferred, null)) + } + } + } + + override fun startInWriteAction() = false +} diff --git a/src/main/kotlin/sideonly/SideOnlyInferredAnnotationProvider.kt b/src/main/kotlin/sideonly/SideOnlyInferredAnnotationProvider.kt deleted file mode 100644 index 2e525e451..000000000 --- a/src/main/kotlin/sideonly/SideOnlyInferredAnnotationProvider.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Minecraft Dev for IntelliJ - * - * https://minecraftdev.org - * - * Copyright (c) 2020 minecraft-dev - * - * MIT License - */ - -package com.demonwav.mcdev.sideonly - -import com.intellij.codeInsight.InferredAnnotationProvider -import com.intellij.psi.JavaPsiFacade -import com.intellij.psi.PsiAnnotation -import com.intellij.psi.PsiModifierListOwner - -class SideOnlyInferredAnnotationProvider : InferredAnnotationProvider { - - override fun findInferredAnnotation(listOwner: PsiModifierListOwner, annotationFQN: String): PsiAnnotation? { - if (annotationFQN != SideOnlyUtil.MCDEV_SIDEONLY_ANNOTATION) { - return null - } - if (SideOnlyUtil.getExplicitAnnotation(listOwner, SideHardness.EITHER) != null) { - return null - } - val inferredAnnotation = SideOnlyUtil.getExplicitOrInferredAnnotation(listOwner, SideHardness.EITHER) - ?: return null - val annotationText = - "@${SideOnlyUtil.MCDEV_SIDEONLY_ANNOTATION}(${SideOnlyUtil.MCDEV_SIDE}.${inferredAnnotation.side})" - return JavaPsiFacade.getElementFactory(listOwner.project).createAnnotationFromText(annotationText, listOwner) - } - - override fun findInferredAnnotations(listOwner: PsiModifierListOwner): MutableList { - val annotation = findInferredAnnotation(listOwner, SideOnlyUtil.MCDEV_SIDEONLY_ANNOTATION) - return if (annotation == null) { - mutableListOf() - } else { - mutableListOf(annotation) - } - } -} diff --git a/src/main/kotlin/sideonly/SideOnlyLineMarkerProvider.kt b/src/main/kotlin/sideonly/SideOnlyLineMarkerProvider.kt new file mode 100644 index 000000000..280ad93e9 --- /dev/null +++ b/src/main/kotlin/sideonly/SideOnlyLineMarkerProvider.kt @@ -0,0 +1,92 @@ +/* + * Minecraft Dev for IntelliJ + * + * https://minecraftdev.org + * + * Copyright (c) 2020 minecraft-dev + * + * MIT License + */ + +package com.demonwav.mcdev.sideonly + +import com.demonwav.mcdev.MinecraftSettings +import com.intellij.codeInsight.daemon.LineMarkerInfo +import com.intellij.codeInsight.daemon.LineMarkerProvider +import com.intellij.icons.AllIcons +import com.intellij.ide.actions.ApplyIntentionAction +import com.intellij.openapi.actionSystem.DefaultActionGroup +import com.intellij.openapi.actionSystem.impl.SimpleDataContext +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.markup.GutterIconRenderer +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.popup.JBPopup +import com.intellij.openapi.ui.popup.JBPopupFactory +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiIdentifier +import com.intellij.psi.PsiModifierListOwner +import com.intellij.psi.util.PsiUtilCore +import com.intellij.ui.awt.RelativePoint +import java.awt.event.MouseEvent + +class SideOnlyLineMarkerProvider : LineMarkerProvider { + override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? { + if (!MinecraftSettings.instance.isShowSideOnlyGutterIcons) { + return null + } + if (element !is PsiIdentifier) { + return null + } + val listOwner = element.parent as? PsiModifierListOwner ?: return null + val implicitHard = SideOnlyUtil.getInferredAnnotationOnly(listOwner, SideHardness.HARD) + val implicitSoft = SideOnlyUtil.getInferredAnnotationOnly(listOwner, SideHardness.SOFT) + val implicitAnnotation = implicitHard ?: implicitSoft ?: return null + + var message = "Implicit " + message += if (implicitHard == null) { + "soft" + } else { + "hard" + } + message += "-sided annotation available: " + implicitAnnotation.reason + return LineMarkerInfo( + element, + element.textRange, + AllIcons.Gutter.ExtAnnotation, + { message }, + this::navigate, + GutterIconRenderer.Alignment.RIGHT + ) + } + + private fun navigate(event: MouseEvent, element: PsiElement) { + val listOwner = element.parent + val containingFile = listOwner.containingFile + val virtualFile = PsiUtilCore.getVirtualFile(listOwner) + + if (virtualFile != null && containingFile != null) { + val project = listOwner.project + val editor = FileEditorManager.getInstance(project).selectedTextEditor + if (editor != null) { + editor.caretModel.moveToOffset(element.textOffset) + val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) + if (file != null && virtualFile == file.virtualFile) { + val popup = createActionGroupPopup(containingFile, project, editor) + popup?.show(RelativePoint(event)) + } + } + } + } + + private fun createActionGroupPopup(file: PsiFile, project: Project, editor: Editor): JBPopup? { + val intention = MakeInferredMcdevAnnotationExplicit() + val action = ApplyIntentionAction(intention, intention.text, editor, file) + val group = DefaultActionGroup(action) + val context = SimpleDataContext.getProjectContext(null) + return JBPopupFactory.getInstance() + .createActionGroupPopup(null, group, context, JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, true) + } +} diff --git a/src/main/kotlin/sideonly/SideOnlyUtil.kt b/src/main/kotlin/sideonly/SideOnlyUtil.kt index 97fe59628..2453bbe90 100644 --- a/src/main/kotlin/sideonly/SideOnlyUtil.kt +++ b/src/main/kotlin/sideonly/SideOnlyUtil.kt @@ -16,6 +16,8 @@ import com.demonwav.mcdev.platform.fabric.util.FabricConstants import com.demonwav.mcdev.platform.forge.util.ForgeConstants import com.demonwav.mcdev.platform.mixin.util.isMixin import com.demonwav.mcdev.platform.mixin.util.mixinTargets +import com.demonwav.mcdev.util.SemanticVersion +import com.demonwav.mcdev.util.addGradleDependency import com.demonwav.mcdev.util.findModule import com.demonwav.mcdev.util.packageName import com.intellij.codeInspection.ProblemsHolder @@ -23,6 +25,9 @@ import com.intellij.codeInspection.dataFlow.StandardDataFlowRunner import com.intellij.json.psi.JsonFile import com.intellij.json.psi.JsonObject import com.intellij.json.psi.JsonStringLiteral +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.Messages import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiArrayType @@ -43,13 +48,63 @@ import com.intellij.psi.PsiPackage import com.intellij.psi.PsiReferenceExpression import com.intellij.psi.PsiResolveHelper import com.intellij.psi.PsiType +import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.util.parentOfType object SideOnlyUtil { const val MCDEV_SIDEONLY_ANNOTATION = "com.demonwav.mcdev.annotations.CheckEnv" const val MCDEV_SIDE = "com.demonwav.mcdev.annotations.Env" - internal fun getAnnotationSide(annotation: PsiAnnotation, hardness: SideHardness): Side { + fun ensureMcdevDependencyPresent( + project: Project, + module: Module, + title: String, + scope: GlobalSearchScope + ): Boolean { + if (JavaPsiFacade.getInstance(project).findClass(MCDEV_SIDEONLY_ANNOTATION, scope) != null) { + return true + } + + if (addMcdevAnnotationsDependency(project, module, title)) { + return true + } + + return false + } + + private fun addMcdevAnnotationsDependency(project: Project, module: Module, title: String): Boolean { + val message = "MinecraftDev annotations library is missing. " + + "Without the library, MinecraftDev cannot run the analysis.\n" + + "Would you like to add it to the project buildscript automatically?" + if (Messages.showOkCancelDialog( + project, + message, + title, + Messages.OK_BUTTON, + Messages.CANCEL_BUTTON, + Messages.getErrorIcon() + ) == Messages.OK + ) { + // TODO: fetch the latest version + if (addGradleDependency( + project, + module, + "com.demonwav.mcdev", + "annotations", + SemanticVersion.release(1, 0) + ) + ) { + return true + } else { + val errorMessage = "Failed to add MinecraftDev annotations library automatically. " + + "Please add it to your buildscript manually." + Messages.showMessageDialog(project, errorMessage, title, Messages.getErrorIcon()) + } + } + return false + } + + fun getAnnotationSide(annotation: PsiAnnotation, hardness: SideHardness): Side { var isSideAnnotation = false if (hardness != SideHardness.HARD) { @@ -83,7 +138,7 @@ object SideOnlyUtil { } } - internal fun getExplicitAnnotation(element: PsiModifierListOwner, hardness: SideHardness): SideInstance? { + fun getExplicitAnnotation(element: PsiModifierListOwner, hardness: SideHardness): SideInstance? { return element.annotations.asSequence() .map { it to getAnnotationSide(it, hardness) } .firstOrNull { it.second != Side.BOTH } @@ -98,12 +153,7 @@ object SideOnlyUtil { } } - internal fun getExplicitOrInferredAnnotation(element: PsiModifierListOwner, hardness: SideHardness): SideInstance? { - val explicitAnnotation = getExplicitAnnotation(element, hardness) - if (explicitAnnotation != null) { - return explicitAnnotation - } - + private fun getInferredAnnotation(element: PsiModifierListOwner, hardness: SideHardness): SideInstance? { return when (element) { is PsiClass -> getInferredClassAnnotation(element, hardness) is PsiMethod -> getInferredMethodAnnotation(element, hardness) @@ -111,6 +161,15 @@ object SideOnlyUtil { } } + fun getExplicitOrInferredAnnotation(element: PsiModifierListOwner, hardness: SideHardness): SideInstance? { + return getExplicitAnnotation(element, hardness) ?: getInferredAnnotation(element, hardness) + } + + fun getInferredAnnotationOnly(element: PsiModifierListOwner, hardness: SideHardness): SideInstance? { + if (getExplicitAnnotation(element, hardness) != null) return null + return getInferredAnnotation(element, hardness) + } + private fun getInferredClassAnnotation(cls: PsiClass, hardness: SideHardness): SideInstance? { // If mixing into a client class, the mixin is also clientside if (cls.isMixin) { diff --git a/src/main/kotlin/util/gradle-util.kt b/src/main/kotlin/util/gradle-util.kt index a91c6e0b1..21faae3c5 100644 --- a/src/main/kotlin/util/gradle-util.kt +++ b/src/main/kotlin/util/gradle-util.kt @@ -11,16 +11,35 @@ package com.demonwav.mcdev.util import com.intellij.execution.executors.DefaultRunExecutor +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode import com.intellij.openapi.externalSystem.task.TaskCallback import com.intellij.openapi.externalSystem.util.ExternalSystemUtil +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project +import com.intellij.openapi.project.rootManager +import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.psi.PsiManager +import com.intellij.psi.codeStyle.CodeStyleManager import java.nio.file.Path import java.util.concurrent.locks.Condition import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock +import org.jetbrains.kotlin.psi.KtCallElement +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtLambdaExpression +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtScriptInitializer +import org.jetbrains.kotlin.psi.KtSimpleNameExpression import org.jetbrains.plugins.gradle.util.GradleConstants +import org.jetbrains.plugins.groovy.lang.psi.GroovyFile +import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression +import org.jetbrains.plugins.groovy.lang.psi.util.childrenOfType inline fun runGradleTask( project: Project, @@ -51,6 +70,81 @@ inline fun runGradleTask( } } +fun addGradleDependency( + project: Project, + module: Module, + group: String, + artifact: String, + version: SemanticVersion +): Boolean { + val buildGradleFile = module.rootManager.contentRoots.mapFirstNotNull { moduleFile -> + moduleFile.findChild("build.gradle") ?: moduleFile.findChild("build.gradle.kts") + } ?: ProjectRootManager.getInstance(project).contentRoots.mapFirstNotNull { projectFile -> + projectFile.findChild("build.gradle") ?: projectFile.findChild("build.gradle.kts") + } ?: return false + val psiFile = PsiManager.getInstance(project).findFile(buildGradleFile) ?: return false + val success = when (psiFile) { + is GroovyFile -> addGroovyGradleDependency(project, psiFile, group, artifact, version) + is KtFile -> addKotlinGradleDependency(project, psiFile, group, artifact, version) + else -> false + } + if (success) { + FileDocumentManager.getInstance().saveAllDocuments() + ExternalSystemUtil.refreshProjects( + ImportSpecBuilder(project, GradleConstants.SYSTEM_ID).use(ProgressExecutionMode.MODAL_SYNC) + ) + } + return success +} + +private fun addGroovyGradleDependency( + project: Project, + file: GroovyFile, + group: String, + artifact: String, + version: SemanticVersion +): Boolean { + val dependenciesCall = file.childrenOfType().firstOrNull { methodCall -> + (methodCall.invokedExpression as? GrReferenceExpression)?.referenceName == "dependencies" + } ?: return false + val dependenciesClosure = dependenciesCall.closureArguments.firstOrNull() ?: return false + val toInsert = GroovyPsiElementFactory.getInstance(project).createStatementFromText( + "implementation '$group:$artifact:$version'", + dependenciesClosure + ) + WriteCommandAction.runWriteCommandAction(project) { + dependenciesClosure.addStatementBefore(toInsert, null) + CodeStyleManager.getInstance(project).reformat(dependenciesClosure) + } + return true +} + +private fun addKotlinGradleDependency( + project: Project, + file: KtFile, + group: String, + artifact: String, + version: SemanticVersion +): Boolean { + val scriptBlock = file.script?.blockExpression ?: return false + val dependenciesCall = scriptBlock.statements.asSequence() + .mapNotNull { it as? KtScriptInitializer } + .mapNotNull { it.body as? KtCallElement } + .firstOrNull { (it.calleeExpression as? KtSimpleNameExpression)?.getReferencedName() == "dependencies" } + ?: return false + val dependenciesLambda = dependenciesCall.lambdaArguments.firstOrNull()?.getArgumentExpression() + as? KtLambdaExpression ?: return false + val dependenciesBlock = dependenciesLambda.functionLiteral.bodyBlockExpression ?: return false + val factory = KtPsiFactory(project) + val toInsert = factory.createExpression("implementation(\"$group:$artifact:$version\")") + WriteCommandAction.runWriteCommandAction(project) { + dependenciesBlock.addBefore(factory.createNewLine(), dependenciesBlock.rBrace) + dependenciesBlock.addBefore(toInsert, dependenciesBlock.rBrace) + CodeStyleManager.getInstance(project).reformat(dependenciesBlock) + } + return true +} + class GradleCallback(private val lock: ReentrantLock, private val condition: Condition) : TaskCallback { override fun onSuccess() = resume() diff --git a/src/main/kotlin/util/utils.kt b/src/main/kotlin/util/utils.kt index 95a12cb52..9a62117fa 100644 --- a/src/main/kotlin/util/utils.kt +++ b/src/main/kotlin/util/utils.kt @@ -20,6 +20,8 @@ import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.Project import com.intellij.openapi.project.ProjectManager @@ -67,6 +69,29 @@ inline fun Project.runWriteTaskInSmartMode(crossinline func: () -> T) return ref.get() } +fun Project.runInSmartModeFromReadAction( + progressIndicator: ProgressIndicator? = null, + func: () -> Unit +) { + val dumbService = DumbService.getInstance(this) + if (dumbService.isDumb) { + ApplicationManager.getApplication().executeOnPooledThread { + ProgressManager.getInstance().runProcess( + { + ProgressManager.progress("Indexing") + dumbService.runReadActionInSmartMode { + ProgressManager.checkCanceled() + invokeLater(func) + } + }, + progressIndicator + ) + } + } else { + func() + } +} + fun invokeAndWait(func: () -> T): T { val ref = Ref() ApplicationManager.getApplication().invokeAndWait({ ref.set(func()) }, ModalityState.defaultModalityState()) diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 48a653590..4a6756ef5 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -14,6 +14,7 @@ org.jetbrains.idea.maven com.intellij.gradle org.intellij.groovy + org.jetbrains.kotlin com.demonwav.minecraft-dev Minecraft Development @@ -57,10 +58,18 @@ + + + com.demonwav.mcdev.sideonly.MakeInferredMcdevAnnotationExplicit + Minecraft + MakeInferredMcdevAnnotationExplicit + + + @@ -83,9 +92,6 @@ parentId="Settings.Minecraft" instance="com.demonwav.mcdev.translations.sorting.TranslationTemplateConfigurable"/> - - - diff --git a/src/main/resources/intentionDescriptions/MakeInferredMcdevAnnotationExplicit/after.java.template b/src/main/resources/intentionDescriptions/MakeInferredMcdevAnnotationExplicit/after.java.template new file mode 100644 index 000000000..0a063aeba --- /dev/null +++ b/src/main/resources/intentionDescriptions/MakeInferredMcdevAnnotationExplicit/after.java.template @@ -0,0 +1,7 @@ +import com.demonwav.mcdev.annotations.CheckEnv; +import com.demonwav.mcdev.annotations.Env; + +@CheckEnv(Env.CLIENT) +class A extends ClientOnlyClass { + // stuff +} diff --git a/src/main/resources/intentionDescriptions/MakeInferredMcdevAnnotationExplicit/before.java.template b/src/main/resources/intentionDescriptions/MakeInferredMcdevAnnotationExplicit/before.java.template new file mode 100644 index 000000000..df77c4d46 --- /dev/null +++ b/src/main/resources/intentionDescriptions/MakeInferredMcdevAnnotationExplicit/before.java.template @@ -0,0 +1,3 @@ +class A extends ClientOnlyClass { + // stuff +} diff --git a/src/main/resources/intentionDescriptions/MakeInferredMcdevAnnotationExplicit/description.html b/src/main/resources/intentionDescriptions/MakeInferredMcdevAnnotationExplicit/description.html new file mode 100644 index 000000000..e8393688c --- /dev/null +++ b/src/main/resources/intentionDescriptions/MakeInferredMcdevAnnotationExplicit/description.html @@ -0,0 +1,15 @@ + + + + +This intention adds annotations inferred by MinecraftDev (@CheckEnv) to the code explicitly. + +