().schedule()
+ }
- val architecturyGradleData = GradleUtil.findGradleModuleData(module)?.children
- ?.find { it.key == ArchitecturyGradleData.KEY }?.data as? ArchitecturyGradleData
- if (architecturyGradleData?.moduleType == ArchitecturyModel.ModuleType.COMMON) {
- platformKinds.add(ARCHITECTURY_LIBRARY_KIND)
- platformKinds.removeIf { it == FABRIC_LIBRARY_KIND }
- }
- return platformKinds.mapNotNullTo(mutableSetOf()) { kind -> PlatformType.fromLibraryKind(kind) }
+ override fun rootsChanged(event: ModuleRootEvent) {
+ if (event.isCausedByFileTypesChange) {
+ return
}
- private fun processLibraryMinecraftPlatformKinds(
- library: Library,
- container: LibrariesContainer,
- action: (kind: LibraryKind, version: String?) -> Boolean
- ): Boolean {
- val libraryFiles = container.getLibraryFiles(library, OrderRootType.CLASSES)
- val propertiesProcessor = object : LibraryPropertiesProcessor {
- override fun > processProperties(kind: LibraryKind, properties: P): Boolean {
- if (kind in MINECRAFT_LIBRARY_KINDS) {
- val version = (properties as? LibraryVersionProperties)?.versionString
- return action(kind, version)
- }
- return true
- }
- }
- return LibraryDetectionManager.getInstance().processProperties(libraryFiles.asList(), propertiesProcessor)
- }
+ val project = event.source as? Project ?: return
+ project.service().schedule()
}
}
diff --git a/src/main/kotlin/facet/MinecraftLibraryDetector.kt b/src/main/kotlin/facet/MinecraftLibraryDetector.kt
new file mode 100644
index 000000000..1b02e2fbf
--- /dev/null
+++ b/src/main/kotlin/facet/MinecraftLibraryDetector.kt
@@ -0,0 +1,112 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2026 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.facet
+
+import com.demonwav.mcdev.platform.PlatformType
+import com.intellij.openapi.extensions.ExtensionPointName
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.search.FilenameIndex
+import com.intellij.psi.search.GlobalSearchScope
+import java.util.*
+import java.util.jar.Manifest
+
+interface MinecraftLibraryDetector {
+ val platformType: PlatformType
+
+ fun isLibraryPresent(project: Project, scope: GlobalSearchScope): Boolean
+
+ companion object {
+ @JvmStatic
+ val EP_NAME = ExtensionPointName.create(
+ "com.demonwav.minecraft-dev.minecraftLibraryDetector",
+ )
+
+ fun isLibraryPresent(
+ platformType: PlatformType,
+ project: Project,
+ scope: GlobalSearchScope,
+ ): Boolean = EP_NAME.extensionList.any {
+ it.platformType == platformType && it.isLibraryPresent(project, scope)
+ }
+ }
+}
+
+abstract class ClassMinecraftLibraryDetector(
+ final override val platformType: PlatformType,
+ private val qualifiedClassName: String,
+) : MinecraftLibraryDetector {
+ override fun isLibraryPresent(project: Project, scope: GlobalSearchScope): Boolean =
+ JavaPsiFacade.getInstance(project).findClass(qualifiedClassName, scope) != null
+}
+
+abstract class MavenMinecraftLibraryDetector(
+ final override val platformType: PlatformType,
+ private val groupId: String,
+ private val artifactId: String,
+ private val strict: Boolean = true,
+) : MinecraftLibraryDetector {
+ private val propertiesPath = "META-INF/maven/$groupId/$artifactId/pom.properties"
+
+ final override fun isLibraryPresent(project: Project, scope: GlobalSearchScope): Boolean =
+ filesByName("pom.properties", scope).any { file ->
+ if (!file.hasRelativePath(propertiesPath)) {
+ return@any false
+ }
+
+ runCatching {
+ val properties = Properties()
+ file.inputStream.use(properties::load)
+
+ (!strict ||
+ properties.getProperty("groupId") == groupId &&
+ properties.getProperty("artifactId") == artifactId) &&
+ properties.getProperty("version") != null
+ }.getOrDefault(false)
+ }
+}
+
+fun hasLibraryFile(
+ name: String,
+ relativePath: String,
+ scope: GlobalSearchScope,
+ predicate: (VirtualFile) -> Boolean = { true },
+): Boolean = filesByName(name, scope).any { file ->
+ file.hasRelativePath(relativePath) && predicate(file)
+}
+
+fun hasLibraryManifest(scope: GlobalSearchScope, predicate: (Manifest) -> Boolean): Boolean =
+ hasLibraryFile("MANIFEST.MF", "META-INF/MANIFEST.MF", scope) { file ->
+ runCatching {
+ file.inputStream.use { input -> predicate(Manifest(input)) }
+ }.getOrDefault(false)
+ }
+
+private fun filesByName(name: String, scope: GlobalSearchScope): Collection =
+ FilenameIndex.getVirtualFilesByName(name, scope)
+
+private fun VirtualFile.hasRelativePath(relativePath: String): Boolean {
+ val normalizedPath = path.replace('\\', '/')
+ val normalizedRelativePath = relativePath.replace('\\', '/')
+ return normalizedPath.endsWith("!/$normalizedRelativePath") ||
+ normalizedPath.endsWith("/$normalizedRelativePath")
+}
diff --git a/src/main/kotlin/facet/MinecraftLibraryKinds.kt b/src/main/kotlin/facet/MinecraftLibraryKinds.kt
deleted file mode 100644
index df61bf469..000000000
--- a/src/main/kotlin/facet/MinecraftLibraryKinds.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.facet
-
-import com.demonwav.mcdev.platform.adventure.framework.ADVENTURE_LIBRARY_KIND
-import com.demonwav.mcdev.platform.architectury.framework.ARCHITECTURY_LIBRARY_KIND
-import com.demonwav.mcdev.platform.bukkit.framework.BUKKIT_LIBRARY_KIND
-import com.demonwav.mcdev.platform.bukkit.framework.PAPER_LIBRARY_KIND
-import com.demonwav.mcdev.platform.bukkit.framework.SPIGOT_LIBRARY_KIND
-import com.demonwav.mcdev.platform.bungeecord.framework.BUNGEECORD_LIBRARY_KIND
-import com.demonwav.mcdev.platform.bungeecord.framework.WATERFALL_LIBRARY_KIND
-import com.demonwav.mcdev.platform.fabric.framework.FABRIC_LIBRARY_KIND
-import com.demonwav.mcdev.platform.forge.framework.FORGE_LIBRARY_KIND
-import com.demonwav.mcdev.platform.mcp.framework.MCP_LIBRARY_KIND
-import com.demonwav.mcdev.platform.mixin.framework.MIXIN_LIBRARY_KIND
-import com.demonwav.mcdev.platform.neoforge.framework.NEOFORGE_LIBRARY_KIND
-import com.demonwav.mcdev.platform.sponge.framework.SPONGE_LIBRARY_KIND
-import com.demonwav.mcdev.platform.velocity.framework.VELOCITY_LIBRARY_KIND
-
-val MINECRAFT_LIBRARY_KINDS by lazy {
- setOf(
- BUKKIT_LIBRARY_KIND,
- SPIGOT_LIBRARY_KIND,
- PAPER_LIBRARY_KIND,
- SPONGE_LIBRARY_KIND,
- FORGE_LIBRARY_KIND,
- NEOFORGE_LIBRARY_KIND,
- FABRIC_LIBRARY_KIND,
- ARCHITECTURY_LIBRARY_KIND,
- MCP_LIBRARY_KIND,
- MIXIN_LIBRARY_KIND,
- BUNGEECORD_LIBRARY_KIND,
- WATERFALL_LIBRARY_KIND,
- VELOCITY_LIBRARY_KIND,
- ADVENTURE_LIBRARY_KIND,
- )
-}
diff --git a/src/main/kotlin/platform/PlatformType.kt b/src/main/kotlin/platform/PlatformType.kt
index 10469fb39..b6b61f6fe 100644
--- a/src/main/kotlin/platform/PlatformType.kt
+++ b/src/main/kotlin/platform/PlatformType.kt
@@ -21,34 +21,19 @@
package com.demonwav.mcdev.platform
import com.demonwav.mcdev.platform.adventure.AdventureModuleType
-import com.demonwav.mcdev.platform.adventure.framework.ADVENTURE_LIBRARY_KIND
import com.demonwav.mcdev.platform.architectury.ArchitecturyModuleType
-import com.demonwav.mcdev.platform.architectury.framework.ARCHITECTURY_LIBRARY_KIND
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.bukkit.framework.BUKKIT_LIBRARY_KIND
-import com.demonwav.mcdev.platform.bukkit.framework.PAPER_LIBRARY_KIND
-import com.demonwav.mcdev.platform.bukkit.framework.SPIGOT_LIBRARY_KIND
import com.demonwav.mcdev.platform.bungeecord.BungeeCordModuleType
import com.demonwav.mcdev.platform.bungeecord.WaterfallModuleType
-import com.demonwav.mcdev.platform.bungeecord.framework.BUNGEECORD_LIBRARY_KIND
-import com.demonwav.mcdev.platform.bungeecord.framework.WATERFALL_LIBRARY_KIND
import com.demonwav.mcdev.platform.fabric.FabricModuleType
-import com.demonwav.mcdev.platform.fabric.framework.FABRIC_LIBRARY_KIND
import com.demonwav.mcdev.platform.forge.ForgeModuleType
-import com.demonwav.mcdev.platform.forge.framework.FORGE_LIBRARY_KIND
import com.demonwav.mcdev.platform.mcp.McpModuleType
-import com.demonwav.mcdev.platform.mcp.framework.MCP_LIBRARY_KIND
import com.demonwav.mcdev.platform.mixin.MixinModuleType
-import com.demonwav.mcdev.platform.mixin.framework.MIXIN_LIBRARY_KIND
import com.demonwav.mcdev.platform.neoforge.NeoForgeModuleType
-import com.demonwav.mcdev.platform.neoforge.framework.NEOFORGE_LIBRARY_KIND
import com.demonwav.mcdev.platform.sponge.SpongeModuleType
-import com.demonwav.mcdev.platform.sponge.framework.SPONGE_LIBRARY_KIND
import com.demonwav.mcdev.platform.velocity.VelocityModuleType
-import com.demonwav.mcdev.platform.velocity.framework.VELOCITY_LIBRARY_KIND
-import com.intellij.openapi.roots.libraries.LibraryKind
enum class PlatformType(
val type: AbstractModuleType<*>,
@@ -89,23 +74,5 @@ enum class PlatformType(
.filterNotNull()
.filter { type -> type.children.isEmpty() || types.none { type.children.contains(it) } }
.toHashSet()
-
- fun fromLibraryKind(kind: LibraryKind) = when (kind) {
- BUKKIT_LIBRARY_KIND -> BUKKIT
- SPIGOT_LIBRARY_KIND -> SPIGOT
- PAPER_LIBRARY_KIND -> PAPER
- SPONGE_LIBRARY_KIND -> SPONGE
- ARCHITECTURY_LIBRARY_KIND -> ARCHITECTURY
- FORGE_LIBRARY_KIND -> FORGE
- FABRIC_LIBRARY_KIND -> FABRIC
- MCP_LIBRARY_KIND -> MCP
- MIXIN_LIBRARY_KIND -> MIXIN
- BUNGEECORD_LIBRARY_KIND -> BUNGEECORD
- WATERFALL_LIBRARY_KIND -> WATERFALL
- VELOCITY_LIBRARY_KIND -> VELOCITY
- ADVENTURE_LIBRARY_KIND -> ADVENTURE
- NEOFORGE_LIBRARY_KIND -> NEOFORGE
- else -> null
- }
}
}
diff --git a/src/main/kotlin/platform/adventure/framework/AdventureLibraryKind.kt b/src/main/kotlin/platform/adventure/framework/AdventureLibraryKind.kt
deleted file mode 100644
index 2049d216e..000000000
--- a/src/main/kotlin/platform/adventure/framework/AdventureLibraryKind.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.adventure.framework
-
-import com.demonwav.mcdev.util.libraryKind
-import com.intellij.openapi.roots.libraries.LibraryKind
-
-val ADVENTURE_LIBRARY_KIND: LibraryKind by libraryKind("adventure-api")
diff --git a/src/main/kotlin/platform/adventure/framework/AdventurePresentationProvider.kt b/src/main/kotlin/platform/adventure/framework/AdventurePresentationProvider.kt
index a741e9058..0fdfffe1d 100644
--- a/src/main/kotlin/platform/adventure/framework/AdventurePresentationProvider.kt
+++ b/src/main/kotlin/platform/adventure/framework/AdventurePresentationProvider.kt
@@ -20,32 +20,21 @@
package com.demonwav.mcdev.platform.adventure.framework
-import com.demonwav.mcdev.asset.PlatformAssets
+import com.demonwav.mcdev.facet.MinecraftLibraryDetector
+import com.demonwav.mcdev.facet.hasLibraryManifest
+import com.demonwav.mcdev.platform.PlatformType
import com.demonwav.mcdev.platform.adventure.AdventureConstants
import com.demonwav.mcdev.util.get
-import com.demonwav.mcdev.util.manifest
-import com.intellij.openapi.roots.libraries.DummyLibraryProperties
-import com.intellij.openapi.roots.libraries.LibraryPresentationProvider
-import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.openapi.project.Project
+import com.intellij.psi.search.GlobalSearchScope
import java.util.jar.Attributes.Name.SPECIFICATION_TITLE
-import java.util.jar.Manifest
-class AdventurePresentationProvider : LibraryPresentationProvider(ADVENTURE_LIBRARY_KIND) {
- override fun getIcon(properties: DummyLibraryProperties?) = PlatformAssets.ADVENTURE_ICON
+class AdventureLibraryDetector : MinecraftLibraryDetector {
+ override val platformType = PlatformType.ADVENTURE
- override fun detect(classesRoots: MutableList): DummyLibraryProperties? {
- for (classesRoot in classesRoots) {
- val manifest = classesRoot.manifest ?: continue
-
- if (findUsingManifest(manifest)) {
- return DummyLibraryProperties.INSTANCE
- }
- }
-
- return null
- }
-
- private fun findUsingManifest(manifest: Manifest): Boolean =
+ override fun isLibraryPresent(project: Project, scope: GlobalSearchScope): Boolean =
+ hasLibraryManifest(scope) { manifest ->
manifest[SPECIFICATION_TITLE] == AdventureConstants.API_SPECIFICATION_TITLE ||
manifest["Automatic-Module-Name"] == AdventureConstants.API_MODULE_ID
+ }
}
diff --git a/src/main/kotlin/platform/architectury/framework/ArchitecturyLibraryKind.kt b/src/main/kotlin/platform/architectury/framework/ArchitecturyLibraryKind.kt
deleted file mode 100644
index d1e86b18a..000000000
--- a/src/main/kotlin/platform/architectury/framework/ArchitecturyLibraryKind.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.architectury.framework
-
-import com.demonwav.mcdev.util.libraryKind
-import com.intellij.openapi.roots.libraries.LibraryKind
-
-val ARCHITECTURY_LIBRARY_KIND: LibraryKind by libraryKind("architectury-api")
diff --git a/src/main/kotlin/platform/architectury/framework/ArchitecturyPresentationProvider.kt b/src/main/kotlin/platform/architectury/framework/ArchitecturyPresentationProvider.kt
index a69aff348..bc0cc0d82 100644
--- a/src/main/kotlin/platform/architectury/framework/ArchitecturyPresentationProvider.kt
+++ b/src/main/kotlin/platform/architectury/framework/ArchitecturyPresentationProvider.kt
@@ -20,30 +20,15 @@
package com.demonwav.mcdev.platform.architectury.framework
-import com.demonwav.mcdev.asset.PlatformAssets
-import com.demonwav.mcdev.util.localFile
-import com.intellij.framework.library.LibraryVersionProperties
-import com.intellij.openapi.roots.libraries.LibraryPresentationProvider
-import com.intellij.openapi.vfs.VirtualFile
-import java.util.jar.JarFile
+import com.demonwav.mcdev.facet.MinecraftLibraryDetector
+import com.demonwav.mcdev.facet.hasLibraryFile
+import com.demonwav.mcdev.platform.PlatformType
+import com.intellij.openapi.project.Project
+import com.intellij.psi.search.GlobalSearchScope
-class ArchitecturyPresentationProvider : LibraryPresentationProvider(
- ARCHITECTURY_LIBRARY_KIND,
-) {
- override fun getIcon(properties: LibraryVersionProperties?) = PlatformAssets.ARCHITECTURY_ICON
+class ArchitecturyLibraryDetector : MinecraftLibraryDetector {
+ override val platformType = PlatformType.ARCHITECTURY
- override fun detect(classesRoots: MutableList): LibraryVersionProperties? {
- for (classesRoot in classesRoots) {
- if (!classesRoot.name.endsWith(".jar")) {
- continue
- }
- runCatching {
- JarFile(classesRoot.localFile).use { jar ->
- jar.getEntry("architectury.common.json") ?: return@runCatching
- return LibraryVersionProperties()
- }
- }
- }
- return null
- }
+ override fun isLibraryPresent(project: Project, scope: GlobalSearchScope): Boolean =
+ hasLibraryFile("architectury.common.json", "architectury.common.json", scope)
}
diff --git a/src/main/kotlin/platform/bukkit/framework/BukkitLibraryKind.kt b/src/main/kotlin/platform/bukkit/framework/BukkitLibraryKind.kt
deleted file mode 100644
index 4bb6f9144..000000000
--- a/src/main/kotlin/platform/bukkit/framework/BukkitLibraryKind.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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.framework
-
-import com.demonwav.mcdev.util.libraryKind
-import com.intellij.openapi.roots.libraries.LibraryKind
-
-val BUKKIT_LIBRARY_KIND: LibraryKind by libraryKind("bukkit-api")
-val SPIGOT_LIBRARY_KIND: LibraryKind by libraryKind("spigot-api")
-val PAPER_LIBRARY_KIND: LibraryKind by libraryKind("paper-api")
diff --git a/src/main/kotlin/platform/bukkit/framework/BukkitPresentationProviders.kt b/src/main/kotlin/platform/bukkit/framework/BukkitPresentationProviders.kt
index d2069db6b..7121cc84e 100644
--- a/src/main/kotlin/platform/bukkit/framework/BukkitPresentationProviders.kt
+++ b/src/main/kotlin/platform/bukkit/framework/BukkitPresentationProviders.kt
@@ -20,24 +20,17 @@
package com.demonwav.mcdev.platform.bukkit.framework
-import com.demonwav.mcdev.asset.PlatformAssets
-import com.demonwav.mcdev.facet.MavenLibraryPresentationProvider
-import com.intellij.framework.library.LibraryVersionProperties
+import com.demonwav.mcdev.facet.MavenMinecraftLibraryDetector
+import com.demonwav.mcdev.platform.PlatformType
-class BukkitPresentationProvider : MavenLibraryPresentationProvider(BUKKIT_LIBRARY_KIND, "org.bukkit", "bukkit") {
- override fun getIcon(properties: LibraryVersionProperties?) = PlatformAssets.BUKKIT_ICON
-}
+class BukkitLibraryDetector :
+ MavenMinecraftLibraryDetector(PlatformType.BUKKIT, "org.bukkit", "bukkit")
-class SpigotPresentationProvider : MavenLibraryPresentationProvider(SPIGOT_LIBRARY_KIND, "org.spigotmc", "spigot-api") {
- override fun getIcon(properties: LibraryVersionProperties?) = PlatformAssets.SPIGOT_ICON
-}
+class SpigotLibraryDetector :
+ MavenMinecraftLibraryDetector(PlatformType.SPIGOT, "org.spigotmc", "spigot-api")
-class OldPaperPresentationProvider :
- MavenLibraryPresentationProvider(PAPER_LIBRARY_KIND, "com.destroystokyo.paper", "paper-api") {
- override fun getIcon(properties: LibraryVersionProperties?) = PlatformAssets.PAPER_ICON
-}
+class OldPaperLibraryDetector :
+ MavenMinecraftLibraryDetector(PlatformType.PAPER, "com.destroystokyo.paper", "paper-api")
-class PaperPresentationProvider :
- MavenLibraryPresentationProvider(PAPER_LIBRARY_KIND, "io.papermc.paper", "paper-api", false) {
- override fun getIcon(properties: LibraryVersionProperties?) = PlatformAssets.PAPER_ICON
-}
+class PaperLibraryDetector :
+ MavenMinecraftLibraryDetector(PlatformType.PAPER, "io.papermc.paper", "paper-api", false)
diff --git a/src/main/kotlin/platform/bukkit/framework/ModernPaperPresentationProvider.kt b/src/main/kotlin/platform/bukkit/framework/ModernPaperPresentationProvider.kt
index 1c42c02e7..f86d11af8 100644
--- a/src/main/kotlin/platform/bukkit/framework/ModernPaperPresentationProvider.kt
+++ b/src/main/kotlin/platform/bukkit/framework/ModernPaperPresentationProvider.kt
@@ -20,36 +20,17 @@
package com.demonwav.mcdev.platform.bukkit.framework
-import com.demonwav.mcdev.asset.PlatformAssets
-import com.demonwav.mcdev.util.localFile
-import com.google.gson.Gson
-import com.intellij.framework.library.LibraryVersionProperties
-import com.intellij.openapi.roots.libraries.LibraryPresentationProvider
-import com.intellij.openapi.vfs.VirtualFile
-import java.util.jar.JarFile
+import com.demonwav.mcdev.facet.ClassMinecraftLibraryDetector
+import com.demonwav.mcdev.facet.hasLibraryFile
+import com.demonwav.mcdev.platform.PlatformType
+import com.intellij.openapi.project.Project
+import com.intellij.psi.search.GlobalSearchScope
-class ModernPaperPresentationProvider : LibraryPresentationProvider(PAPER_LIBRARY_KIND) {
-
- override fun getIcon(properties: LibraryVersionProperties?) = PlatformAssets.PAPER_ICON
-
- override fun detect(classesRoots: List): LibraryVersionProperties? {
- for (classesRoot in classesRoots) {
- if (!classesRoot.name.endsWith(".jar")) {
- continue
- }
- runCatching {
- JarFile(classesRoot.localFile).use { jar ->
- jar.getEntry("io/papermc/paper/ServerBuildInfo.class") ?: return@runCatching
- val versionJson = jar.getEntry("apiVersioning.json") ?: return@runCatching
- jar.getInputStream(versionJson).use { stream ->
- stream.reader().use { reader ->
- val map = Gson().fromJson(reader, Map::class.java)
- return LibraryVersionProperties(map["currentApiVersion"] as String)
- }
- }
- }
- }
- }
- return null
- }
+class ModernPaperLibraryDetector : ClassMinecraftLibraryDetector(
+ PlatformType.PAPER,
+ "io.papermc.paper.ServerBuildInfo",
+) {
+ override fun isLibraryPresent(project: Project, scope: GlobalSearchScope): Boolean =
+ super.isLibraryPresent(project, scope) &&
+ hasLibraryFile("apiVersioning.json", "apiVersioning.json", scope)
}
diff --git a/src/main/kotlin/platform/bungeecord/framework/BungeeCordLibraryKind.kt b/src/main/kotlin/platform/bungeecord/framework/BungeeCordLibraryKind.kt
deleted file mode 100644
index 7f8ceaf45..000000000
--- a/src/main/kotlin/platform/bungeecord/framework/BungeeCordLibraryKind.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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.bungeecord.framework
-
-import com.demonwav.mcdev.util.libraryKind
-import com.intellij.openapi.roots.libraries.LibraryKind
-
-val BUNGEECORD_LIBRARY_KIND: LibraryKind by libraryKind("bungeecord-api")
-val WATERFALL_LIBRARY_KIND: LibraryKind by libraryKind("waterfall-api")
diff --git a/src/main/kotlin/platform/bungeecord/framework/BungeeCordPresentationProvider.kt b/src/main/kotlin/platform/bungeecord/framework/BungeeCordPresentationProvider.kt
index 73002a77a..5988e0f89 100644
--- a/src/main/kotlin/platform/bungeecord/framework/BungeeCordPresentationProvider.kt
+++ b/src/main/kotlin/platform/bungeecord/framework/BungeeCordPresentationProvider.kt
@@ -20,16 +20,11 @@
package com.demonwav.mcdev.platform.bungeecord.framework
-import com.demonwav.mcdev.asset.PlatformAssets
-import com.demonwav.mcdev.facet.MavenLibraryPresentationProvider
-import com.intellij.framework.library.LibraryVersionProperties
+import com.demonwav.mcdev.facet.MavenMinecraftLibraryDetector
+import com.demonwav.mcdev.platform.PlatformType
-class BungeeCordPresentationProvider :
- MavenLibraryPresentationProvider(BUNGEECORD_LIBRARY_KIND, "net.md-5", "bungeecord-api") {
- override fun getIcon(properties: LibraryVersionProperties?) = PlatformAssets.BUNGEECORD_ICON
-}
+class BungeeCordLibraryDetector :
+ MavenMinecraftLibraryDetector(PlatformType.BUNGEECORD, "net.md-5", "bungeecord-api")
-class WaterfallPresentationProvider :
- MavenLibraryPresentationProvider(WATERFALL_LIBRARY_KIND, "io.github.waterfallmc", "waterfall-api") {
- override fun getIcon(properties: LibraryVersionProperties?) = PlatformAssets.WATERFALL_ICON
-}
+class WaterfallLibraryDetector :
+ MavenMinecraftLibraryDetector(PlatformType.WATERFALL, "io.github.waterfallmc", "waterfall-api")
diff --git a/src/main/kotlin/platform/fabric/framework/FabricLibraryKind.kt b/src/main/kotlin/platform/fabric/framework/FabricLibraryKind.kt
deleted file mode 100644
index f809e248e..000000000
--- a/src/main/kotlin/platform/fabric/framework/FabricLibraryKind.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.fabric.framework
-
-import com.demonwav.mcdev.util.libraryKind
-import com.intellij.openapi.roots.libraries.LibraryKind
-
-val FABRIC_LIBRARY_KIND: LibraryKind by libraryKind("fabric-library")
diff --git a/src/main/kotlin/platform/fabric/framework/FabricPresentationProvider.kt b/src/main/kotlin/platform/fabric/framework/FabricPresentationProvider.kt
index 11c9ebe1b..6dbff9e78 100644
--- a/src/main/kotlin/platform/fabric/framework/FabricPresentationProvider.kt
+++ b/src/main/kotlin/platform/fabric/framework/FabricPresentationProvider.kt
@@ -20,29 +20,10 @@
package com.demonwav.mcdev.platform.fabric.framework
-import com.demonwav.mcdev.asset.PlatformAssets
-import com.demonwav.mcdev.util.localFile
-import com.intellij.framework.library.LibraryVersionProperties
-import com.intellij.openapi.roots.libraries.LibraryPresentationProvider
-import com.intellij.openapi.vfs.VirtualFile
-import java.util.jar.JarFile
+import com.demonwav.mcdev.facet.ClassMinecraftLibraryDetector
+import com.demonwav.mcdev.platform.PlatformType
-class FabricPresentationProvider : LibraryPresentationProvider(FABRIC_LIBRARY_KIND) {
-
- override fun getIcon(properties: LibraryVersionProperties?) = PlatformAssets.FABRIC_ICON
-
- override fun detect(classesRoots: MutableList): LibraryVersionProperties? {
- for (classesRoot in classesRoots) {
- if (!classesRoot.name.endsWith(".jar")) {
- continue
- }
- runCatching {
- JarFile(classesRoot.localFile).use { jar ->
- jar.getEntry("net/fabricmc/loader/api/FabricLoader.class") ?: return@runCatching
- return LibraryVersionProperties()
- }
- }
- }
- return null
- }
-}
+class FabricLibraryDetector : ClassMinecraftLibraryDetector(
+ PlatformType.FABRIC,
+ "net.fabricmc.loader.api.FabricLoader",
+)
diff --git a/src/main/kotlin/platform/forge/framework/ForgeLibraryKind.kt b/src/main/kotlin/platform/forge/framework/ForgeLibraryKind.kt
deleted file mode 100644
index 9b55ec277..000000000
--- a/src/main/kotlin/platform/forge/framework/ForgeLibraryKind.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.forge.framework
-
-import com.demonwav.mcdev.util.libraryKind
-import com.intellij.openapi.roots.libraries.LibraryKind
-
-val FORGE_LIBRARY_KIND: LibraryKind by libraryKind("forge-library")
diff --git a/src/main/kotlin/platform/forge/framework/ForgePresentationProvider.kt b/src/main/kotlin/platform/forge/framework/ForgePresentationProvider.kt
index 62520cabe..98f0044c0 100644
--- a/src/main/kotlin/platform/forge/framework/ForgePresentationProvider.kt
+++ b/src/main/kotlin/platform/forge/framework/ForgePresentationProvider.kt
@@ -20,24 +20,8 @@
package com.demonwav.mcdev.platform.forge.framework
-import com.demonwav.mcdev.asset.PlatformAssets
+import com.demonwav.mcdev.facet.ClassMinecraftLibraryDetector
+import com.demonwav.mcdev.platform.PlatformType
import com.demonwav.mcdev.platform.forge.util.ForgeConstants
-import com.demonwav.mcdev.util.localFile
-import com.intellij.framework.library.LibraryVersionProperties
-import com.intellij.openapi.roots.libraries.LibraryPresentationProvider
-import com.intellij.openapi.util.io.JarUtil
-import com.intellij.openapi.vfs.VirtualFile
-class ForgePresentationProvider : LibraryPresentationProvider(FORGE_LIBRARY_KIND) {
-
- override fun getIcon(properties: LibraryVersionProperties?) = PlatformAssets.FORGE_ICON
-
- override fun detect(classesRoots: List): LibraryVersionProperties? {
- for (classesRoot in classesRoots) {
- if (JarUtil.containsClass(classesRoot.localFile, ForgeConstants.MOD_ANNOTATION)) {
- return LibraryVersionProperties()
- }
- }
- return null
- }
-}
+class ForgeLibraryDetector : ClassMinecraftLibraryDetector(PlatformType.FORGE, ForgeConstants.MOD_ANNOTATION)
diff --git a/src/main/kotlin/platform/mcp/framework/McpLibraryKind.kt b/src/main/kotlin/platform/mcp/framework/McpLibraryKind.kt
deleted file mode 100644
index 6a92603ac..000000000
--- a/src/main/kotlin/platform/mcp/framework/McpLibraryKind.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.mcp.framework
-
-import com.demonwav.mcdev.util.libraryKind
-import com.intellij.openapi.roots.libraries.LibraryKind
-
-val MCP_LIBRARY_KIND: LibraryKind by libraryKind("mcp-library")
diff --git a/src/main/kotlin/platform/mcp/framework/McpPresentationProvider.kt b/src/main/kotlin/platform/mcp/framework/McpPresentationProvider.kt
index 29cfb045c..6af00106b 100644
--- a/src/main/kotlin/platform/mcp/framework/McpPresentationProvider.kt
+++ b/src/main/kotlin/platform/mcp/framework/McpPresentationProvider.kt
@@ -20,26 +20,8 @@
package com.demonwav.mcdev.platform.mcp.framework
-import com.demonwav.mcdev.asset.PlatformAssets
+import com.demonwav.mcdev.facet.ClassMinecraftLibraryDetector
+import com.demonwav.mcdev.platform.PlatformType
import com.demonwav.mcdev.platform.mcp.util.McpConstants
-import com.demonwav.mcdev.util.localFile
-import com.intellij.framework.library.LibraryVersionProperties
-import com.intellij.openapi.roots.libraries.LibraryPresentationProvider
-import com.intellij.openapi.util.io.JarUtil
-import com.intellij.openapi.vfs.VirtualFile
-class McpPresentationProvider : LibraryPresentationProvider(MCP_LIBRARY_KIND) {
-
- override fun getIcon(properties: LibraryVersionProperties?) = PlatformAssets.MCP_ICON
-
- override fun detect(classesRoots: List): LibraryVersionProperties? {
- for (classesRoot in classesRoots) {
- val file = classesRoot.localFile
-
- if (JarUtil.containsClass(file, McpConstants.MINECRAFT_SERVER)) {
- return LibraryVersionProperties()
- }
- }
- return null
- }
-}
+class McpLibraryDetector : ClassMinecraftLibraryDetector(PlatformType.MCP, McpConstants.MINECRAFT_SERVER)
diff --git a/src/main/kotlin/platform/mixin/framework/MixinLibraryKind.kt b/src/main/kotlin/platform/mixin/framework/MixinLibraryKind.kt
deleted file mode 100644
index d71763795..000000000
--- a/src/main/kotlin/platform/mixin/framework/MixinLibraryKind.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.mixin.framework
-
-import com.demonwav.mcdev.util.libraryKind
-import com.intellij.openapi.roots.libraries.LibraryKind
-
-val MIXIN_LIBRARY_KIND: LibraryKind by libraryKind("mixin-library")
diff --git a/src/main/kotlin/platform/mixin/framework/MixinPresentationProvider.kt b/src/main/kotlin/platform/mixin/framework/MixinPresentationProvider.kt
index 23af5dabc..58cc17c88 100644
--- a/src/main/kotlin/platform/mixin/framework/MixinPresentationProvider.kt
+++ b/src/main/kotlin/platform/mixin/framework/MixinPresentationProvider.kt
@@ -20,32 +20,21 @@
package com.demonwav.mcdev.platform.mixin.framework
-import com.demonwav.mcdev.asset.PlatformAssets
+import com.demonwav.mcdev.facet.MinecraftLibraryDetector
+import com.demonwav.mcdev.facet.hasLibraryFile
+import com.demonwav.mcdev.facet.hasLibraryManifest
+import com.demonwav.mcdev.platform.PlatformType
import com.demonwav.mcdev.platform.mixin.util.MixinConstants
import com.demonwav.mcdev.util.get
-import com.demonwav.mcdev.util.manifest
-import com.intellij.framework.library.LibraryVersionProperties
-import com.intellij.openapi.roots.libraries.LibraryPresentationProvider
-import com.intellij.openapi.vfs.VirtualFile
-import java.util.jar.Attributes.Name.IMPLEMENTATION_VERSION
+import com.intellij.openapi.project.Project
+import com.intellij.psi.search.GlobalSearchScope
-class MixinPresentationProvider : LibraryPresentationProvider(MIXIN_LIBRARY_KIND) {
+class MixinLibraryDetector : MinecraftLibraryDetector {
+ override val platformType = PlatformType.MIXIN
private val hintFilePath = "META-INF/services/org.spongepowered.asm.service.IMixinService"
- override fun getIcon(properties: LibraryVersionProperties?) = PlatformAssets.MIXIN_ICON
-
- override fun detect(classesRoots: List): LibraryVersionProperties? {
- for (classesRoot in classesRoots) {
- val manifest = classesRoot.manifest
- if (manifest?.get("Agent-Class") != MixinConstants.Classes.MIXIN_AGENT &&
- classesRoot.findFileByRelativePath(hintFilePath) == null
- ) {
- continue
- }
-
- return LibraryVersionProperties(manifest?.get(IMPLEMENTATION_VERSION))
- }
- return null
- }
+ override fun isLibraryPresent(project: Project, scope: GlobalSearchScope): Boolean =
+ hasLibraryManifest(scope) { it["Agent-Class"] == MixinConstants.Classes.MIXIN_AGENT } ||
+ hasLibraryFile("org.spongepowered.asm.service.IMixinService", hintFilePath, scope)
}
diff --git a/src/main/kotlin/platform/neoforge/framework/NeoForgeLibraryKind.kt b/src/main/kotlin/platform/neoforge/framework/NeoForgeLibraryKind.kt
deleted file mode 100644
index 2154c31f6..000000000
--- a/src/main/kotlin/platform/neoforge/framework/NeoForgeLibraryKind.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.neoforge.framework
-
-import com.demonwav.mcdev.util.libraryKind
-import com.intellij.openapi.roots.libraries.LibraryKind
-
-val NEOFORGE_LIBRARY_KIND: LibraryKind by libraryKind("neoforge-library")
diff --git a/src/main/kotlin/platform/neoforge/framework/NeoForgePresentationProvider.kt b/src/main/kotlin/platform/neoforge/framework/NeoForgePresentationProvider.kt
index eaab10b8c..746aa0aad 100644
--- a/src/main/kotlin/platform/neoforge/framework/NeoForgePresentationProvider.kt
+++ b/src/main/kotlin/platform/neoforge/framework/NeoForgePresentationProvider.kt
@@ -20,24 +20,9 @@
package com.demonwav.mcdev.platform.neoforge.framework
-import com.demonwav.mcdev.asset.PlatformAssets
+import com.demonwav.mcdev.facet.ClassMinecraftLibraryDetector
+import com.demonwav.mcdev.platform.PlatformType
import com.demonwav.mcdev.platform.neoforge.util.NeoForgeConstants
-import com.demonwav.mcdev.util.localFile
-import com.intellij.framework.library.LibraryVersionProperties
-import com.intellij.openapi.roots.libraries.LibraryPresentationProvider
-import com.intellij.openapi.util.io.JarUtil
-import com.intellij.openapi.vfs.VirtualFile
-class NeoForgePresentationProvider : LibraryPresentationProvider(NEOFORGE_LIBRARY_KIND) {
-
- override fun getIcon(properties: LibraryVersionProperties?) = PlatformAssets.NEOFORGE_ICON
-
- override fun detect(classesRoots: List): LibraryVersionProperties? {
- for (classesRoot in classesRoots) {
- if (JarUtil.containsClass(classesRoot.localFile, NeoForgeConstants.MOD_ANNOTATION)) {
- return LibraryVersionProperties()
- }
- }
- return null
- }
-}
+class NeoForgeLibraryDetector :
+ ClassMinecraftLibraryDetector(PlatformType.NEOFORGE, NeoForgeConstants.MOD_ANNOTATION)
diff --git a/src/main/kotlin/platform/sponge/framework/SpongeLibraryKind.kt b/src/main/kotlin/platform/sponge/framework/SpongeLibraryKind.kt
deleted file mode 100644
index f85044232..000000000
--- a/src/main/kotlin/platform/sponge/framework/SpongeLibraryKind.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.sponge.framework
-
-import com.demonwav.mcdev.util.libraryKind
-import com.intellij.openapi.roots.libraries.LibraryKind
-
-val SPONGE_LIBRARY_KIND: LibraryKind by libraryKind("sponge-api")
diff --git a/src/main/kotlin/platform/sponge/framework/SpongePresentationProvider.kt b/src/main/kotlin/platform/sponge/framework/SpongePresentationProvider.kt
index 4b7f498ba..f6d690b61 100644
--- a/src/main/kotlin/platform/sponge/framework/SpongePresentationProvider.kt
+++ b/src/main/kotlin/platform/sponge/framework/SpongePresentationProvider.kt
@@ -20,25 +20,22 @@
package com.demonwav.mcdev.platform.sponge.framework
-import com.demonwav.mcdev.asset.PlatformAssets
+import com.demonwav.mcdev.facet.MinecraftLibraryDetector
+import com.demonwav.mcdev.facet.hasLibraryManifest
+import com.demonwav.mcdev.platform.PlatformType
import com.demonwav.mcdev.util.get
-import com.demonwav.mcdev.util.manifest
-import com.intellij.framework.library.LibraryVersionProperties
-import com.intellij.openapi.roots.libraries.LibraryPresentationProvider
-import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.openapi.project.Project
+import com.intellij.psi.search.GlobalSearchScope
import java.util.jar.Attributes.Name.IMPLEMENTATION_TITLE
import java.util.jar.Attributes.Name.IMPLEMENTATION_VERSION
import java.util.jar.Attributes.Name.SPECIFICATION_TITLE
import java.util.jar.Attributes.Name.SPECIFICATION_VERSION
-class SpongePresentationProvider : LibraryPresentationProvider(SPONGE_LIBRARY_KIND) {
-
- override fun getIcon(properties: LibraryVersionProperties?) = PlatformAssets.SPONGE_ICON
-
- override fun detect(classesRoots: List): LibraryVersionProperties? {
- for (classesRoot in classesRoots) {
- val manifest = classesRoot.manifest ?: continue
+class SpongeLibraryDetector : MinecraftLibraryDetector {
+ override val platformType = PlatformType.SPONGE
+ override fun isLibraryPresent(project: Project, scope: GlobalSearchScope): Boolean =
+ hasLibraryManifest(scope) { manifest ->
loop@ for (title in setOf("SpongeAPI", "spongeapi")) {
val versionAttribute = when (title) {
manifest[IMPLEMENTATION_TITLE] -> IMPLEMENTATION_VERSION
@@ -46,10 +43,10 @@ class SpongePresentationProvider : LibraryPresentationProvider continue@loop
}
- val version = manifest[versionAttribute] ?: continue
- return LibraryVersionProperties(version)
+ if (manifest[versionAttribute] != null) {
+ return@hasLibraryManifest true
+ }
}
+ false
}
- return null
- }
}
diff --git a/src/main/kotlin/platform/velocity/framework/VelocityLibraryKind.kt b/src/main/kotlin/platform/velocity/framework/VelocityLibraryKind.kt
deleted file mode 100644
index 87f75a5d6..000000000
--- a/src/main/kotlin/platform/velocity/framework/VelocityLibraryKind.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.velocity.framework
-
-import com.demonwav.mcdev.util.libraryKind
-import com.intellij.openapi.roots.libraries.LibraryKind
-
-val VELOCITY_LIBRARY_KIND: LibraryKind by libraryKind("velocity-api")
diff --git a/src/main/kotlin/platform/velocity/framework/VelocityPresentationProvider.kt b/src/main/kotlin/platform/velocity/framework/VelocityPresentationProvider.kt
index e5ac4a0cc..352f0eaf0 100644
--- a/src/main/kotlin/platform/velocity/framework/VelocityPresentationProvider.kt
+++ b/src/main/kotlin/platform/velocity/framework/VelocityPresentationProvider.kt
@@ -20,37 +20,24 @@
package com.demonwav.mcdev.platform.velocity.framework
-import com.demonwav.mcdev.asset.PlatformAssets
-import com.demonwav.mcdev.util.localFile
-import com.intellij.framework.library.LibraryVersionProperties
-import com.intellij.openapi.roots.libraries.LibraryPresentationProvider
-import com.intellij.openapi.util.io.JarUtil
-import com.intellij.openapi.vfs.VirtualFile
-import java.io.BufferedReader
-import java.util.jar.JarFile
+import com.demonwav.mcdev.facet.ClassMinecraftLibraryDetector
+import com.demonwav.mcdev.facet.hasLibraryFile
+import com.demonwav.mcdev.platform.PlatformType
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.readText
+import com.intellij.psi.search.GlobalSearchScope
-class VelocityPresentationProvider : LibraryPresentationProvider(VELOCITY_LIBRARY_KIND) {
- override fun getIcon(properties: LibraryVersionProperties?) = PlatformAssets.VELOCITY_ICON
+class VelocityLibraryDetector : ClassMinecraftLibraryDetector(
+ PlatformType.VELOCITY,
+ "com.velocitypowered.api.proxy.ProxyServer",
+) {
+ private val annotationProcessorsPath = "META-INF/services/javax.annotation.processing.Processor"
- override fun detect(classesRoots: MutableList): LibraryVersionProperties? {
- for (classesRoot in classesRoots) {
- runCatching {
- if (JarUtil.containsClass(classesRoot.localFile, "com.velocitypowered.api.proxy.ProxyServer")) {
- return LibraryVersionProperties()
- }
-
- // Velocity API jar has no Manifest entries, so we search for their annotation processor instead
- val registeredAPs = JarFile(classesRoot.localFile).use { jar ->
- val aps = jar.getEntry("META-INF/services/javax.annotation.processing.Processor")
- ?: return@use null
- jar.getInputStream(aps).bufferedReader().use(BufferedReader::readLines)
- } ?: return@runCatching
-
- if (registeredAPs.contains("com.velocitypowered.api.plugin.ap.PluginAnnotationProcessor")) {
- return LibraryVersionProperties()
+ override fun isLibraryPresent(project: Project, scope: GlobalSearchScope): Boolean =
+ super.isLibraryPresent(project, scope) ||
+ hasLibraryFile("javax.annotation.processing.Processor", annotationProcessorsPath, scope) { file ->
+ file.readText().lineSequence().any {
+ it.trim() == "com.velocitypowered.api.plugin.ap.PluginAnnotationProcessor"
}
}
- }
- return null
- }
}
diff --git a/src/main/kotlin/translations/identification/TranslationAnnotationsLocationProvider.kt b/src/main/kotlin/translations/identification/TranslationAnnotationsLocationProvider.kt
index 187646e37..e5a854fa7 100644
--- a/src/main/kotlin/translations/identification/TranslationAnnotationsLocationProvider.kt
+++ b/src/main/kotlin/translations/identification/TranslationAnnotationsLocationProvider.kt
@@ -20,13 +20,15 @@
package com.demonwav.mcdev.translations.identification
-import com.demonwav.mcdev.platform.mcp.framework.MCP_LIBRARY_KIND
+import com.demonwav.mcdev.facet.MinecraftLibraryDetector
+import com.demonwav.mcdev.platform.PlatformType
import com.intellij.codeInsight.externalAnnotation.location.AnnotationsLocation
import com.intellij.codeInsight.externalAnnotation.location.AnnotationsLocationProvider
+import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.roots.libraries.Library
-import com.intellij.openapi.roots.ui.configuration.libraries.LibraryPresentationManager
+import com.intellij.psi.search.GlobalSearchScopes
class TranslationAnnotationsLocationProvider : AnnotationsLocationProvider {
override fun getLocations(
@@ -36,11 +38,17 @@ class TranslationAnnotationsLocationProvider : AnnotationsLocationProvider {
groupId: String?,
version: String?
): Collection {
- val isMinecraftLibrary = LibraryPresentationManager.getInstance().isLibraryOfKind(
- library.getFiles(OrderRootType.CLASSES).toList(),
- MCP_LIBRARY_KIND
- )
- if (isMinecraftLibrary) {
+ if (DumbService.isDumb(project)) {
+ return emptyList()
+ }
+
+ val libraryRoots = library.getFiles(OrderRootType.CLASSES)
+ if (libraryRoots.isEmpty()) {
+ return emptyList()
+ }
+
+ val libraryScope = GlobalSearchScopes.directoriesScope(project, true, *libraryRoots)
+ if (MinecraftLibraryDetector.isLibraryPresent(PlatformType.MCP, project, libraryScope)) {
return listOf(TranslationExternalAnnotationsArtifactsResolver.Util.fakeMavenLocation)
}
return emptyList()
diff --git a/src/main/kotlin/util/utils.kt b/src/main/kotlin/util/utils.kt
index dfa6d9087..a93cb493e 100644
--- a/src/main/kotlin/util/utils.kt
+++ b/src/main/kotlin/util/utils.kt
@@ -33,8 +33,6 @@ import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
-import com.intellij.openapi.roots.libraries.LibraryKind
-import com.intellij.openapi.roots.libraries.LibraryKindRegistry
import com.intellij.openapi.util.Computable
import com.intellij.openapi.util.Condition
import com.intellij.openapi.util.Ref
@@ -46,8 +44,7 @@ import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiUtil
import java.lang.invoke.MethodHandles
-import java.util.Locale
-import java.util.Locale.getDefault
+import java.util.*
import java.util.concurrent.CancellationException
import java.util.regex.PatternSyntaxException
import kotlin.math.min
@@ -326,9 +323,6 @@ inline fun Iterable<*>.firstOfType(): T? {
return this.firstOrNull { it is T } as? T
}
-fun libraryKind(id: String): Lazy =
- lazy { LibraryKindRegistry.getInstance().findKindById(id) ?: LibraryKind.create(id) }
-
fun String.capitalize(): String =
replaceFirstChar {
if (it.isLowerCase()) {
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index 5c910c6ca..4c161f9e0 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -66,6 +66,8 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -358,7 +393,7 @@
-
+
@@ -509,9 +544,6 @@
-
-
@@ -525,16 +557,6 @@
-
-
-
-
-
@@ -566,8 +588,6 @@
-
-
-
@@ -601,18 +618,12 @@
-
-
-
-
@@ -622,8 +633,6 @@
-
@@ -634,9 +643,6 @@
-
-
-
-
@@ -829,9 +832,6 @@
-
-
@@ -841,11 +841,6 @@
-
-
-
@@ -1661,7 +1656,7 @@
-
diff --git a/src/test/kotlin/facet/MinecraftLibraryDetectorTest.kt b/src/test/kotlin/facet/MinecraftLibraryDetectorTest.kt
new file mode 100644
index 000000000..d96ff7714
--- /dev/null
+++ b/src/test/kotlin/facet/MinecraftLibraryDetectorTest.kt
@@ -0,0 +1,200 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2026 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.facet
+
+import com.demonwav.mcdev.framework.ProjectBuilderTest
+import com.demonwav.mcdev.platform.PlatformType
+import com.demonwav.mcdev.platform.adventure.framework.AdventureLibraryDetector
+import com.demonwav.mcdev.platform.bukkit.framework.ModernPaperLibraryDetector
+import com.demonwav.mcdev.platform.mcp.framework.McpLibraryDetector
+import com.demonwav.mcdev.platform.mixin.framework.MixinLibraryDetector
+import com.demonwav.mcdev.platform.sponge.framework.SpongeLibraryDetector
+import com.demonwav.mcdev.platform.velocity.framework.VelocityLibraryDetector
+import com.intellij.openapi.application.runReadAction
+import com.intellij.openapi.project.DumbService
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.psi.search.GlobalSearchScopes
+import org.junit.jupiter.api.Assertions.assertFalse
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.Test
+
+class MinecraftLibraryDetectorTest : ProjectBuilderTest() {
+
+ @Test
+ fun `maven detector validates path coordinates and version`() {
+ val strictDetector = object : MavenMinecraftLibraryDetector(
+ PlatformType.BUKKIT,
+ "org.bukkit",
+ "bukkit",
+ ) {}
+ val compatibleDetector = object : MavenMinecraftLibraryDetector(
+ PlatformType.PAPER,
+ "io.papermc.paper",
+ "paper-api",
+ strict = false,
+ ) {}
+
+ val strictMatch = createRoot(
+ "strict-match",
+ mapOf(
+ "META-INF/maven/org.bukkit/bukkit/pom.properties" to
+ "groupId=org.bukkit\nartifactId=bukkit\nversion=1.0\n",
+ ),
+ )
+ val wrongCoordinates = createRoot(
+ "wrong-coordinates",
+ mapOf(
+ "META-INF/maven/org.bukkit/bukkit/pom.properties" to
+ "groupId=example\nartifactId=other\nversion=1.0\n",
+ ),
+ )
+ val missingVersion = createRoot(
+ "missing-version",
+ mapOf(
+ "META-INF/maven/org.bukkit/bukkit/pom.properties" to
+ "groupId=org.bukkit\nartifactId=bukkit\n",
+ ),
+ )
+ val paperCompatible = createRoot(
+ "paper-compatible",
+ mapOf(
+ "META-INF/maven/io.papermc.paper/paper-api/pom.properties" to
+ "groupId=legacy.paper\nartifactId=legacy-api\nversion=1.0\n",
+ ),
+ )
+
+ assertTrue(detect(strictDetector, strictMatch))
+ assertFalse(detect(strictDetector, wrongCoordinates))
+ assertFalse(detect(strictDetector, missingVersion))
+ assertTrue(detect(compatibleDetector, paperCompatible))
+ }
+
+ @Test
+ fun `manifest and service detectors preserve their content rules`() {
+ val adventure = createRoot(
+ "adventure",
+ mapOf(
+ "META-INF/MANIFEST.MF" to
+ "Manifest-Version: 1.0\nAutomatic-Module-Name: net.kyori.adventure\n\n",
+ ),
+ )
+ val spongeWithoutVersion = createRoot(
+ "sponge-without-version",
+ mapOf(
+ "META-INF/MANIFEST.MF" to
+ "Manifest-Version: 1.0\nImplementation-Title: SpongeAPI\n\n",
+ ),
+ )
+ val mixin = createRoot(
+ "mixin",
+ mapOf("META-INF/services/org.spongepowered.asm.service.IMixinService" to ""),
+ )
+ val velocity = createRoot(
+ "velocity",
+ mapOf(
+ "META-INF/services/javax.annotation.processing.Processor" to
+ "com.example.OtherProcessor\ncom.velocitypowered.api.plugin.ap.PluginAnnotationProcessor\n",
+ ),
+ )
+ val wrongVelocity = createRoot(
+ "wrong-velocity",
+ mapOf(
+ "META-INF/services/javax.annotation.processing.Processor" to
+ "com.example.OtherProcessor\n",
+ ),
+ )
+
+ assertTrue(detect(AdventureLibraryDetector(), adventure))
+ assertFalse(detect(SpongeLibraryDetector(), spongeWithoutVersion))
+ assertTrue(detect(MixinLibraryDetector(), mixin))
+ assertTrue(detect(VelocityLibraryDetector(), velocity))
+ assertFalse(detect(VelocityLibraryDetector(), wrongVelocity))
+ }
+
+ @Test
+ fun `modern paper requires both class and versioning resource`() {
+ val both = createRoot(
+ "modern-paper",
+ mapOf(
+ "io/papermc/paper/ServerBuildInfo.java" to
+ "package io.papermc.paper; public interface ServerBuildInfo {}",
+ "apiVersioning.json" to "{}",
+ ),
+ )
+ val classOnly = createRoot(
+ "modern-paper-class-only",
+ mapOf(
+ "io/papermc/paper/ServerBuildInfo.java" to
+ "package io.papermc.paper; public interface ServerBuildInfo {}",
+ ),
+ )
+ val resourceOnly = createRoot(
+ "modern-paper-resource-only",
+ mapOf("apiVersioning.json" to "{}"),
+ )
+
+ val detector = ModernPaperLibraryDetector()
+ assertTrue(detect(detector, both))
+ assertFalse(detect(detector, classOnly))
+ assertFalse(detect(detector, resourceOnly))
+ }
+
+ @Test
+ fun `class detector cannot escape the supplied scope`() {
+ val minecraft = createRoot(
+ "minecraft",
+ mapOf(
+ "net/minecraft/server/MinecraftServer.java" to
+ "package net.minecraft.server; public class MinecraftServer {}",
+ ),
+ )
+ val unrelated = createRoot(
+ "unrelated",
+ mapOf("example/Placeholder.java" to "package example; public class Placeholder {}"),
+ )
+
+ val detector = McpLibraryDetector()
+ assertTrue(detect(detector, minecraft))
+ assertFalse(detect(detector, unrelated))
+ }
+
+ private fun createRoot(name: String, files: Map): VirtualFile {
+ var firstFile: VirtualFile? = null
+ buildProject {
+ for ((path, content) in files) {
+ val extension = path.substring(path.lastIndexOf('.'))
+ val file = file("$name/$path", content, extension, configure = false, allowAst = true)
+ if (firstFile == null) {
+ firstFile = file
+ }
+ }
+ }
+
+ return generateSequence(firstFile) { it.parent }
+ .first { it.name == name }
+ }
+
+ private fun detect(detector: MinecraftLibraryDetector, root: VirtualFile): Boolean {
+ DumbService.getInstance(project).waitForSmartMode()
+ val scope = GlobalSearchScopes.directoryScope(project, root, true)
+ return runReadAction { detector.isLibraryPresent(project, scope) }
+ }
+}
diff --git a/src/test/kotlin/framework/BaseMinecraftTest.kt b/src/test/kotlin/framework/BaseMinecraftTest.kt
index 61e1444e6..0eba1900f 100644
--- a/src/test/kotlin/framework/BaseMinecraftTest.kt
+++ b/src/test/kotlin/framework/BaseMinecraftTest.kt
@@ -22,9 +22,11 @@ package com.demonwav.mcdev.framework
import com.demonwav.mcdev.facet.MinecraftFacet
import com.demonwav.mcdev.facet.MinecraftFacetConfiguration
+import com.demonwav.mcdev.facet.MinecraftFacetDetector
import com.demonwav.mcdev.platform.PlatformType
import com.demonwav.mcdev.util.runWriteTask
import com.intellij.facet.FacetManager
+import com.intellij.openapi.components.service
import com.intellij.openapi.module.Module
import com.intellij.openapi.roots.ContentEntry
import com.intellij.openapi.roots.LanguageLevelModuleExtension
@@ -32,8 +34,13 @@ import com.intellij.openapi.roots.ModifiableRootModel
import com.intellij.pom.java.LanguageLevel
import com.intellij.testFramework.LightProjectDescriptor
import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor
+import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.extension.BeforeTestExecutionCallback
+import org.junit.jupiter.api.extension.ExtendWith
+import org.junit.jupiter.api.extension.ExtensionContext
+@ExtendWith(MinecraftFacetDetectionExtension::class)
abstract class BaseMinecraftTest(
vararg platformTypes: PlatformType,
) : ProjectBuilderTest(
@@ -47,6 +54,18 @@ abstract class BaseMinecraftTest(
fixture.testDataPath = "$BASE_DATA_PATH/$testPath"
}
}
+
+ internal fun awaitMinecraftFacetDetection() {
+ runBlocking {
+ project.service().awaitLatestDetection()
+ }
+ }
+}
+
+class MinecraftFacetDetectionExtension : BeforeTestExecutionCallback {
+ override fun beforeTestExecution(context: ExtensionContext) {
+ (context.requiredTestInstance as BaseMinecraftTest).awaitMinecraftFacetDetection()
+ }
}
fun getProjectDescriptor(platformTypes: Array): LightProjectDescriptor {