-
Notifications
You must be signed in to change notification settings - Fork 220
Add completion contributor for Bukkit event handlers #2613
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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,105 @@ | ||
| /* | ||
| * 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.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<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) | ||
| .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.removeSuffix("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() | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| /* | ||
| * 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.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<LookupElement> { | ||
|
|
||
| override fun handleInsert(insertionContext: InsertionContext, lookupElement: LookupElement) { | ||
| val project = insertionContext.project | ||
| val editor = insertionContext.editor | ||
|
|
||
| val templateManager = TemplateManager.getInstance(project) | ||
| val template = templateManager.createTemplate("", "") | ||
| template.isToReformat = true | ||
|
|
||
| template.addTextSegment("@${BukkitConstants.HANDLER_ANNOTATION}\n") | ||
| template.addTextSegment("public void ") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see that the format of this template is hard-coded. Some people might want a different format, like have their methods be package-private (like I do). Some people might also want a different default method name (I've seen people put
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will read into it thanks for making me aware of this.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did play around a bit but I don't think i can implement this in a clean way. Especially with using existing logic. It definitely should be possible but I simply am not familiar enough with kotlin to do it.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might play around with this and send a patch if I succeed, no guarantees though. |
||
| 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.document.deleteString( | ||
| insertionContext.startOffset, | ||
| insertionContext.tailOffset | ||
| ) | ||
|
|
||
| templateManager.startTemplate(editor, template) | ||
|
|
||
| } | ||
| } | ||
There was a problem hiding this comment.
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.