Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/

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()
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/

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
import com.intellij.codeInsight.completion.PrioritizedLookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.icons.AllIcons
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.util.ProcessingContext

class BukkitEventHandlerCompletionProvider : CompletionProvider<CompletionParameters>() {

override fun addCompletions(
completionParameters: CompletionParameters,
processingContext: ProcessingContext,
completionResultSet: CompletionResultSet
) {
val prefix = completionResultSet.prefixMatcher.prefix
if (!prefix.startsWith("on") || prefix.length == 2) {
return
}

val position = completionParameters.position
val containingClass = position.findContainingClass() ?: return
val project = position.project
val facade = JavaPsiFacadeEx.getInstance(project)

val eventListenerClass = facade.findClass(BukkitConstants.LISTENER_CLASS, GlobalSearchScope.allScope(project)) ?: return
if (!containingClass.isInheritor(eventListenerClass, true)) {
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)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks expensive. However it's not so easy to cache effectively because the easiest way to do that is using the PSI modification count as the cache key, and since you're in a completion provider that is changing on every keystroke. Leave it as it is for now.

.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 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(psiClass))

completionResultSet.addElement(
PrioritizedLookupElement.withPriority(element, 100.0)
)
}

completionResultSet.stopHere()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/

package com.demonwav.mcdev.platform.bukkit.completion

import com.demonwav.mcdev.util.psiType
import com.intellij.codeInsight.completion.InsertHandler
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.intention.impl.TypeExpression
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.template.TemplateManager
import com.intellij.codeInsight.template.impl.TemplateSettings
import com.intellij.codeInsight.template.impl.Variable
import com.intellij.psi.PsiClass
import com.jetbrains.rd.util.string.printToString

class BukkitEventHandlerInsertHandler(
private val psiClass: PsiClass
) : InsertHandler<LookupElement> {

override fun handleInsert(insertionContext: InsertionContext, lookupElement: LookupElement) {
insertionContext.document.deleteString(
insertionContext.startOffset,
insertionContext.tailOffset
)

val sourceTemplate = TemplateSettings.getInstance().getTemplate("event_handler", "Bukkit") ?: return
val template = sourceTemplate.copy()

val classTypeExpr = TypeExpression(insertionContext.project, listOf(psiClass.psiType))

val index = template.variables.indexOfFirst { it.name == "EVENT_CLASS" }
if (index != -1) {
template.removeVariable(index)
}

template.addVariable(Variable("EVENT_CLASS", classTypeExpr, classTypeExpr, false, false));

TemplateManager.getInstance(insertionContext.project).startTemplate(insertionContext.editor, template)
}
}
42 changes: 42 additions & 0 deletions src/main/kotlin/platform/bukkit/macro/BukkitEventNameMacro.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/

package com.demonwav.mcdev.platform.bukkit.macro

import com.intellij.codeInsight.template.Expression
import com.intellij.codeInsight.template.ExpressionContext
import com.intellij.codeInsight.template.Result
import com.intellij.codeInsight.template.TextResult
import com.intellij.codeInsight.template.macro.MacroBase

class BukkitEventNameMacro : MacroBase("bukkitEventName", "Usage: bukkitEventName(ClassType)") {
override fun calculateResult(
params: Array<out Expression?>,
context: ExpressionContext?,
quick: Boolean
): Result? {
val text = params[0]
?.calculateResult(context)
?.toString() ?: return null

val result = text.split("\\.").last().removeSuffix("Event")
return TextResult(result)
}
}
11 changes: 11 additions & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,17 @@
<library.presentationProvider
implementation="com.demonwav.mcdev.platform.bukkit.framework.ModernPaperPresentationProvider"/>

<!-- Bukkit Completion Contributor -->

<completion.contributor
language="JAVA"
implementationClass="com.demonwav.mcdev.platform.bukkit.completion.BukkitEventHandlerCompletionContributor"
order="first"/>

<defaultLiveTemplates file="/liveTemplates/Bukkit.xml"/>

<liveTemplateMacro implementation="com.demonwav.mcdev.platform.bukkit.macro.BukkitEventNameMacro"/>

<!--endregion-->

<!--region SPONGE-->
Expand Down
32 changes: 32 additions & 0 deletions src/main/resources/liveTemplates/Bukkit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!--
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 <https://www.gnu.org/licenses/>.
-->

<templateSet group="Bukkit">
<template
name="event_handler"
value="@org.bukkit.event.EventHandler&#10;public void on$NAME$($EVENT_CLASS$ $PARAM_NAME$) {&#10;$END$&#10;}"
toReformat="true"
toShortenFQNames="true"
key="live.template.bukkit.event_handler.description"
resource-bundle="messages.MinecraftDevelopment">
<variable name="NAME" expression="bukkitEventName(EVENT_CLASS)" alwaysStopAt="true"/>
<variable name="PARAM_NAME" expression="&quot;event&quot;" alwaysStopAt="true"/>
</template>
</templateSet>
2 changes: 2 additions & 0 deletions src/main/resources/messages/MinecraftDevelopment.properties
Original file line number Diff line number Diff line change
Expand Up @@ -370,3 +370,5 @@ template.provider.builtin.label=Built In
template.provider.remote.label=Remote
template.provider.local.label=Local
template.provider.zip.label=Archive

live.template.bukkit.event_handler.description=Create an event handler