User: rednesto Date: 30 Jul 24 11:59 Revision: 7198d5f7c00898e8f561a29725279d1f85bcef27 Summary: Support Kotlin event listener generation TeamCity URL: http://ci.mcdev.io:80/viewModification.html?tab=vcsModificationFiles&modId=9517&personal=false Index: changelog.md =================================================================== --- changelog.md (revision 03fb693644f3cfc2dd2e0fa46a67e2ab2aca2a94) +++ changelog.md (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -5,6 +5,7 @@ ### Added - Access widener completion in fabric.mod.json +- Event listener generation for Kotlin ### Fixed Index: src/main/kotlin/insight/generation/EventGenHelper.kt =================================================================== --- src/main/kotlin/insight/generation/EventGenHelper.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) +++ src/main/kotlin/insight/generation/EventGenHelper.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -0,0 +1,122 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.insight.generation + +import com.demonwav.mcdev.util.addImplements +import com.intellij.core.CoreJavaCodeStyleManager +import com.intellij.lang.LanguageExtension +import com.intellij.lang.LanguageExtensionPoint +import com.intellij.openapi.editor.RangeMarker +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.codeStyle.CodeStyleManager +import com.intellij.psi.util.parentOfType +import org.jetbrains.kotlin.idea.core.ShortenReferences +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtPsiFactory + +interface EventGenHelper { + + fun addImplements(context: PsiElement, fqn: String) + + fun reformatAndShortenRefs(file: PsiFile, startOffset: Int, endOffset: Int) + + companion object { + + val EP_NAME = ExtensionPointName.create>( + "com.demonwav.minecraft-dev.eventGenHelper" + ) + val COLLECTOR = LanguageExtension(EP_NAME, JvmEventGenHelper()) + } +} + +open class JvmEventGenHelper : EventGenHelper { + + override fun addImplements(context: PsiElement, fqn: String) {} + + override fun reformatAndShortenRefs(file: PsiFile, startOffset: Int, endOffset: Int) { + val project = file.project + + val marker = doReformat(project, file, startOffset, endOffset) ?: return + + CoreJavaCodeStyleManager.getInstance(project).shortenClassReferences(file, marker.startOffset, marker.endOffset) + } + + companion object { + + fun doReformat(project: Project, file: PsiFile, startOffset: Int, endOffset: Int): RangeMarker? { + val documentManager = PsiDocumentManager.getInstance(project) + val document = documentManager.getDocument(file) ?: return null + + val marker = document.createRangeMarker(startOffset, endOffset).apply { + isGreedyToLeft = true + isGreedyToRight = true + } + + CodeStyleManager.getInstance(project).reformatText(file, startOffset, endOffset) + documentManager.commitDocument(document) + + return marker + } + } +} + +class JavaEventGenHelper : JvmEventGenHelper() { + + override fun addImplements(context: PsiElement, fqn: String) { + val psiClass = context.parentOfType(true) ?: return + psiClass.addImplements(fqn) + } +} + +class KotlinEventGenHelper : EventGenHelper { + + private fun hasSuperType(ktClass: KtClassOrObject, fqn: String): Boolean { + val names = setOf(fqn, fqn.substringAfterLast('.')) + return ktClass.superTypeListEntries.any { it.text in names } + } + + override fun addImplements(context: PsiElement, fqn: String) { + val ktClass = context.parentOfType(true) ?: return + if (hasSuperType(ktClass, fqn)) { + return + } + + val factory = KtPsiFactory.contextual(context) + val entry = factory.createSuperTypeEntry(fqn) + val insertedEntry = ktClass.addSuperTypeListEntry(entry) + ShortenReferences.DEFAULT.process(insertedEntry) + } + + override fun reformatAndShortenRefs(file: PsiFile, startOffset: Int, endOffset: Int) { + file as? KtFile ?: return + val project = file.project + + val marker = JvmEventGenHelper.doReformat(project, file, startOffset, endOffset) ?: return + + ShortenReferences.DEFAULT.process(file, marker.startOffset, marker.endOffset) + } +} Index: src/main/kotlin/insight/generation/EventListenerGenerationSupport.kt =================================================================== --- src/main/kotlin/insight/generation/EventListenerGenerationSupport.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) +++ src/main/kotlin/insight/generation/EventListenerGenerationSupport.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -0,0 +1,38 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.insight.generation + +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement + +interface EventListenerGenerationSupport { + + fun canGenerate(context: PsiElement, editor: Editor): Boolean + + fun generateEventListener( + context: PsiElement, + listenerName: String, + eventClass: PsiClass, + data: GenerationData?, + editor: Editor + ) +} Index: src/main/kotlin/insight/generation/GenerateEventListenerAction.kt =================================================================== --- src/main/kotlin/insight/generation/GenerateEventListenerAction.kt (revision 03fb693644f3cfc2dd2e0fa46a67e2ab2aca2a94) +++ src/main/kotlin/insight/generation/GenerateEventListenerAction.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -21,13 +21,35 @@ package com.demonwav.mcdev.insight.generation import com.demonwav.mcdev.asset.MCDevBundle -import com.intellij.codeInsight.generation.actions.BaseGenerateAction +import com.demonwav.mcdev.facet.MinecraftFacet +import com.demonwav.mcdev.util.findModule +import com.intellij.codeInsight.CodeInsightActionHandler +import com.intellij.codeInsight.actions.CodeInsightAction import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile -class GenerateEventListenerAction : BaseGenerateAction(GenerateEventListenerHandler()) { +class GenerateEventListenerAction : CodeInsightAction() { + private val handler = GenerateEventListenerHandler() + + override fun getHandler(): CodeInsightActionHandler = handler + override fun update(e: AnActionEvent) { super.update(e) e.presentation.text = MCDevBundle("generate.event_listener.title") } + + override fun isValidForFile( + project: Project, + editor: Editor, + file: PsiFile + ): Boolean { + val module = file.findModule() ?: return false + val minecraftFacet = MinecraftFacet.getInstance(module) ?: return false + val support = minecraftFacet.modules.firstNotNullOfOrNull { it.eventListenerGenSupport } ?: return false + val caretElement = file.findElementAt(editor.caretModel.offset) ?: return false + return support.canGenerate(caretElement, editor) -} + } +} Index: src/main/kotlin/insight/generation/GenerateEventListenerHandler.kt =================================================================== --- src/main/kotlin/insight/generation/GenerateEventListenerHandler.kt (revision 03fb693644f3cfc2dd2e0fa46a67e2ab2aca2a94) +++ src/main/kotlin/insight/generation/GenerateEventListenerHandler.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -20,72 +20,45 @@ package com.demonwav.mcdev.insight.generation -import com.demonwav.mcdev.asset.MCDevBundle import com.demonwav.mcdev.facet.MinecraftFacet import com.demonwav.mcdev.insight.generation.ui.EventGenerationDialog import com.demonwav.mcdev.platform.AbstractModule -import com.demonwav.mcdev.util.castNotNull -import com.intellij.codeInsight.generation.ClassMember -import com.intellij.codeInsight.generation.GenerateMembersHandlerBase -import com.intellij.codeInsight.generation.GenerationInfo -import com.intellij.codeInsight.generation.PsiGenerationInfo +import com.demonwav.mcdev.util.findModule +import com.demonwav.mcdev.util.mapFirstNotNull +import com.intellij.codeInsight.CodeInsightActionHandler import com.intellij.ide.util.TreeClassChooserFactory -import com.intellij.openapi.actionSystem.DataContext -import com.intellij.openapi.editor.CaretModel import com.intellij.openapi.editor.Editor -import com.intellij.openapi.editor.LogicalPosition -import com.intellij.openapi.module.ModuleUtilCore import com.intellij.openapi.project.Project import com.intellij.psi.PsiClass import com.intellij.psi.PsiFile -import com.intellij.psi.PsiMethod import com.intellij.psi.search.GlobalSearchScope -import com.intellij.psi.util.PsiTreeUtil import com.intellij.refactoring.RefactoringBundle -/** - * The standard handler to generate a new event listener as a method. - * Note that this is a psuedo generator as it relies on a wizard and the - * [.cleanup] to complete - */ -class GenerateEventListenerHandler : GenerateMembersHandlerBase(MCDevBundle("generate.event_listener.title")) { +class GenerateEventListenerHandler : CodeInsightActionHandler { - private data class GenerateData( - var editor: Editor, - var position: LogicalPosition, - var method: PsiMethod?, - var model: CaretModel, - var data: GenerationData?, - var chosenClass: PsiClass, - var chosenName: String, - var relevantModule: AbstractModule, - ) + override fun invoke(project: Project, editor: Editor, file: PsiFile) { + val module = file.findModule() ?: return + val facet = MinecraftFacet.getInstance(module) ?: return + val eventListenerGenSupport = facet.modules.mapFirstNotNull { it.eventListenerGenSupport } ?: return + val caretElement = file.findElementAt(editor.caretModel.offset) ?: return + val context = caretElement.context ?: return - private var data: GenerateData? = null - - override fun getHelpId() = "Generate Event Listener Dialog" - - override fun chooseOriginalMembers(aClass: PsiClass, project: Project, editor: Editor): Array? { - val moduleForPsiElement = ModuleUtilCore.findModuleForPsiElement(aClass) ?: return null - - val facet = MinecraftFacet.getInstance(moduleForPsiElement) ?: return null - val chooser = TreeClassChooserFactory.getInstance(project) .createWithInnerClassesScopeChooser( RefactoringBundle.message("choose.destination.class"), - GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(moduleForPsiElement, false), + GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, false), { aClass1 -> isSuperEventListenerAllowed(aClass1, facet) }, null, ) chooser.showDialog() - val chosenClass = chooser.selected ?: return null + val chosenClass = chooser.selected ?: return val relevantModule = facet.modules.asSequence() .filter { m -> isSuperEventListenerAllowed(chosenClass, m) } - .firstOrNull() ?: return null + .firstOrNull() ?: return - val chosenClassName = chosenClass.nameIdentifier?.text ?: return null + val chosenClassName = chosenClass.nameIdentifier?.text ?: return val generationDialog = EventGenerationDialog( editor, @@ -94,72 +67,21 @@ relevantModule.moduleType.getDefaultListenerName(chosenClass), ) - val okay = generationDialog.showAndGet() - - if (!okay) { - return null + if (!generationDialog.showAndGet()) { + return } - val dialogDAta = generationDialog.data - val chosenName = generationDialog.chosenName - - val position = editor.caretModel.logicalPosition - - val method = PsiTreeUtil.getParentOfType( - aClass.containingFile.findElementAt(editor.caretModel.offset), - PsiMethod::class.java, - ) - - this.data = GenerateData( - editor, - position, - method, - editor.caretModel, - dialogDAta, + eventListenerGenSupport.generateEventListener( + context, + generationDialog.chosenName, chosenClass, - chosenName, - relevantModule, + generationDialog.data, + editor ) - - return DUMMY_RESULT } - override fun getAllOriginalMembers(aClass: PsiClass) = null + override fun startInWriteAction(): Boolean = false - override fun generateMemberPrototypes(aClass: PsiClass, originalMember: ClassMember?): Array? { - if (data == null) { - return null - } - - data?.let { data -> - data.relevantModule.doPreEventGenerate(aClass, data.data) - - data.model.moveToLogicalPosition(data.position) - - val newMethod = - data.relevantModule.generateEventListenerMethod(aClass, data.chosenClass, data.chosenName, data.data) - - if (newMethod != null) { - val info = PsiGenerationInfo(newMethod) - info.positionCaret(data.editor, true) - if (data.method != null) { - info.insert(aClass, data.method, false) - } - - return arrayOf(info) - } - } - - return null - } - - override fun isAvailableForQuickList(editor: Editor, file: PsiFile, dataContext: DataContext): Boolean { - val module = ModuleUtilCore.findModuleForPsiElement(file) ?: return false - - val instance = MinecraftFacet.getInstance(module) - return instance != null && instance.isEventGenAvailable - } - private fun isSuperEventListenerAllowed(eventClass: PsiClass, module: AbstractModule): Boolean { val supers = eventClass.supers for (aSuper in supers) { @@ -185,10 +107,4 @@ } return false } - - companion object { - private val DUMMY_RESULT = - // cannot return empty array, but this result won't be used anyway - arrayOfNulls(1).castNotNull() - } +} -} Index: src/main/kotlin/insight/generation/MethodRenderer.kt =================================================================== --- src/main/kotlin/insight/generation/MethodRenderer.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) +++ src/main/kotlin/insight/generation/MethodRenderer.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -0,0 +1,173 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.insight.generation + +import com.intellij.lang.jvm.JvmModifier +import com.intellij.lang.jvm.actions.AnnotationAttributeRequest +import com.intellij.lang.jvm.actions.AnnotationAttributeValueRequest +import com.intellij.psi.PsiType +import com.intellij.psi.PsiTypes + +interface MethodRenderer { + + fun renderMethod( + name: String, + parameters: List>, + modifiers: Set, + returnType: PsiType, + annotations: List>> + ): String + + companion object { + + val byLanguage = mapOf( + "JAVA" to JavaRenderer, + "kotlin" to KotlinRenderer, + ) + } + + private object JavaRenderer : MethodRenderer { + + override fun renderMethod( + name: String, + parameters: List>, + modifiers: Set, + returnType: PsiType, + annotations: List>> + ): String = buildString { + for ((fqn, attributes) in annotations) { + renderAnnotation(fqn, attributes) + appendLine() + } + + when { + JvmModifier.PUBLIC in modifiers -> append("public ") + JvmModifier.PRIVATE in modifiers -> append("private ") + JvmModifier.PROTECTED in modifiers -> append("protected ") + } + + when { + JvmModifier.STATIC in modifiers -> append("static ") + JvmModifier.ABSTRACT in modifiers -> append("abstract ") + JvmModifier.FINAL in modifiers -> append("final ") + } + + append(returnType.canonicalText) + append(' ') + append(name) + parameters.joinTo(this, prefix = "(", postfix = ")") { (paramName, paramType) -> + paramType.canonicalText + " " + paramName + } + appendLine("{}") + } + + fun StringBuilder.renderAnnotation(fqn: String, attributes: List) { + append('@') + append(fqn) + if (attributes.isNotEmpty()) { + attributes.joinTo(this, prefix = "(", postfix = ")") { attribute -> + attribute.name + " = " + renderAnnotationValue(attribute.value) + } + } + } + + fun renderAnnotationValue(value: AnnotationAttributeValueRequest): String = when (value) { + is AnnotationAttributeValueRequest.PrimitiveValue -> value.value.toString() + is AnnotationAttributeValueRequest.StringValue -> '"' + value.value + '"' + is AnnotationAttributeValueRequest.ClassValue -> value.classFqn + ".class" + is AnnotationAttributeValueRequest.ConstantValue -> value.text + is AnnotationAttributeValueRequest.NestedAnnotation -> buildString { + renderAnnotation(value.annotationRequest.qualifiedName, value.annotationRequest.attributes) + } + + is AnnotationAttributeValueRequest.ArrayValue -> + value.members.joinToString(prefix = "{", postfix = "}", transform = ::renderAnnotationValue) + } + } + + private object KotlinRenderer : MethodRenderer { + + override fun renderMethod( + name: String, + parameters: List>, + modifiers: Set, + returnType: PsiType, + annotations: List>> + ): String = buildString { + for ((fqn, attributes) in annotations) { + renderAnnotation(fqn, attributes) + appendLine() + } + + if (JvmModifier.STATIC in modifiers) { + appendLine("@JvmStatic") + } + + when { + // Skipping public as it is the default visibility + JvmModifier.PRIVATE in modifiers -> append("private ") + JvmModifier.PROTECTED in modifiers -> append("protected ") + JvmModifier.PACKAGE_LOCAL in modifiers -> append("internal ") // Close enough + } + + when { + JvmModifier.ABSTRACT in modifiers -> append("abstract ") + JvmModifier.FINAL in modifiers -> append("final ") + } + + append("fun ") + append(name) + parameters.joinTo(this, prefix = "(", postfix = ")") { (paramName, paramType) -> + paramName + ": " + paramType.canonicalText + } + + if (returnType != PsiTypes.voidType()) { + append(": ") + append(returnType.canonicalText) + } + + appendLine("{}") + } + + fun StringBuilder.renderAnnotation(fqn: String, attributes: List) { + append('@') + append(fqn) + if (attributes.isNotEmpty()) { + attributes.joinTo(this, prefix = "(", postfix = ")") { attribute -> + attribute.name + " = " + renderAnnotationValue(attribute.value) + } + } + } + + fun renderAnnotationValue(value: AnnotationAttributeValueRequest): String = when (value) { + is AnnotationAttributeValueRequest.PrimitiveValue -> value.value.toString() + is AnnotationAttributeValueRequest.StringValue -> '"' + value.value + '"' + is AnnotationAttributeValueRequest.ClassValue -> value.classFqn + "::class" + is AnnotationAttributeValueRequest.ConstantValue -> value.text + is AnnotationAttributeValueRequest.NestedAnnotation -> buildString { + renderAnnotation(value.annotationRequest.qualifiedName, value.annotationRequest.attributes) + } + + is AnnotationAttributeValueRequest.ArrayValue -> + value.members.joinToString(prefix = "[", postfix = "]", transform = ::renderAnnotationValue) + } + } +} Index: src/main/kotlin/insight/generation/MethodRendererBasedEventListenerGenerationSupport.kt =================================================================== --- src/main/kotlin/insight/generation/MethodRendererBasedEventListenerGenerationSupport.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) +++ src/main/kotlin/insight/generation/MethodRendererBasedEventListenerGenerationSupport.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -0,0 +1,94 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.insight.generation + +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import org.jetbrains.uast.UClass +import org.jetbrains.uast.UDeclaration +import org.jetbrains.uast.getUastParentOfType + +abstract class MethodRendererBasedEventListenerGenerationSupport : EventListenerGenerationSupport { + + override fun canGenerate(context: PsiElement, editor: Editor): Boolean { + if (context.language.id !in MethodRenderer.byLanguage) { + return false + } + + return adjustOffset(context, editor) != null + } + + override fun generateEventListener( + context: PsiElement, + listenerName: String, + eventClass: PsiClass, + data: GenerationData?, + editor: Editor + ) = runWriteAction { + val document = editor.document + + preGenerationProcess(context, data) + PsiDocumentManager.getInstance(context.project).doPostponedOperationsAndUnblockDocument(document) + + val renderer = MethodRenderer.byLanguage[context.language.id]!! + val offset = adjustOffset(context, editor) ?: return@runWriteAction + val text = invokeRenderer(renderer, context, listenerName, eventClass, data, editor) + + document.insertString(offset, text) + PsiDocumentManager.getInstance(context.project).commitDocument(document) + + val file = context.containingFile + editor.caretModel.moveToOffset(offset + text.length - 2) + + EventGenHelper.COLLECTOR.forLanguage(file.language) + .reformatAndShortenRefs(file, offset, offset + text.length) + } + + private fun adjustOffset(context: PsiElement, editor: Editor): Int? { + val declaration = context.getUastParentOfType() + if (declaration == null) { + return null + } + + if (declaration is UClass) { + return editor.caretModel.offset + } + + return declaration.sourcePsi?.textRange?.endOffset + } + + protected open fun preGenerationProcess( + context: PsiElement, + data: GenerationData?, + ) = Unit + + protected abstract fun invokeRenderer( + renderer: MethodRenderer, + context: PsiElement, + listenerName: String, + eventClass: PsiClass, + data: GenerationData?, + editor: Editor + ): String +} Index: src/main/kotlin/insight/generation/ui/EventGenerationPanel.kt =================================================================== --- src/main/kotlin/insight/generation/ui/EventGenerationPanel.kt (revision 03fb693644f3cfc2dd2e0fa46a67e2ab2aca2a94) +++ src/main/kotlin/insight/generation/ui/EventGenerationPanel.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -43,12 +43,10 @@ /** * This is called when the dialog is closing from the OK action. The platform should fill in their [GenerationData] object as * needed for whatever information their panel provides. The state of the panel can be assumed to be valid, since this will only be - * called if [.doValidate] has passed successfully. + * called if [doValidate] has passed successfully. * @return The [GenerationData] object which will be passed to the - * * [AbstractModule#doPreEventGenerate()][com.demonwav.mcdev.platform.AbstractModule.doPreEventGenerate] and - * * [AbstractModule#generateEventListenerMethod][com.demonwav.mcdev.platform.AbstractModule.generateEventListenerMethod] - * * methods. + * [EventListenerGenerationSupport][com.demonwav.mcdev.insight.generation.EventListenerGenerationSupport] */ open fun gatherData(): GenerationData? { return null Index: src/main/kotlin/platform/AbstractModule.kt =================================================================== --- src/main/kotlin/platform/AbstractModule.kt (revision 03fb693644f3cfc2dd2e0fa46a67e2ab2aca2a94) +++ src/main/kotlin/platform/AbstractModule.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -21,7 +21,7 @@ package com.demonwav.mcdev.platform import com.demonwav.mcdev.facet.MinecraftFacet -import com.demonwav.mcdev.insight.generation.GenerationData +import com.demonwav.mcdev.insight.generation.EventListenerGenerationSupport import com.demonwav.mcdev.inspection.IsCancelled import com.intellij.openapi.module.Module import com.intellij.psi.PsiClass @@ -44,6 +44,8 @@ open val icon: Icon? get() = moduleType.icon + open val eventListenerGenSupport: EventListenerGenerationSupport? = null + /** * By default, this method is provided in the case that a specific platform has no * listener handling whatsoever, or simply accepts event listeners with random @@ -63,15 +65,6 @@ open fun writeErrorMessageForEventParameter(eventClass: PsiClass, method: PsiMethod) = "Parameter does not extend the proper Event Class!" - open fun doPreEventGenerate(psiClass: PsiClass, data: GenerationData?) {} - - open fun generateEventListenerMethod( - containingClass: PsiClass, - chosenClass: PsiClass, - chosenName: String, - data: GenerationData?, - ): PsiMethod? = null - open fun shouldShowPluginIcon(element: PsiElement?) = false open fun checkUselessCancelCheck(expression: PsiMethodCallExpression): IsCancelled? { Index: src/main/kotlin/platform/bukkit/BukkitEventListenerGenerationSupport.kt =================================================================== --- src/main/kotlin/platform/bukkit/BukkitEventListenerGenerationSupport.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) +++ src/main/kotlin/platform/bukkit/BukkitEventListenerGenerationSupport.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -0,0 +1,81 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.bukkit + +import com.demonwav.mcdev.insight.generation.EventGenHelper +import com.demonwav.mcdev.insight.generation.GenerationData +import com.demonwav.mcdev.insight.generation.MethodRenderer +import com.demonwav.mcdev.insight.generation.MethodRendererBasedEventListenerGenerationSupport +import com.demonwav.mcdev.platform.bukkit.generation.BukkitGenerationData +import com.demonwav.mcdev.platform.bukkit.util.BukkitConstants +import com.demonwav.mcdev.util.psiType +import com.intellij.lang.jvm.JvmModifier +import com.intellij.lang.jvm.actions.AnnotationAttributeRequest +import com.intellij.lang.jvm.actions.constantAttribute +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiTypes + +class BukkitEventListenerGenerationSupport : MethodRendererBasedEventListenerGenerationSupport() { + + override fun preGenerationProcess( + context: PsiElement, + data: GenerationData? + ) { + require(data is BukkitGenerationData) + + EventGenHelper.COLLECTOR.forLanguage(context.language) + .addImplements(context, BukkitConstants.LISTENER_CLASS) + } + + override fun invokeRenderer( + renderer: MethodRenderer, + context: PsiElement, + listenerName: String, + eventClass: PsiClass, + data: GenerationData?, + editor: Editor + ): String { + require(data is BukkitGenerationData) + + val handlerAttributes = mutableListOf() + if (data.eventPriority != "NORMAL") { + handlerAttributes.add( + constantAttribute("priority", BukkitConstants.EVENT_PRIORITY_CLASS + '.' + data.eventPriority) + ) + } + + if (data.isIgnoreCanceled) { + handlerAttributes.add(constantAttribute("ignoreCancelled", "true")) + } + + return renderer.renderMethod( + listenerName, + listOf("event" to eventClass.psiType), + setOf(JvmModifier.PUBLIC), + PsiTypes.voidType(), + listOf( + BukkitConstants.HANDLER_ANNOTATION to handlerAttributes + ) + ) + } +} Index: src/main/kotlin/platform/bukkit/BukkitModule.kt =================================================================== --- src/main/kotlin/platform/bukkit/BukkitModule.kt (revision 03fb693644f3cfc2dd2e0fa46a67e2ab2aca2a94) +++ src/main/kotlin/platform/bukkit/BukkitModule.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -21,16 +21,14 @@ package com.demonwav.mcdev.platform.bukkit import com.demonwav.mcdev.facet.MinecraftFacet -import com.demonwav.mcdev.insight.generation.GenerationData +import com.demonwav.mcdev.insight.generation.EventListenerGenerationSupport import com.demonwav.mcdev.inspection.IsCancelled import com.demonwav.mcdev.platform.AbstractModule import com.demonwav.mcdev.platform.AbstractModuleType import com.demonwav.mcdev.platform.PlatformType -import com.demonwav.mcdev.platform.bukkit.generation.BukkitGenerationData import com.demonwav.mcdev.platform.bukkit.util.BukkitConstants import com.demonwav.mcdev.platform.bukkit.util.PaperConstants import com.demonwav.mcdev.util.SourceType -import com.demonwav.mcdev.util.addImplements import com.demonwav.mcdev.util.createVoidMethodWithParameterType import com.demonwav.mcdev.util.extendsOrImplements import com.demonwav.mcdev.util.findContainingMethod @@ -66,6 +64,8 @@ override val moduleType: T = type + override val eventListenerGenSupport: EventListenerGenerationSupport? = BukkitEventListenerGenerationSupport() + private val pluginParentClasses = listOf( BukkitConstants.PLUGIN, PaperConstants.PLUGIN_BOOTSTRAP, @@ -81,44 +81,6 @@ "Parameter is not a subclass of org.bukkit.event.Event\n" + "Compiling and running this listener may result in a runtime exception" - override fun doPreEventGenerate(psiClass: PsiClass, data: GenerationData?) { - if (!psiClass.extendsOrImplements(BukkitConstants.LISTENER_CLASS)) { - psiClass.addImplements(BukkitConstants.LISTENER_CLASS) - } - } - - override fun generateEventListenerMethod( - containingClass: PsiClass, - chosenClass: PsiClass, - chosenName: String, - data: GenerationData?, - ): PsiMethod? { - val bukkitData = data as BukkitGenerationData - - val method = generateBukkitStyleEventListenerMethod( - chosenClass, - chosenName, - project, - BukkitConstants.HANDLER_ANNOTATION, - bukkitData.isIgnoreCanceled, - ) ?: return null - - if (bukkitData.eventPriority != "NORMAL") { - val list = method.modifierList - val annotation = list.findAnnotation(BukkitConstants.HANDLER_ANNOTATION) ?: return method - - val value = JavaPsiFacade.getElementFactory(project) - .createExpressionFromText( - BukkitConstants.EVENT_PRIORITY_CLASS + "." + bukkitData.eventPriority, - annotation, - ) - - annotation.setDeclaredAttributeValue("priority", value) - } - - return method - } - override fun checkUselessCancelCheck(expression: PsiMethodCallExpression): IsCancelled? { val method = expression.findContainingMethod() ?: return null Index: src/main/kotlin/platform/bungeecord/BungeeCordEventListenerGenerationSupport.kt =================================================================== --- src/main/kotlin/platform/bungeecord/BungeeCordEventListenerGenerationSupport.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) +++ src/main/kotlin/platform/bungeecord/BungeeCordEventListenerGenerationSupport.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -0,0 +1,77 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.bungeecord + +import com.demonwav.mcdev.insight.generation.EventGenHelper +import com.demonwav.mcdev.insight.generation.GenerationData +import com.demonwav.mcdev.insight.generation.MethodRenderer +import com.demonwav.mcdev.insight.generation.MethodRendererBasedEventListenerGenerationSupport +import com.demonwav.mcdev.platform.bungeecord.generation.BungeeCordGenerationData +import com.demonwav.mcdev.platform.bungeecord.util.BungeeCordConstants +import com.demonwav.mcdev.util.psiType +import com.intellij.lang.jvm.JvmModifier +import com.intellij.lang.jvm.actions.AnnotationAttributeRequest +import com.intellij.lang.jvm.actions.constantAttribute +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiTypes + +class BungeeCordEventListenerGenerationSupport : MethodRendererBasedEventListenerGenerationSupport() { + + override fun preGenerationProcess( + context: PsiElement, + data: GenerationData? + ) { + require(data is BungeeCordGenerationData) + + EventGenHelper.COLLECTOR.forLanguage(context.language) + .addImplements(context, BungeeCordConstants.LISTENER_CLASS) + } + + override fun invokeRenderer( + renderer: MethodRenderer, + context: PsiElement, + listenerName: String, + eventClass: PsiClass, + data: GenerationData?, + editor: Editor + ): String { + require(data is BungeeCordGenerationData) + + val handlerAttributes = mutableListOf() + if (data.eventPriority != "NORMAL") { + handlerAttributes.add( + constantAttribute("priority", BungeeCordConstants.EVENT_PRIORITY_CLASS + '.' + data.eventPriority) + ) + } + + return renderer.renderMethod( + listenerName, + listOf("event" to eventClass.psiType), + setOf(JvmModifier.PUBLIC), + PsiTypes.voidType(), + listOf( + BungeeCordConstants.HANDLER_ANNOTATION to handlerAttributes + ) + ) + } +} Index: src/main/kotlin/platform/bungeecord/BungeeCordModule.kt =================================================================== --- src/main/kotlin/platform/bungeecord/BungeeCordModule.kt (revision 03fb693644f3cfc2dd2e0fa46a67e2ab2aca2a94) +++ src/main/kotlin/platform/bungeecord/BungeeCordModule.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -21,19 +21,15 @@ package com.demonwav.mcdev.platform.bungeecord import com.demonwav.mcdev.facet.MinecraftFacet -import com.demonwav.mcdev.insight.generation.GenerationData +import com.demonwav.mcdev.insight.generation.EventListenerGenerationSupport import com.demonwav.mcdev.platform.AbstractModule import com.demonwav.mcdev.platform.AbstractModuleType import com.demonwav.mcdev.platform.PlatformType -import com.demonwav.mcdev.platform.bukkit.BukkitModule import com.demonwav.mcdev.platform.bukkit.BukkitModuleType import com.demonwav.mcdev.platform.bukkit.PaperModuleType import com.demonwav.mcdev.platform.bukkit.SpigotModuleType -import com.demonwav.mcdev.platform.bungeecord.generation.BungeeCordGenerationData import com.demonwav.mcdev.platform.bungeecord.util.BungeeCordConstants import com.demonwav.mcdev.util.SourceType -import com.demonwav.mcdev.util.addImplements -import com.demonwav.mcdev.util.extendsOrImplements import com.demonwav.mcdev.util.nullable import com.demonwav.mcdev.util.runCatchingKtIdeaExceptions import com.intellij.lang.jvm.JvmModifier @@ -65,6 +61,8 @@ override val moduleType: T = type + override val eventListenerGenSupport: EventListenerGenerationSupport? = BungeeCordEventListenerGenerationSupport() + override fun isEventClassValid(eventClass: PsiClass, method: PsiMethod?) = BungeeCordConstants.EVENT_CLASS == eventClass.qualifiedName @@ -72,48 +70,6 @@ "Parameter is not a subclass of net.md_5.bungee.api.plugin.Event\n" + "Compiling and running this listener may result in a runtime exception" - override fun doPreEventGenerate(psiClass: PsiClass, data: GenerationData?) { - val bungeeCordListenerClass = BungeeCordConstants.LISTENER_CLASS - - if (!psiClass.extendsOrImplements(bungeeCordListenerClass)) { - psiClass.addImplements(bungeeCordListenerClass) - } - } - - override fun generateEventListenerMethod( - containingClass: PsiClass, - chosenClass: PsiClass, - chosenName: String, - data: GenerationData?, - ): PsiMethod? { - val method = BukkitModule.generateBukkitStyleEventListenerMethod( - chosenClass, - chosenName, - project, - BungeeCordConstants.HANDLER_ANNOTATION, - false, - ) ?: return null - - val generationData = data as BungeeCordGenerationData? ?: return method - - val modifierList = method.modifierList - val annotation = modifierList.findAnnotation(BungeeCordConstants.HANDLER_ANNOTATION) ?: return method - - if (generationData.eventPriority == "NORMAL") { - return method - } - - val value = JavaPsiFacade.getElementFactory(project) - .createExpressionFromText( - BungeeCordConstants.EVENT_PRIORITY_CLASS + "." + generationData.eventPriority, - annotation, - ) - - annotation.setDeclaredAttributeValue("priority", value) - - return method - } - override fun shouldShowPluginIcon(element: PsiElement?): Boolean { val identifier = element?.toUElementOfType() ?: return false Index: src/main/kotlin/platform/forge/ForgeEventListenerGenerationSupport.kt =================================================================== --- src/main/kotlin/platform/forge/ForgeEventListenerGenerationSupport.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) +++ src/main/kotlin/platform/forge/ForgeEventListenerGenerationSupport.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -0,0 +1,59 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.forge + +import com.demonwav.mcdev.insight.generation.GenerationData +import com.demonwav.mcdev.insight.generation.MethodRenderer +import com.demonwav.mcdev.insight.generation.MethodRendererBasedEventListenerGenerationSupport +import com.demonwav.mcdev.platform.forge.util.ForgeConstants +import com.demonwav.mcdev.util.extendsOrImplements +import com.demonwav.mcdev.util.psiType +import com.intellij.lang.jvm.JvmModifier +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiTypes + +class ForgeEventListenerGenerationSupport : MethodRendererBasedEventListenerGenerationSupport() { + + override fun invokeRenderer( + renderer: MethodRenderer, + context: PsiElement, + listenerName: String, + eventClass: PsiClass, + data: GenerationData?, + editor: Editor + ): String { + val annotationFqn = if (eventClass.extendsOrImplements(ForgeConstants.FML_EVENT)) { + ForgeConstants.EVENT_HANDLER_ANNOTATION + } else { + ForgeConstants.EVENTBUS_SUBSCRIBE_EVENT_ANNOTATION + } + + return renderer.renderMethod( + listenerName, + listOf("event" to eventClass.psiType), + setOf(JvmModifier.PUBLIC), + PsiTypes.voidType(), + listOf(annotationFqn to emptyList()) + ) + } +} Index: src/main/kotlin/platform/forge/ForgeModule.kt =================================================================== --- src/main/kotlin/platform/forge/ForgeModule.kt (revision 03fb693644f3cfc2dd2e0fa46a67e2ab2aca2a94) +++ src/main/kotlin/platform/forge/ForgeModule.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -22,7 +22,7 @@ import com.demonwav.mcdev.asset.PlatformAssets import com.demonwav.mcdev.facet.MinecraftFacet -import com.demonwav.mcdev.insight.generation.GenerationData +import com.demonwav.mcdev.insight.generation.EventListenerGenerationSupport import com.demonwav.mcdev.inspection.IsCancelled import com.demonwav.mcdev.platform.AbstractModule import com.demonwav.mcdev.platform.PlatformType @@ -31,8 +31,6 @@ import com.demonwav.mcdev.platform.mcp.McpModuleSettings import com.demonwav.mcdev.util.SemanticVersion import com.demonwav.mcdev.util.SourceType -import com.demonwav.mcdev.util.createVoidMethodWithParameterType -import com.demonwav.mcdev.util.extendsOrImplements import com.demonwav.mcdev.util.nullable import com.demonwav.mcdev.util.runCatchingKtIdeaExceptions import com.demonwav.mcdev.util.runWriteTaskLater @@ -62,6 +60,8 @@ override val type = PlatformType.FORGE override val icon = PlatformAssets.FORGE_ICON + override val eventListenerGenSupport: EventListenerGenerationSupport = ForgeEventListenerGenerationSupport() + override fun init() { ApplicationManager.getApplication().executeOnPooledThread { waitForAllSmart() @@ -158,32 +158,6 @@ override fun isStaticListenerSupported(method: PsiMethod) = true - override fun generateEventListenerMethod( - containingClass: PsiClass, - chosenClass: PsiClass, - chosenName: String, - data: GenerationData?, - ): PsiMethod? { - val isFmlEvent = chosenClass.extendsOrImplements(ForgeConstants.FML_EVENT) - - val method = createVoidMethodWithParameterType(project, chosenName, chosenClass) ?: return null - val modifierList = method.modifierList - - if (isFmlEvent) { - modifierList.addAnnotation(ForgeConstants.EVENT_HANDLER_ANNOTATION) - } else { - val mcVersion = McpModuleSettings.getInstance(module).state.minecraftVersion - ?.let { SemanticVersion.parse(it) } - if (mcVersion != null && mcVersion >= ForgeModuleType.FG3_MC_VERSION) { - modifierList.addAnnotation(ForgeConstants.EVENTBUS_SUBSCRIBE_EVENT_ANNOTATION) - } else { - modifierList.addAnnotation(ForgeConstants.SUBSCRIBE_EVENT_ANNOTATION) - } - } - - return method - } - override fun shouldShowPluginIcon(element: PsiElement?): Boolean { val identifier = element?.toUElementOfType() ?: return false Index: src/main/kotlin/platform/neoforge/NeoForgeEventListenerGenerationSupport.kt =================================================================== --- src/main/kotlin/platform/neoforge/NeoForgeEventListenerGenerationSupport.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) +++ src/main/kotlin/platform/neoforge/NeoForgeEventListenerGenerationSupport.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -0,0 +1,52 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.neoforge + +import com.demonwav.mcdev.insight.generation.GenerationData +import com.demonwav.mcdev.insight.generation.MethodRenderer +import com.demonwav.mcdev.insight.generation.MethodRendererBasedEventListenerGenerationSupport +import com.demonwav.mcdev.platform.neoforge.util.NeoForgeConstants +import com.demonwav.mcdev.util.psiType +import com.intellij.lang.jvm.JvmModifier +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiTypes + +class NeoForgeEventListenerGenerationSupport : MethodRendererBasedEventListenerGenerationSupport() { + + override fun invokeRenderer( + renderer: MethodRenderer, + context: PsiElement, + listenerName: String, + eventClass: PsiClass, + data: GenerationData?, + editor: Editor + ): String { + return renderer.renderMethod( + listenerName, + listOf("event" to eventClass.psiType), + setOf(JvmModifier.PUBLIC), + PsiTypes.voidType(), + listOf(NeoForgeConstants.SUBSCRIBE_EVENT to emptyList()) + ) + } +} Index: src/main/kotlin/platform/neoforge/NeoForgeModule.kt =================================================================== --- src/main/kotlin/platform/neoforge/NeoForgeModule.kt (revision 03fb693644f3cfc2dd2e0fa46a67e2ab2aca2a94) +++ src/main/kotlin/platform/neoforge/NeoForgeModule.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -22,13 +22,12 @@ import com.demonwav.mcdev.asset.PlatformAssets import com.demonwav.mcdev.facet.MinecraftFacet -import com.demonwav.mcdev.insight.generation.GenerationData +import com.demonwav.mcdev.insight.generation.EventListenerGenerationSupport import com.demonwav.mcdev.inspection.IsCancelled import com.demonwav.mcdev.platform.AbstractModule import com.demonwav.mcdev.platform.PlatformType import com.demonwav.mcdev.platform.neoforge.util.NeoForgeConstants import com.demonwav.mcdev.util.SourceType -import com.demonwav.mcdev.util.createVoidMethodWithParameterType import com.demonwav.mcdev.util.nullable import com.demonwav.mcdev.util.runCatchingKtIdeaExceptions import com.demonwav.mcdev.util.runWriteTaskLater @@ -54,6 +53,8 @@ override val type = PlatformType.NEOFORGE override val icon = PlatformAssets.NEOFORGE_ICON + override val eventListenerGenSupport: EventListenerGenerationSupport = NeoForgeEventListenerGenerationSupport() + override fun init() { ApplicationManager.getApplication().executeOnPooledThread { waitForAllSmart() @@ -97,20 +98,6 @@ override fun isStaticListenerSupported(method: PsiMethod) = true - override fun generateEventListenerMethod( - containingClass: PsiClass, - chosenClass: PsiClass, - chosenName: String, - data: GenerationData?, - ): PsiMethod? { - val method = createVoidMethodWithParameterType(project, chosenName, chosenClass) ?: return null - val modifierList = method.modifierList - - modifierList.addAnnotation(NeoForgeConstants.SUBSCRIBE_EVENT) - - return method - } - override fun shouldShowPluginIcon(element: PsiElement?): Boolean { val identifier = element?.toUElementOfType() ?: return false Index: src/main/kotlin/platform/sponge/SpongeEventListenerGenerationSupport.kt =================================================================== --- src/main/kotlin/platform/sponge/SpongeEventListenerGenerationSupport.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) +++ src/main/kotlin/platform/sponge/SpongeEventListenerGenerationSupport.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -0,0 +1,78 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.sponge + +import com.demonwav.mcdev.insight.generation.GenerationData +import com.demonwav.mcdev.insight.generation.MethodRenderer +import com.demonwav.mcdev.insight.generation.MethodRendererBasedEventListenerGenerationSupport +import com.demonwav.mcdev.platform.sponge.generation.SpongeGenerationData +import com.demonwav.mcdev.platform.sponge.util.SpongeConstants +import com.demonwav.mcdev.util.psiType +import com.intellij.lang.jvm.JvmModifier +import com.intellij.lang.jvm.actions.AnnotationAttributeRequest +import com.intellij.lang.jvm.actions.constantAttribute +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiTypes + +class SpongeEventListenerGenerationSupport : MethodRendererBasedEventListenerGenerationSupport() { + + override fun invokeRenderer( + renderer: MethodRenderer, + context: PsiElement, + listenerName: String, + eventClass: PsiClass, + data: GenerationData?, + editor: Editor + ): String { + require(data is SpongeGenerationData) + + val handlerAnnotations = mutableListOf>>() + + val handlerAttributes = mutableListOf() + if (data.eventOrder != "DEFAULT") { + handlerAttributes.add( + constantAttribute("order", SpongeConstants.ORDER + '.' + data.eventOrder) + ) + } + + handlerAnnotations.add(SpongeConstants.LISTENER_ANNOTATION to handlerAttributes) + + if (!data.isIgnoreCanceled) { + handlerAnnotations.add( + SpongeConstants.IS_CANCELLED_ANNOTATION to listOf( + constantAttribute("value", "org.spongepowered.api.util.Tristate.UNDEFINED") + ) + ) + } + + return renderer.renderMethod( + listenerName, + listOf("event" to eventClass.psiType), + setOf(JvmModifier.PUBLIC), + PsiTypes.voidType(), + listOf( + SpongeConstants.LISTENER_ANNOTATION to handlerAttributes + ) + ) + } +} Index: src/main/kotlin/platform/sponge/SpongeModule.kt =================================================================== --- src/main/kotlin/platform/sponge/SpongeModule.kt (revision 03fb693644f3cfc2dd2e0fa46a67e2ab2aca2a94) +++ src/main/kotlin/platform/sponge/SpongeModule.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -22,19 +22,16 @@ import com.demonwav.mcdev.asset.PlatformAssets import com.demonwav.mcdev.facet.MinecraftFacet -import com.demonwav.mcdev.insight.generation.GenerationData +import com.demonwav.mcdev.insight.generation.EventListenerGenerationSupport import com.demonwav.mcdev.inspection.IsCancelled import com.demonwav.mcdev.platform.AbstractModule import com.demonwav.mcdev.platform.PlatformType -import com.demonwav.mcdev.platform.sponge.generation.SpongeGenerationData import com.demonwav.mcdev.platform.sponge.util.SpongeConstants -import com.demonwav.mcdev.util.createVoidMethodWithParameterType import com.demonwav.mcdev.util.extendsOrImplements import com.demonwav.mcdev.util.findContainingMethod import com.demonwav.mcdev.util.runCatchingKtIdeaExceptions import com.intellij.lang.jvm.JvmModifier import com.intellij.psi.JavaPsiFacade -import com.intellij.psi.PsiAnnotationMemberValue import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement import com.intellij.psi.PsiMethod @@ -49,6 +46,8 @@ override val type = PlatformType.SPONGE override val icon = PlatformAssets.SPONGE_ICON + override val eventListenerGenSupport: EventListenerGenerationSupport = SpongeEventListenerGenerationSupport() + override fun isEventClassValid(eventClass: PsiClass, method: PsiMethod?) = "org.spongepowered.api.event.Event" == eventClass.qualifiedName @@ -56,40 +55,6 @@ "Parameter is not an instance of org.spongepowered.api.event.Event\n" + "Compiling and running this listener may result in a runtime exception" - override fun generateEventListenerMethod( - containingClass: PsiClass, - chosenClass: PsiClass, - chosenName: String, - data: GenerationData?, - ): PsiMethod? { - val method = createVoidMethodWithParameterType(project, chosenName, chosenClass) ?: return null - val modifierList = method.modifierList - - val listenerAnnotation = modifierList.addAnnotation("org.spongepowered.api.event.Listener") - - val generationData = (data as SpongeGenerationData?)!! - - if (!generationData.isIgnoreCanceled) { - val annotation = modifierList.addAnnotation("org.spongepowered.api.event.filter.IsCancelled") - val value = JavaPsiFacade.getElementFactory(project) - .createExpressionFromText("org.spongepowered.api.util.Tristate.UNDEFINED", annotation) - - annotation.setDeclaredAttributeValue("value", value) - } - - if (generationData.eventOrder != "DEFAULT") { - val value = JavaPsiFacade.getElementFactory(project) - .createExpressionFromText( - "org.spongepowered.api.event.Order." + generationData.eventOrder, - listenerAnnotation, - ) - - listenerAnnotation.setDeclaredAttributeValue("order", value) - } - - return method - } - override fun shouldShowPluginIcon(element: PsiElement?): Boolean { val identifier = element?.toUElementOfType() ?: return false Index: src/main/kotlin/platform/sponge/util/SpongeConstants.kt =================================================================== --- src/main/kotlin/platform/sponge/util/SpongeConstants.kt (revision 03fb693644f3cfc2dd2e0fa46a67e2ab2aca2a94) +++ src/main/kotlin/platform/sponge/util/SpongeConstants.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -31,6 +31,7 @@ const val TEXT_COLORS = "org.spongepowered.api.text.format.TextColors" const val EVENT = "org.spongepowered.api.event.Event" const val LISTENER_ANNOTATION = "org.spongepowered.api.event.Listener" + const val ORDER = "org.spongepowered.api.event.Order" const val GETTER_ANNOTATION = "org.spongepowered.api.event.filter.Getter" const val IS_CANCELLED_ANNOTATION = "org.spongepowered.api.event.filter.IsCancelled" const val CANCELLABLE = "org.spongepowered.api.event.Cancellable" Index: src/main/kotlin/platform/velocity/VelocityEventListenerGenerationSupport.kt =================================================================== --- src/main/kotlin/platform/velocity/VelocityEventListenerGenerationSupport.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) +++ src/main/kotlin/platform/velocity/VelocityEventListenerGenerationSupport.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -0,0 +1,66 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.velocity + +import com.demonwav.mcdev.insight.generation.GenerationData +import com.demonwav.mcdev.insight.generation.MethodRenderer +import com.demonwav.mcdev.insight.generation.MethodRendererBasedEventListenerGenerationSupport +import com.demonwav.mcdev.platform.velocity.generation.VelocityGenerationData +import com.demonwav.mcdev.platform.velocity.util.VelocityConstants +import com.demonwav.mcdev.util.psiType +import com.intellij.lang.jvm.JvmModifier +import com.intellij.lang.jvm.actions.AnnotationAttributeRequest +import com.intellij.lang.jvm.actions.constantAttribute +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiTypes + +class VelocityEventListenerGenerationSupport : MethodRendererBasedEventListenerGenerationSupport() { + + override fun invokeRenderer( + renderer: MethodRenderer, + context: PsiElement, + listenerName: String, + eventClass: PsiClass, + data: GenerationData?, + editor: Editor + ): String { + require(data is VelocityGenerationData) + + val handlerAttributes = mutableListOf() + if (data.eventOrder != "NORMAL") { + handlerAttributes.add( + constantAttribute("order", VelocityConstants.POST_ORDER + '.' + data.eventOrder) + ) + } + + return renderer.renderMethod( + listenerName, + listOf("event" to eventClass.psiType), + setOf(JvmModifier.PUBLIC), + PsiTypes.voidType(), + listOf( + VelocityConstants.SUBSCRIBE_ANNOTATION to handlerAttributes + ) + ) + } +} Index: src/main/kotlin/platform/velocity/VelocityModule.kt =================================================================== --- src/main/kotlin/platform/velocity/VelocityModule.kt (revision 03fb693644f3cfc2dd2e0fa46a67e2ab2aca2a94) +++ src/main/kotlin/platform/velocity/VelocityModule.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -22,16 +22,12 @@ import com.demonwav.mcdev.asset.PlatformAssets import com.demonwav.mcdev.facet.MinecraftFacet -import com.demonwav.mcdev.insight.generation.GenerationData +import com.demonwav.mcdev.insight.generation.EventListenerGenerationSupport import com.demonwav.mcdev.platform.AbstractModule import com.demonwav.mcdev.platform.PlatformType -import com.demonwav.mcdev.platform.velocity.generation.VelocityGenerationData import com.demonwav.mcdev.platform.velocity.util.VelocityConstants -import com.demonwav.mcdev.platform.velocity.util.VelocityConstants.SUBSCRIBE_ANNOTATION -import com.demonwav.mcdev.util.createVoidMethodWithParameterType import com.demonwav.mcdev.util.runCatchingKtIdeaExceptions import com.intellij.lang.jvm.JvmModifier -import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement import com.intellij.psi.PsiMethod @@ -44,34 +40,10 @@ override val type = PlatformType.VELOCITY override val icon = PlatformAssets.VELOCITY_ICON + override val eventListenerGenSupport: EventListenerGenerationSupport = VelocityEventListenerGenerationSupport() + override fun isEventClassValid(eventClass: PsiClass, method: PsiMethod?): Boolean = true - override fun generateEventListenerMethod( - containingClass: PsiClass, - chosenClass: PsiClass, - chosenName: String, - data: GenerationData?, - ): PsiMethod? { - val method = createVoidMethodWithParameterType(project, chosenName, chosenClass) ?: return null - val modifierList = method.modifierList - - val subscribeAnnotation = modifierList.addAnnotation(SUBSCRIBE_ANNOTATION) - - val generationData = data as VelocityGenerationData - - if (generationData.eventOrder != "NORMAL") { - val value = JavaPsiFacade.getElementFactory(project) - .createExpressionFromText( - "com.velocitypowered.api.event.PostOrder." + generationData.eventOrder, - subscribeAnnotation, - ) - - subscribeAnnotation.setDeclaredAttributeValue("order", value) - } - - return method - } - override fun shouldShowPluginIcon(element: PsiElement?): Boolean { val identifier = element?.toUElementOfType() ?: return false Index: src/main/kotlin/platform/velocity/util/VelocityConstants.kt =================================================================== --- src/main/kotlin/platform/velocity/util/VelocityConstants.kt (revision 03fb693644f3cfc2dd2e0fa46a67e2ab2aca2a94) +++ src/main/kotlin/platform/velocity/util/VelocityConstants.kt (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -26,6 +26,7 @@ const val PLUGIN_ANNOTATION = "com.velocitypowered.api.plugin.Plugin" const val SUBSCRIBE_ANNOTATION = "com.velocitypowered.api.event.Subscribe" + const val POST_ORDER = "com.velocitypowered.api.event.PostOrder" const val KYORI_TEXT_COLOR = "net.kyori.text.format.TextColor" val API_2 = SemanticVersion.release(2) Index: src/main/resources/META-INF/mcdev-kotlin.xml =================================================================== --- src/main/resources/META-INF/mcdev-kotlin.xml (revision 03fb693644f3cfc2dd2e0fa46a67e2ab2aca2a94) +++ src/main/resources/META-INF/mcdev-kotlin.xml (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -21,5 +21,7 @@ + + Index: src/main/resources/META-INF/plugin.xml =================================================================== --- src/main/resources/META-INF/plugin.xml (revision 03fb693644f3cfc2dd2e0fa46a67e2ab2aca2a94) +++ src/main/resources/META-INF/plugin.xml (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -92,6 +92,10 @@ + + + + @@ -199,6 +203,8 @@ + + Index: src/main/resources/messages/MinecraftDevelopment.properties =================================================================== --- src/main/resources/messages/MinecraftDevelopment.properties (revision 03fb693644f3cfc2dd2e0fa46a67e2ab2aca2a94) +++ src/main/resources/messages/MinecraftDevelopment.properties (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -159,7 +159,7 @@ facet.reimport.failed.content.no_error=Failed to start project refresh, please refresh your project manually. facet.reimport.failed.content.with_error=Failed to start project refresh, please refresh your project manually. Cause: {0} -generate.event_listener.title=Generate Event Listener +generate.event_listener.title=Event Listener generate.event_listener.settings=Event Listener Settings generate.event_listener.event_priority=Event Priority generate.event_listener.event_order=Event Order Index: src/main/resources/messages/MinecraftDevelopment_fr.properties =================================================================== --- src/main/resources/messages/MinecraftDevelopment_fr.properties (revision 03fb693644f3cfc2dd2e0fa46a67e2ab2aca2a94) +++ src/main/resources/messages/MinecraftDevelopment_fr.properties (revision 7198d5f7c00898e8f561a29725279d1f85bcef27) @@ -18,5 +18,5 @@ # along with this program. If not, see . # -generate.event_listener.title=Générer un Event Listener +generate.event_listener.title=Event Listener generate.event_listener.settings=Configuration du Listener