From e7f4d43be95e6453940e2f8b4d3514647ce82b25 Mon Sep 17 00:00:00 2001 From: leguan Date: Tue, 21 Apr 2026 19:06:27 +0200 Subject: [PATCH 1/3] Add completion contributor for Bukkit event handlers --- ...BukkitEventHandlerCompletionContributor.kt | 16 ++++ .../BukkitEventHandlerCompletionProvider.kt | 80 +++++++++++++++++++ .../BukkitEventHandlerInsertHandler.kt | 45 +++++++++++ src/main/resources/META-INF/plugin.xml | 7 ++ 4 files changed, 148 insertions(+) create mode 100644 src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionContributor.kt create mode 100644 src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionProvider.kt create mode 100644 src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerInsertHandler.kt diff --git a/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionContributor.kt b/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionContributor.kt new file mode 100644 index 000000000..53c776b78 --- /dev/null +++ b/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionContributor.kt @@ -0,0 +1,16 @@ +package com.demonwav.mcdev.platform.bukkit.completion + +import com.intellij.codeInsight.completion.CompletionContributor +import com.intellij.codeInsight.completion.CompletionType +import com.intellij.patterns.PlatformPatterns +import com.intellij.psi.PsiClass + +class BukkitEventHandlerCompletionContributor : CompletionContributor() { + init { + extend( + CompletionType.BASIC, + PlatformPatterns.psiElement().inside(PsiClass::class.java), + BukkitEventHandlerCompletionProvider() + ) + } +} diff --git a/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionProvider.kt b/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionProvider.kt new file mode 100644 index 000000000..73abaf0f1 --- /dev/null +++ b/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionProvider.kt @@ -0,0 +1,80 @@ +package com.demonwav.mcdev.platform.bukkit.completion + +import com.intellij.codeInsight.completion.CompletionParameters +import com.intellij.codeInsight.completion.CompletionProvider +import com.intellij.codeInsight.completion.CompletionResultSet +import com.intellij.codeInsight.completion.PrioritizedLookupElement +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.icons.AllIcons +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiModifier +import com.intellij.psi.impl.JavaPsiFacadeEx +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.searches.ClassInheritorsSearch +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.util.ProcessingContext +import org.jetbrains.annotations.NotNull + +class BukkitEventHandlerCompletionProvider : CompletionProvider() { + companion object { + const val EVENT_LISTENER = "org.bukkit.event.Listener" + private const val BUKKIT_EVENT_FQN = "org.bukkit.event.Event" + } + + override fun addCompletions( + @NotNull completionParameters: CompletionParameters, + @NotNull processingContext: ProcessingContext, + @NotNull completionResultSet: CompletionResultSet + ) { + val prefix = completionResultSet.prefixMatcher.prefix + if (!prefix.startsWith("on") || prefix.length == 2) return + + val position = completionParameters.position + val containingClass = PsiTreeUtil.getParentOfType(position, PsiClass::class.java) ?: return + val project: Project = position.project + val facade = JavaPsiFacadeEx.getInstanceEx(project) + + val eventListenerClass = facade.findClass(EVENT_LISTENER, GlobalSearchScope.allScope(project)) ?: return + if (!containingClass.isInheritor(eventListenerClass, true)) return + if (PsiTreeUtil.getParentOfType(position, PsiMethod::class.java) != null) return + + val scope = GlobalSearchScope.allScope(project) + val eventBaseClass = facade.findClass(BUKKIT_EVENT_FQN, scope) ?: return + + val eventNameFilter = prefix.substring(2).lowercase() + + ClassInheritorsSearch.search(eventBaseClass, scope, true) + .forEach { psiClass -> + if (psiClass.isInterface || psiClass.hasModifierProperty(PsiModifier.ABSTRACT)) { + return@forEach + } + + val eventSimpleName = psiClass.name ?: return@forEach + if (eventNameFilter.isNotEmpty() && !eventSimpleName.lowercase().startsWith(eventNameFilter)) { + return@forEach + } + + val lookupString = "on$eventSimpleName" + val methodName = lookupString.replace("Event", "") + val qualifiedName = psiClass.qualifiedName + + val element = LookupElementBuilder + .create(lookupString) + .withPresentableText("$lookupString()") + .withTailText(" - $qualifiedName", true) + .withTypeText("@EventHandler") + .withIcon(AllIcons.Nodes.Method) + .withBaseLookupString(lookupString) + .withBoldness(true) + .withInsertHandler(BukkitEventHandlerInsertHandler(methodName, qualifiedName)) + + completionResultSet.addElement( + PrioritizedLookupElement.withPriority(element, 100.0) + ) + } + + completionResultSet.stopHere() + } +} diff --git a/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerInsertHandler.kt b/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerInsertHandler.kt new file mode 100644 index 000000000..456408929 --- /dev/null +++ b/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerInsertHandler.kt @@ -0,0 +1,45 @@ +package com.demonwav.mcdev.platform.bukkit.completion + +import com.intellij.codeInsight.completion.InsertHandler +import com.intellij.codeInsight.completion.InsertionContext +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.template.TemplateManager +import com.intellij.codeInsight.template.impl.TextExpression +import com.intellij.openapi.util.NlsSafe +import org.jetbrains.annotations.NotNull + +class BukkitEventHandlerInsertHandler( + private val methodName: String, + private val qualifiedName: @NlsSafe String? +) : InsertHandler { + + companion object { + private const val EVENT_HANDLER_FQN = "org.bukkit.event.EventHandler" + } + + override fun handleInsert(@NotNull insertionContext: InsertionContext, @NotNull lookupElement: LookupElement) { + val project = insertionContext.project + val editor = insertionContext.editor + + val templateManager = TemplateManager.getInstance(project) + val template = templateManager.createTemplate("", "") + template.isToReformat = true + + template.addTextSegment("@${EVENT_HANDLER_FQN}\n") + template.addTextSegment("public void ") + template.addVariable("METHOD_NAME", TextExpression(methodName), true) + template.addTextSegment("($qualifiedName ") + template.addVariable("EVENT_PARAM", TextExpression("event"), true) + template.addTextSegment(") { \n") + template.addEndVariable() + template.addTextSegment("\n}") + + insertionContext.getDocument().deleteString( + insertionContext.getStartOffset(), + insertionContext.getTailOffset() + ); + + templateManager.startTemplate(editor, template) + + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 05cbc4dc0..13ca850e5 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -532,6 +532,13 @@ + + + + From f8e9e4faabec6aef48d25581804642d3919fa6fd Mon Sep 17 00:00:00 2001 From: leguan Date: Wed, 22 Apr 2026 11:33:26 +0200 Subject: [PATCH 2/3] cleanup code --- .../BukkitEventHandlerCompletionProvider.kt | 39 +++++++++++-------- .../BukkitEventHandlerInsertHandler.kt | 17 ++++---- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionProvider.kt b/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionProvider.kt index 73abaf0f1..1d6684c4e 100644 --- a/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionProvider.kt +++ b/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionProvider.kt @@ -1,5 +1,8 @@ package com.demonwav.mcdev.platform.bukkit.completion +import com.demonwav.mcdev.platform.bukkit.util.BukkitConstants +import com.demonwav.mcdev.util.findContainingClass +import com.demonwav.mcdev.util.findContainingMethod import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet @@ -18,31 +21,33 @@ import com.intellij.util.ProcessingContext import org.jetbrains.annotations.NotNull class BukkitEventHandlerCompletionProvider : CompletionProvider() { - companion object { - const val EVENT_LISTENER = "org.bukkit.event.Listener" - private const val BUKKIT_EVENT_FQN = "org.bukkit.event.Event" - } override fun addCompletions( - @NotNull completionParameters: CompletionParameters, - @NotNull processingContext: ProcessingContext, - @NotNull completionResultSet: CompletionResultSet + completionParameters: CompletionParameters, + processingContext: ProcessingContext, + completionResultSet: CompletionResultSet ) { val prefix = completionResultSet.prefixMatcher.prefix - if (!prefix.startsWith("on") || prefix.length == 2) return + if (!prefix.startsWith("on") || prefix.length == 2) { + return + } val position = completionParameters.position - val containingClass = PsiTreeUtil.getParentOfType(position, PsiClass::class.java) ?: return - val project: Project = position.project - val facade = JavaPsiFacadeEx.getInstanceEx(project) + val containingClass = position.findContainingClass() ?: return + val project = position.project + val facade = JavaPsiFacadeEx.getInstance(project) - val eventListenerClass = facade.findClass(EVENT_LISTENER, GlobalSearchScope.allScope(project)) ?: return - if (!containingClass.isInheritor(eventListenerClass, true)) return - if (PsiTreeUtil.getParentOfType(position, PsiMethod::class.java) != null) return + val eventListenerClass = facade.findClass(BukkitConstants.LISTENER_CLASS, GlobalSearchScope.allScope(project)) ?: return + if (!containingClass.isInheritor(eventListenerClass, true)) { + return + } - val scope = GlobalSearchScope.allScope(project) - val eventBaseClass = facade.findClass(BUKKIT_EVENT_FQN, scope) ?: return + if (position.findContainingMethod() != null) { + return + } + val scope = GlobalSearchScope.allScope(project) + val eventBaseClass = facade.findClass(BukkitConstants.EVENT_CLASS, scope) ?: return val eventNameFilter = prefix.substring(2).lowercase() ClassInheritorsSearch.search(eventBaseClass, scope, true) @@ -57,7 +62,7 @@ class BukkitEventHandlerCompletionProvider : CompletionProvider { - companion object { - private const val EVENT_HANDLER_FQN = "org.bukkit.event.EventHandler" - } - - override fun handleInsert(@NotNull insertionContext: InsertionContext, @NotNull lookupElement: LookupElement) { + override fun handleInsert(insertionContext: InsertionContext, lookupElement: LookupElement) { val project = insertionContext.project val editor = insertionContext.editor @@ -25,7 +22,7 @@ class BukkitEventHandlerInsertHandler( val template = templateManager.createTemplate("", "") template.isToReformat = true - template.addTextSegment("@${EVENT_HANDLER_FQN}\n") + template.addTextSegment("@${BukkitConstants.HANDLER_ANNOTATION}\n") template.addTextSegment("public void ") template.addVariable("METHOD_NAME", TextExpression(methodName), true) template.addTextSegment("($qualifiedName ") @@ -34,10 +31,10 @@ class BukkitEventHandlerInsertHandler( template.addEndVariable() template.addTextSegment("\n}") - insertionContext.getDocument().deleteString( - insertionContext.getStartOffset(), - insertionContext.getTailOffset() - ); + insertionContext.document.deleteString( + insertionContext.startOffset, + insertionContext.tailOffset + ) templateManager.startTemplate(editor, template) From a6fff0455dff65974a50bac0cacb3aa40ff33e81 Mon Sep 17 00:00:00 2001 From: leguan Date: Wed, 22 Apr 2026 12:24:30 +0200 Subject: [PATCH 3/3] add license header --- ...BukkitEventHandlerCompletionContributor.kt | 20 +++++++++++++++++++ .../BukkitEventHandlerCompletionProvider.kt | 20 +++++++++++++++++++ .../BukkitEventHandlerInsertHandler.kt | 20 +++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionContributor.kt b/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionContributor.kt index 53c776b78..d7702e672 100644 --- a/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionContributor.kt +++ b/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionContributor.kt @@ -1,3 +1,23 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2025 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.completion import com.intellij.codeInsight.completion.CompletionContributor diff --git a/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionProvider.kt b/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionProvider.kt index 1d6684c4e..f388bd0f9 100644 --- a/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionProvider.kt +++ b/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerCompletionProvider.kt @@ -1,3 +1,23 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2025 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.completion import com.demonwav.mcdev.platform.bukkit.util.BukkitConstants diff --git a/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerInsertHandler.kt b/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerInsertHandler.kt index deaa89e48..653e12ece 100644 --- a/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerInsertHandler.kt +++ b/src/main/kotlin/platform/bukkit/completion/BukkitEventHandlerInsertHandler.kt @@ -1,3 +1,23 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2025 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.completion import com.demonwav.mcdev.platform.bukkit.util.BukkitConstants