diff --git a/build.gradle.kts b/build.gradle.kts index 4d7c88970..dd9da3451 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,9 +19,6 @@ */ import io.sentry.android.gradle.extensions.SentryPluginExtension -import java.util.concurrent.atomic.AtomicInteger -import kotlin.concurrent.atomics.AtomicInt -import org.gradle.kotlin.dsl.configure import org.jetbrains.changelog.Changelog import org.jetbrains.gradle.ext.settings import org.jetbrains.gradle.ext.taskTriggers diff --git a/src/main/kotlin/facet/LibraryPresentationProviders.kt b/src/main/kotlin/facet/LibraryPresentationProviders.kt deleted file mode 100644 index 931b3ec40..000000000 --- a/src/main/kotlin/facet/LibraryPresentationProviders.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.util.localFile -import com.intellij.framework.library.LibraryVersionProperties -import com.intellij.openapi.roots.libraries.LibraryKind -import com.intellij.openapi.roots.libraries.LibraryPresentationProvider -import com.intellij.openapi.util.io.JarUtil -import com.intellij.openapi.vfs.VirtualFile - -abstract class MavenLibraryPresentationProvider( - kind: LibraryKind, - private val groupId: String, - private val artifactId: String, - private val strict: Boolean = true, -) : - LibraryPresentationProvider(kind) { - - private val propertiesPath = "META-INF/maven/$groupId/$artifactId/pom.properties" - - final override fun detect(classesRoots: List): LibraryVersionProperties? { - for (classesRoot in classesRoots) { - val file = classesRoot.localFile - val properties = JarUtil.loadProperties(file, propertiesPath) ?: continue - - if (strict && (properties["groupId"] != groupId || properties["artifactId"] != artifactId)) { - continue - } - - val version = properties["version"] as? String ?: continue - return LibraryVersionProperties(version) - } - - return null - } -} diff --git a/src/main/kotlin/facet/MinecraftFacetDetector.kt b/src/main/kotlin/facet/MinecraftFacetDetector.kt index 3024ee001..03a2a43bc 100644 --- a/src/main/kotlin/facet/MinecraftFacetDetector.kt +++ b/src/main/kotlin/facet/MinecraftFacetDetector.kt @@ -21,237 +21,211 @@ package com.demonwav.mcdev.facet import com.demonwav.mcdev.platform.PlatformType -import com.demonwav.mcdev.platform.architectury.framework.ARCHITECTURY_LIBRARY_KIND import com.demonwav.mcdev.platform.architectury.framework.ArchitecturyGradleData -import com.demonwav.mcdev.platform.fabric.framework.FABRIC_LIBRARY_KIND import com.demonwav.mcdev.platform.mcp.gradle.tooling.archloom.ArchitecturyModel -import com.demonwav.mcdev.platform.sponge.framework.SPONGE_LIBRARY_KIND -import com.demonwav.mcdev.util.ifEmpty -import com.demonwav.mcdev.util.runWriteTaskLater import com.intellij.facet.FacetManager -import com.intellij.facet.impl.ui.libraries.LibrariesValidatorContextImpl -import com.intellij.framework.library.LibraryVersionProperties import com.intellij.openapi.application.EDT -import com.intellij.openapi.application.readAction +import com.intellij.openapi.application.backgroundWriteAction +import com.intellij.openapi.application.smartReadAction import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ModuleRootEvent import com.intellij.openapi.roots.ModuleRootListener -import com.intellij.openapi.roots.OrderRootType -import com.intellij.openapi.roots.libraries.Library -import com.intellij.openapi.roots.libraries.LibraryDetectionManager -import com.intellij.openapi.roots.libraries.LibraryDetectionManager.LibraryPropertiesProcessor -import com.intellij.openapi.roots.libraries.LibraryKind -import com.intellij.openapi.roots.libraries.LibraryProperties -import com.intellij.openapi.roots.ui.configuration.projectRoot.LibrariesContainer +import com.intellij.openapi.roots.OrderEnumerator import com.intellij.openapi.startup.ProjectActivity -import com.intellij.openapi.util.Key import com.intellij.platform.ide.progress.withBackgroundProgress -import com.intellij.platform.util.progress.forEachWithProgress -import com.intellij.util.concurrency.NonUrgentExecutor +import com.intellij.psi.search.GlobalSearchScopes +import java.util.concurrent.atomic.AtomicLong +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.cancelAndJoin -import kotlinx.coroutines.job +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext +import org.jetbrains.annotations.TestOnly import org.jetbrains.plugins.gradle.util.GradleUtil -class MinecraftFacetDetector : ProjectActivity { - private object Const { - val libraryVersionsKey = Key>("mcdev.libraryVersions") +@OptIn(FlowPreview::class) +@Service(Service.Level.PROJECT) +class MinecraftFacetDetector( + private val project: Project, + scope: CoroutineScope, +) { + private val requests = MutableSharedFlow( + replay = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST, + ) + private val requestedGeneration = AtomicLong() + private val completedGeneration = MutableStateFlow(0L) + + init { + scope.launch { + requests + .debounce(300.milliseconds) + .collectLatest { generation -> + withBackgroundProgress(project, "Detecting Minecraft Frameworks", cancellable = true) { + val detections = smartReadAction(project) { detectModules() } + val needsReimport = withContext(Dispatchers.EDT) { applyDetections(detections) } + coroutineContext.ensureActive() + if (needsReimport) { + withContext(Dispatchers.EDT) { + ProjectReimporter.reimport(project) + } + } + } + completedGeneration.value = generation + } + } } - override suspend fun execute(project: Project) { - val detectorService = project.service() - MinecraftModuleRootListener.doCheckUnderProgress(project, detectorService) + fun schedule() { + requests.tryEmit(requestedGeneration.incrementAndGet()) } - @Service(Service.Level.PROJECT) - private class FacetDetectorScopeProvider(val scope: CoroutineScope) { - val dispatcher = NonUrgentExecutor.getInstance().asCoroutineDispatcher() - val lock = Mutex() - var currentJob: Job? = null + @TestOnly + suspend fun awaitLatestDetection() { + val generation = requestedGeneration.get() + completedGeneration.first { it >= generation } } - private object MinecraftModuleRootListener : ModuleRootListener { - override fun rootsChanged(event: ModuleRootEvent) { - if (event.isCausedByFileTypesChange) { - return - } - - val project = event.source as? Project ?: return - val detectorService = project.service() - detectorService.scope.launch(detectorService.dispatcher) { - doCheckUnderProgress(project, detectorService) + private fun detectModules(): List = buildList { + for (module in ModuleManager.getInstance(project).modules) { + ProgressManager.checkCanceled() + if (!module.isDisposed) { + add(ModuleDetection(module, autoDetectTypes(module))) } } + } - suspend fun doCheckUnderProgress(project: Project, detectorService: FacetDetectorScopeProvider) { - withBackgroundProgress(project, "Detecting Minecraft Frameworks", cancellable = false) { - detectorService.lock.withLock { - detectorService.currentJob?.cancelAndJoin() - detectorService.currentJob = coroutineContext.job + private fun autoDetectTypes(module: Module): Set { + val platformTypes = mutableSetOf() + val libraryRoots = OrderEnumerator.orderEntries(module) + .recursively() + .librariesOnly() + .classes() + .roots + + if (libraryRoots.isNotEmpty()) { + val libraryScope = GlobalSearchScopes.directoriesScope(module.project, true, *libraryRoots) + for (detector in MinecraftLibraryDetector.EP_NAME.extensionList) { + ProgressManager.checkCanceled() + if (detector.isLibraryPresent(module.project, libraryScope)) { + platformTypes.add(detector.platformType) } - doCheck(project) } } - suspend fun doCheck(project: Project) { - val moduleManager = ModuleManager.getInstance(project) - - var needsReimport = false - - moduleManager.modules.asList().forEachWithProgress { module -> - val facetManager = FacetManager.getInstance(module) - val minecraftFacet = facetManager.getFacetByType(MinecraftFacet.ID) - - if (minecraftFacet == null) { - checkNoFacet(module) - } else { - checkExistingFacet(module, minecraftFacet) - if (ProjectReimporter.needsReimport(minecraftFacet)) { - needsReimport = true + OrderEnumerator.orderEntries(module) + .recursively() + .withoutLibraries() + .withoutSdk() + .forEachModule { dependencyModule -> + if (dependencyModule.name.startsWith("SpongeAPI", ignoreCase = true)) { + val moduleManager = ModuleManager.getInstance(dependencyModule.project) + val groupPath = moduleManager.getModuleGrouper(null).getGroupPath(dependencyModule) + if (groupPath.isEmpty() || dependencyModule.name != groupPath.lastOrNull()) { + platformTypes.add(PlatformType.SPONGE) } } + true } - if (needsReimport) { - project.service().scope.launch(Dispatchers.EDT) { - ProjectReimporter.reimport(project) - } - } + val architecturyGradleData = GradleUtil.findGradleModuleData(module)?.children + ?.find { it.key == ArchitecturyGradleData.KEY }?.data as? ArchitecturyGradleData + if (architecturyGradleData?.moduleType == ArchitecturyModel.ModuleType.COMMON) { + platformTypes.add(PlatformType.ARCHITECTURY) + platformTypes.remove(PlatformType.FABRIC) } - private suspend fun checkNoFacet(module: Module) { - val platforms = readAction { - if (!module.isDisposed) { - autoDetectTypes(module) - } else { - emptyList() - } - }.ifEmpty { return } + return platformTypes + } - runWriteTaskLater { - // Only add the new facet if there isn't a Minecraft facet already - double check here since this - // task may run much later - if (module.isDisposed) { - // Module may be disposed before we run - return@runWriteTaskLater - } + private suspend fun applyDetections(detections: List): Boolean { + var needsReimport = false - val facetType = MinecraftFacet.facetTypeOrNull - ?: return@runWriteTaskLater + for ((module, platformTypes) in detections) { + if (module.isDisposed) { + continue + } - val facetManager = FacetManager.getInstance(module) - val model = facetManager.createModifiableModel() - if (model.getFacetByType(MinecraftFacet.ID) == null) { - val configuration = MinecraftFacetConfiguration() - configuration.state.autoDetectTypes.addAll(platforms) - val facet = facetManager.createFacet(facetType, "Minecraft", configuration, null) - model.addFacet(facet) - model.commit() + val facetManager = FacetManager.getInstance(module) + val minecraftFacet = facetManager.getFacetByType(MinecraftFacet.ID) + if (minecraftFacet == null) { + if (platformTypes.isNotEmpty()) { + addFacet(module, platformTypes) } + continue } - } - private fun checkExistingFacet(module: Module, facet: MinecraftFacet) { - val platforms = autoDetectTypes(module).ifEmpty { return } + if (platformTypes.isNotEmpty()) { + val types = minecraftFacet.configuration.state.autoDetectTypes + types.clear() + types.addAll(platformTypes) - val types = facet.configuration.state.autoDetectTypes - types.clear() - types.addAll(platforms) + if (minecraftFacet.configuration.state.forgePatcher) { + types.add(PlatformType.FORGE) + types.add(PlatformType.MCP) + } - if (facet.configuration.state.forgePatcher) { - // make sure Forge and MCP are present - types.add(PlatformType.FORGE) - types.add(PlatformType.MCP) + minecraftFacet.refresh() } - facet.refresh() + if (ProjectReimporter.needsReimport(minecraftFacet)) { + needsReimport = true + } } - private fun autoDetectTypes(module: Module): Set { - val libraryVersions = module.getUserData(Const.libraryVersionsKey) - ?: mutableMapOf().also { module.putUserData(Const.libraryVersionsKey, it) } - libraryVersions.clear() + return needsReimport + } - val context = LibrariesValidatorContextImpl(module) + private suspend fun addFacet(module: Module, platformTypes: Set) { + backgroundWriteAction { + if (module.isDisposed) { + return@backgroundWriteAction + } - val platformKinds = mutableSetOf() - context.rootModel - .orderEntries() - .using(context.modulesProvider) - .recursively() - .librariesOnly() - .forEachLibrary forEach@{ library -> - processLibraryMinecraftPlatformKinds(library, context.librariesContainer) { kind, version -> - platformKinds.add(kind) - if (version != null) { - libraryVersions[kind] = version - } - true - } - return@forEach true - } + val facetType = MinecraftFacet.facetTypeOrNull ?: return@backgroundWriteAction + val facetManager = FacetManager.getInstance(module) + val model = facetManager.createModifiableModel() + if (model.getFacetByType(MinecraftFacet.ID) == null) { + val configuration = MinecraftFacetConfiguration() + configuration.state.autoDetectTypes.addAll(platformTypes) + val facet = facetManager.createFacet(facetType, "Minecraft", configuration, null) + model.addFacet(facet) + model.commit() + } + } + } - context.rootModel - .orderEntries() - .using(context.modulesProvider) - .recursively() - .withoutLibraries() - .withoutSdk() - .forEachModule forEach@{ m -> - if (m.name.startsWith("SpongeAPI", ignoreCase = true)) { - // We don't want want to add parent modules in module groups - val moduleManager = ModuleManager.getInstance(m.project) - val groupPath = moduleManager.getModuleGrouper(null).getGroupPath(m) - if (groupPath.isEmpty()) { - platformKinds.add(SPONGE_LIBRARY_KIND) - return@forEach true - } + private data class ModuleDetection( + val module: Module, + val platformTypes: Set, + ) +} - val name = groupPath.lastOrNull() ?: return@forEach true - if (m.name == name) { - return@forEach true - } +class MinecraftFacetDetectorListener : ProjectActivity, ModuleRootListener { - platformKinds.add(SPONGE_LIBRARY_KIND) - } - return@forEach true - } + override suspend fun execute(project: Project) { + project.service().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 {