User: strokkur24 Date: 18 Jan 26 11:12 Revision: d68645f00c4fd2d1693085cc94158ae4f9547894 Summary: Add GradlePluginSelectorCreatorProperty.kt for inline Gradle plugin enable/version selection TeamCity URL: http://ci.mcdev.io:80/viewModification.html?tab=vcsModificationFiles&modId=10396&personal=false Index: src/main/kotlin/creator/custom/types/GradlePluginSelectorCreatorProperty.kt =================================================================== --- src/main/kotlin/creator/custom/types/GradlePluginSelectorCreatorProperty.kt (revision d68645f00c4fd2d1693085cc94158ae4f9547894) +++ src/main/kotlin/creator/custom/types/GradlePluginSelectorCreatorProperty.kt (revision d68645f00c4fd2d1693085cc94158ae4f9547894) @@ -0,0 +1,256 @@ +/* + * 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.creator.custom.types + +import com.demonwav.mcdev.creator.collectMavenVersions +import com.demonwav.mcdev.creator.custom.CreatorContext +import com.demonwav.mcdev.creator.custom.CreatorCredentials +import com.demonwav.mcdev.creator.custom.TemplateEvaluator +import com.demonwav.mcdev.creator.custom.TemplatePropertyDescriptor +import com.demonwav.mcdev.creator.custom.TemplateValidationReporter +import com.demonwav.mcdev.creator.custom.model.TemplateApi +import com.demonwav.mcdev.creator.custom.types.GradlePluginSelectorCreatorProperty.Holder +import com.demonwav.mcdev.util.SemanticVersion +import com.demonwav.mcdev.util.getOrLogException +import com.github.kittinunf.fuel.core.Request +import com.github.kittinunf.fuel.core.extensions.authentication +import com.intellij.openapi.diagnostic.thisLogger +import com.intellij.openapi.observable.properties.GraphProperty +import com.intellij.openapi.observable.util.transform +import com.intellij.ui.ComboboxSpeedSearch +import com.intellij.ui.JBColor +import com.intellij.ui.dsl.builder.Panel +import com.intellij.ui.dsl.builder.RightGap +import com.intellij.ui.dsl.builder.bindItem +import com.intellij.ui.dsl.builder.bindSelected +import com.intellij.ui.dsl.builder.bindText +import com.intellij.util.ui.AsyncProcessIcon +import fleet.multiplatform.shims.ConcurrentHashMap +import java.util.function.Function +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.jetbrains.kotlin.cli.common.toBooleanLenient + +class GradlePluginSelectorCreatorProperty( + descriptor: TemplatePropertyDescriptor, + context: CreatorContext +) : CreatorProperty(descriptor, context, Holder::class.java) { + private val loadingVersionsProperty = graph.property(true) + private val loadingVersionsStatusProperty = graph.property("") + + override val graphProperty: GraphProperty = graph.property(Holder(SemanticVersion(emptyList()), false)) + var property: Holder by graphProperty + + private val versionsModel = graph.property>(emptySet()) + + private val versionProperty = graphProperty.transform({ it.version }, { property.copy(version = it) }) + private val enabledProperty = graphProperty.transform({ it.enabled }, { property.copy(enabled = it) }) + + override fun createDefaultValue(raw: Any?): Holder { + if (raw is String) { + return deserialize(raw) + } + + return Holder(SemanticVersion(emptyList()), false) + } + + override fun serialize(value: Holder): String = value.toString() + + override fun deserialize(string: String): Holder = + Holder.tryParse(string) ?: Holder(SemanticVersion(emptyList()), false) + + override fun buildUi(panel: Panel) { + val label = descriptor.translatedLabel + panel.row(label) { + checkBox("") + .bindSelected(enabledProperty) + .enabled(descriptor.editable != false) + + label("Version:").gap(RightGap.SMALL) + val combobox = comboBox(versionsModel.get()) + .bindItem(versionProperty) + .enabled(descriptor.editable != false) + .also { ComboboxSpeedSearch.installOn(it.component) } + + val warning = descriptor.translatedWarning + if (warning != null) { + combobox.comment(descriptor.translate(warning)) + } + + cell(AsyncProcessIcon(makeStorageKey("progress"))) + .visibleIf(loadingVersionsProperty) + label("").applyToComponent { foreground = JBColor.RED } + .bindText(loadingVersionsStatusProperty) + .visibleIf(loadingVersionsProperty) + + versionsModel.afterChange { versions -> + combobox.component.removeAllItems() + for (version in versions) { + combobox.component.addItem(version) + } + } + + + }.propertyVisibility() + } + + override fun setupProperty(reporter: TemplateValidationReporter) { + super.setupProperty(reporter) + + var rawVersionFilter: (String) -> Boolean = { true } + var versionFilter: (SemanticVersion) -> Boolean = { true } + + val url = descriptor.parameters?.get("sourceUrl") as? String + if (url == null) { + reporter.error("Expected string parameter 'sourceUrl'") + return + } + + val rawVersionFilterCondition = descriptor.parameters["rawVersionFilter"] + if (rawVersionFilterCondition != null) { + if (rawVersionFilterCondition !is String) { + reporter.error("'rawVersionFilter' must be a string") + } else { + rawVersionFilter = { version -> + val props = mapOf("version" to version) + TemplateEvaluator.condition(props, rawVersionFilterCondition) + .getOrLogException(thisLogger()) == true + } + } + } + + val versionFilterCondition = descriptor.parameters["versionFilter"] + if (versionFilterCondition != null) { + if (versionFilterCondition !is String) { + reporter.error("'versionFilter' must be a string") + } else { + versionFilter = { version -> + val props = mapOf("version" to version) + TemplateEvaluator.condition(props, versionFilterCondition) + .getOrLogException(thisLogger()) == true + } + } + } + + downloadVersions( + context, + // The key might be a bit too unique, but that'll do the job + descriptor.name + "@" + descriptor.hashCode(), + url, + rawVersionFilter, + versionFilter, + descriptor.limit ?: 50 + ) { result -> + result.onSuccess { versions -> + val set = versions.toSet() + versionsModel.set(set) + loadingVersionsProperty.set(false) + }.onFailure { exception -> + loadingVersionsStatusProperty.set(exception.message ?: exception.javaClass.simpleName) + } + } + } + + companion object { + + private var versionsCache = ConcurrentHashMap>() + + fun downloadVersions( + context: CreatorContext, + key: String, + url: String, + rawVersionFilter: (String) -> Boolean, + versionFilter: (SemanticVersion) -> Boolean, + limit: Int, + uiCallback: (Result>) -> Unit + ) { + // Let's not mix up cached versions if different properties + // point to the same URL, but have different filters or limits + val cacheKey = "$key-$url" + val cachedVersions = versionsCache[cacheKey] + if (cachedVersions != null) { + uiCallback(Result.success(cachedVersions)) + return + } + + val scope = context.childScope("GradlePluginSelectorCreatorProperty") + scope.launch(Dispatchers.Default) { + val result = withContext(Dispatchers.IO) { + val requestCustomizer = CreatorCredentials.findMavenRepoCredentials(url)?.let { (user, pass) -> + Function { request -> request.authentication().basic(user, pass) } + } + + runCatching { collectMavenVersions(url, requestCustomizer) } + }.map { result -> + val versions = result.asSequence() + .filter(rawVersionFilter) + .mapNotNull(SemanticVersion::tryParse) + .filter(versionFilter) + .sortedDescending() + .take(limit) + .toList() + + versionsCache[cacheKey] = versions + versions + } + + withContext(context.uiContext) { + uiCallback(result) + } + } + } + } + + class Factory : CreatorPropertyFactory { + override fun create( + descriptor: TemplatePropertyDescriptor, + context: CreatorContext + ): CreatorProperty<*> = GradlePluginSelectorCreatorProperty(descriptor, context) + } + + @TemplateApi + data class Holder( + val version: SemanticVersion, + val enabled: Boolean + ) { + override fun toString(): String { + return "$enabled $version" + } + + companion object { + fun tryParse(raw: String): Holder? { + val split = raw.split(" ", limit = 2) + return when (split.size) { + 1 -> raw.toBooleanLenient()?.let { Holder(SemanticVersion(emptyList()), it) } + 2 -> split[0].toBooleanLenient()?.let { + Holder( + SemanticVersion.tryParse(split[1]) ?: SemanticVersion(emptyList()), + it + ) + } + + else -> null + } + } + } + } +} Index: src/main/resources/META-INF/plugin.xml =================================================================== --- src/main/resources/META-INF/plugin.xml (revision 4e393eaee4fb9cbb42c89742c88a996dc3733104) +++ src/main/resources/META-INF/plugin.xml (revision d68645f00c4fd2d1693085cc94158ae4f9547894) @@ -200,6 +200,9 @@ implementation="com.demonwav.mcdev.creator.custom.types.ForgeVersionsCreatorProperty$Factory" type="forge_versions"/> + Minecraft EULA. +creator.ui.shadow_plugin.label=Use shadow Gradle plugin\: +creator.ui.paperweight_userdev_plugin.label=Use paperweight-userdev Gradle plugin\: +creator.ui.resource_factory_plugin.label=Use resource-factory Gradle plugin\: +creator.ui.gremlin_plugin.label=Use gremlin Gradle plugin\: +creator.ui.gremlin_plugin.warning=Using gremlin force includes the shadow plugin and makes the plugin loader file inaccessible. creator.ui.warn.no_yarn_to_mc_match=Unable to match Yarn versions to Minecraft version creator.ui.warn.no_fabricapi_to_mc_match=Unable to match API versions to Minecraft version