From fe706856f5293f7d29e9ec882e21537b910ed6e1 Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Fri, 9 Jan 2026 17:37:48 +0100 Subject: [PATCH 1/9] feat: add template translations --- .../messages/MinecraftDevelopment.properties | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/resources/messages/MinecraftDevelopment.properties b/src/main/resources/messages/MinecraftDevelopment.properties index 5ea08a1ee..fa35a432b 100644 --- a/src/main/resources/messages/MinecraftDevelopment.properties +++ b/src/main/resources/messages/MinecraftDevelopment.properties @@ -109,6 +109,17 @@ creator.ui.use_fabricapi.label=Use Fabric API creator.ui.spongeapi_version.label=Sponge Version\: creator.ui.velocity_version.label=Velocity Version\: creator.ui.versions_download.label=Downloading versions... +creator.ui.use_version_catalog.label=Use Gradle version catalog\: +creator.ui.include_plugin_bootstrap.label=Include plugin bootstrap file\: +creator.ui.use_gremlin.label=Use Gremlin for dependency resolution\: +creator.ui.include_plugin_loader.label=Include plugin loader file\: +creator.ui.java_plugin_loader.label=Generate the plugin loader in Java\: +creator.ui.use_resource_factory_plugin.label=Use the resource-factory Gradle plugin\: +creator.ui.use_paperweight_userdev.label=Use the paperweight-userdev Gradle plugin\: +creator.ui.include_shadow_plugin.label=Include the shadow Gradle plugin\: +creator.ui.include_run_paper_plugin.label=Include the run-paper Gradle plugin\: +creator.ui.accept_eula.label=Add eula-agree runServer JVM flag\: +creator.ui.accept_eula.warning=By using this feature, you agree to the Minecraft EULA. 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 From f344512766ad6bead332fabdc4fddf7f126a5ca8 Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Fri, 9 Jan 2026 18:32:45 +0100 Subject: [PATCH 2/9] feat: prepare other misc changes --- ...xtractPaperApiVersionPropertyDerivation.kt | 79 +++++++++++++++++++ ...ractVersionMajorMinorPropertyDerivation.kt | 2 +- .../kotlin/creator/custom/model/StringList.kt | 3 + .../types/SemanticVersionCreatorProperty.kt | 6 ++ 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/creator/custom/derivation/ExtractPaperApiVersionPropertyDerivation.kt diff --git a/src/main/kotlin/creator/custom/derivation/ExtractPaperApiVersionPropertyDerivation.kt b/src/main/kotlin/creator/custom/derivation/ExtractPaperApiVersionPropertyDerivation.kt new file mode 100644 index 000000000..258b3ec07 --- /dev/null +++ b/src/main/kotlin/creator/custom/derivation/ExtractPaperApiVersionPropertyDerivation.kt @@ -0,0 +1,79 @@ +/* + * 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.derivation + +import com.demonwav.mcdev.creator.custom.PropertyDerivation +import com.demonwav.mcdev.creator.custom.TemplateValidationReporter +import com.demonwav.mcdev.creator.custom.types.CreatorProperty +import com.demonwav.mcdev.util.SemanticVersion + +class ExtractPaperApiVersionPropertyDerivation : ExtractVersionMajorMinorPropertyDerivation() { + + override fun derive(parentValues: List): Any { + val from = parentValues[0] as SemanticVersion + if (from.parts.size < 2) { + return SemanticVersion(emptyList()) + } + + if (from.parts.size == 3) { + val (part1, part2, part3) = from.parts + if (part1 is SemanticVersion.Companion.VersionPart.ReleasePart && + part2 is SemanticVersion.Companion.VersionPart.ReleasePart && + part3 is SemanticVersion.Companion.VersionPart.ReleasePart + ) { + // From Minecraft version 1.20.5 onwards, the Paper API version also contains the 'minor' number. + if (part1.version >= 26 || (part2.version >= 21) || (part2.version == 20 && part3.version >= 5)) { + return SemanticVersion(listOf(part1, part2, part3)) + } + } + } + + val (part1, part2) = from.parts + if (part1 is SemanticVersion.Companion.VersionPart.ReleasePart && + part2 is SemanticVersion.Companion.VersionPart.ReleasePart + ) { + return SemanticVersion(listOf(part1, part2)) + } + + return SemanticVersion(emptyList()) + } + + companion object : PropertyDerivationFactory { + + override fun create( + reporter: TemplateValidationReporter, + parents: List?>?, + derivation: PropertyDerivation + ): PreparedDerivation? { + if (parents.isNullOrEmpty()) { + reporter.error("Expected a parent") + return null + } + + if (!parents[0]!!.acceptsType(SemanticVersion::class.java)) { + reporter.error("First parent must produce a semantic version") + return null + } + + return ExtractPaperApiVersionPropertyDerivation() + } + } +} diff --git a/src/main/kotlin/creator/custom/derivation/ExtractVersionMajorMinorPropertyDerivation.kt b/src/main/kotlin/creator/custom/derivation/ExtractVersionMajorMinorPropertyDerivation.kt index 6d5c3ac04..c8ef54b87 100644 --- a/src/main/kotlin/creator/custom/derivation/ExtractVersionMajorMinorPropertyDerivation.kt +++ b/src/main/kotlin/creator/custom/derivation/ExtractVersionMajorMinorPropertyDerivation.kt @@ -25,7 +25,7 @@ import com.demonwav.mcdev.creator.custom.TemplateValidationReporter import com.demonwav.mcdev.creator.custom.types.CreatorProperty import com.demonwav.mcdev.util.SemanticVersion -class ExtractVersionMajorMinorPropertyDerivation : PreparedDerivation { +open class ExtractVersionMajorMinorPropertyDerivation : PreparedDerivation { override fun derive(parentValues: List): Any? { val from = parentValues[0] as SemanticVersion diff --git a/src/main/kotlin/creator/custom/model/StringList.kt b/src/main/kotlin/creator/custom/model/StringList.kt index 95ec00df2..9256b5b84 100644 --- a/src/main/kotlin/creator/custom/model/StringList.kt +++ b/src/main/kotlin/creator/custom/model/StringList.kt @@ -28,4 +28,7 @@ data class StringList(val values: List) : List by values { @JvmOverloads fun toString(separator: String, prefix: String = "", postfix: String = ""): String = values.joinToString(separator, prefix, postfix) + + fun toStringQuoted(): String = + values.joinToString(", ", transform = { '"' + it + '"' }) } diff --git a/src/main/kotlin/creator/custom/types/SemanticVersionCreatorProperty.kt b/src/main/kotlin/creator/custom/types/SemanticVersionCreatorProperty.kt index 46dae66ef..2bf327a38 100644 --- a/src/main/kotlin/creator/custom/types/SemanticVersionCreatorProperty.kt +++ b/src/main/kotlin/creator/custom/types/SemanticVersionCreatorProperty.kt @@ -24,6 +24,7 @@ import com.demonwav.mcdev.creator.custom.CreatorContext import com.demonwav.mcdev.creator.custom.PropertyDerivation import com.demonwav.mcdev.creator.custom.TemplatePropertyDescriptor import com.demonwav.mcdev.creator.custom.TemplateValidationReporter +import com.demonwav.mcdev.creator.custom.derivation.ExtractPaperApiVersionPropertyDerivation import com.demonwav.mcdev.creator.custom.derivation.ExtractVersionMajorMinorPropertyDerivation import com.demonwav.mcdev.creator.custom.derivation.PreparedDerivation import com.demonwav.mcdev.creator.custom.derivation.SelectPropertyDerivation @@ -64,6 +65,11 @@ open class SemanticVersionCreatorProperty( ExtractVersionMajorMinorPropertyDerivation.create(reporter, parents, derives) } + "extractPaperApiVersion" -> { + val parents = collectDerivationParents(reporter) + ExtractPaperApiVersionPropertyDerivation.create(reporter, parents, derives) + } + null -> { SelectPropertyDerivation.create(reporter, emptyList(), derives) } From cdb986df23af764e96459a59e1bc4ba2c57dfa93 Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Fri, 9 Jan 2026 22:23:16 +0100 Subject: [PATCH 3/9] refactor: use MinecraftVersions field for version comparison --- ...xtractPaperApiVersionPropertyDerivation.kt | 29 ++++--------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/creator/custom/derivation/ExtractPaperApiVersionPropertyDerivation.kt b/src/main/kotlin/creator/custom/derivation/ExtractPaperApiVersionPropertyDerivation.kt index 258b3ec07..de2c57940 100644 --- a/src/main/kotlin/creator/custom/derivation/ExtractPaperApiVersionPropertyDerivation.kt +++ b/src/main/kotlin/creator/custom/derivation/ExtractPaperApiVersionPropertyDerivation.kt @@ -23,37 +23,18 @@ package com.demonwav.mcdev.creator.custom.derivation import com.demonwav.mcdev.creator.custom.PropertyDerivation import com.demonwav.mcdev.creator.custom.TemplateValidationReporter import com.demonwav.mcdev.creator.custom.types.CreatorProperty +import com.demonwav.mcdev.util.MinecraftVersions import com.demonwav.mcdev.util.SemanticVersion class ExtractPaperApiVersionPropertyDerivation : ExtractVersionMajorMinorPropertyDerivation() { - override fun derive(parentValues: List): Any { + override fun derive(parentValues: List): Any? { val from = parentValues[0] as SemanticVersion - if (from.parts.size < 2) { - return SemanticVersion(emptyList()) + if (from >= MinecraftVersions.MC1_20_5) { + return from } - if (from.parts.size == 3) { - val (part1, part2, part3) = from.parts - if (part1 is SemanticVersion.Companion.VersionPart.ReleasePart && - part2 is SemanticVersion.Companion.VersionPart.ReleasePart && - part3 is SemanticVersion.Companion.VersionPart.ReleasePart - ) { - // From Minecraft version 1.20.5 onwards, the Paper API version also contains the 'minor' number. - if (part1.version >= 26 || (part2.version >= 21) || (part2.version == 20 && part3.version >= 5)) { - return SemanticVersion(listOf(part1, part2, part3)) - } - } - } - - val (part1, part2) = from.parts - if (part1 is SemanticVersion.Companion.VersionPart.ReleasePart && - part2 is SemanticVersion.Companion.VersionPart.ReleasePart - ) { - return SemanticVersion(listOf(part1, part2)) - } - - return SemanticVersion(emptyList()) + return super.derive(parentValues); } companion object : PropertyDerivationFactory { From 60162c1058fff3103c1a82c619983fbe616a299b Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sun, 11 Jan 2026 16:07:15 +0100 Subject: [PATCH 4/9] chore: bump to template version 2 --- src/main/kotlin/creator/custom/TemplateDescriptor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/creator/custom/TemplateDescriptor.kt b/src/main/kotlin/creator/custom/TemplateDescriptor.kt index 54890a8e3..5b6352cce 100644 --- a/src/main/kotlin/creator/custom/TemplateDescriptor.kt +++ b/src/main/kotlin/creator/custom/TemplateDescriptor.kt @@ -40,7 +40,7 @@ data class TemplateDescriptor( companion object { - const val FORMAT_VERSION = 1 + const val FORMAT_VERSION = 2 } } From ce5196f057c30a6a25cd4db9eba065831884f077 Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sun, 11 Jan 2026 22:39:57 +0100 Subject: [PATCH 5/9] feat: add automatic Paper version fetching --- .editorconfig | 8 +- .../types/PaperVersionCreatorProperty.kt | 82 +++++++++++++++++++ .../custom/types/SimpleCreatorProperty.kt | 66 ++++++++------- src/main/resources/META-INF/plugin.xml | 3 + 4 files changed, 126 insertions(+), 33 deletions(-) create mode 100644 src/main/kotlin/creator/custom/types/PaperVersionCreatorProperty.kt diff --git a/.editorconfig b/.editorconfig index e8a57bf1e..66b889a6a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,6 +1,10 @@ +root = true + [*.{kt,kts}] -max_line_length=120 -ij_kotlin_imports_layout=* +indent_size = 4 +indent_style = space +max_line_length = 120 +ij_kotlin_imports_layout = * ij_kotlin_name_count_to_use_star_import = 2147483647 ij_kotlin_name_count_to_use_star_import_for_members = 2147483647 insert_final_newline = true diff --git a/src/main/kotlin/creator/custom/types/PaperVersionCreatorProperty.kt b/src/main/kotlin/creator/custom/types/PaperVersionCreatorProperty.kt new file mode 100644 index 000000000..191a3d208 --- /dev/null +++ b/src/main/kotlin/creator/custom/types/PaperVersionCreatorProperty.kt @@ -0,0 +1,82 @@ +/* + * 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.custom.CreatorContext +import com.demonwav.mcdev.creator.custom.TemplatePropertyDescriptor +import com.demonwav.mcdev.util.MinecraftVersions +import com.demonwav.mcdev.util.SemanticVersion +import com.intellij.ui.dsl.builder.Panel +import io.ktor.client.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive + +open class PaperVersionCreatorProperty( + descriptor: TemplatePropertyDescriptor, + context: CreatorContext +) : SemanticVersionCreatorProperty(descriptor, context) { + + @OptIn(DelicateCoroutinesApi::class) + companion object { + val paperVersions: Map? = loadPaperVersions()?.associate { it to it.toString() } + + fun loadPaperVersions(): List? { + val client = HttpClient() + return runBlocking { + val response = client.get("https://fill.papermc.io/v3/projects/paper") + if (response.status.isSuccess()) { + val element = Json.parseToJsonElement(response.bodyAsText()) + return@runBlocking element.jsonObject["versions"]?.jsonObject?.values + ?.asSequence() + ?.flatMap { it.jsonArray } + ?.map { it.jsonPrimitive.content } + ?.mapNotNull { SemanticVersion.tryParse(it) } + // only release versions + ?.filter { ver -> ver.parts.all { it is SemanticVersion.Companion.VersionPart.ReleasePart } } + // nothing lower than 1.18.2 should be selectable + ?.filter { it >= MinecraftVersions.MC1_18_2 } + ?.toList() + ?.sortedDescending() + } else { + return@runBlocking null + } + } + } + } + + override fun buildUi(panel: Panel) { + super.buildDropdownMenu(panel, paperVersions) + } + + class Factory : CreatorPropertyFactory { + override fun create( + descriptor: TemplatePropertyDescriptor, + context: CreatorContext + ): CreatorProperty<*> = PaperVersionCreatorProperty(descriptor, context) + } +} diff --git a/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt b/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt index 5f5d2a5a7..df99c215e 100644 --- a/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt +++ b/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt @@ -38,7 +38,7 @@ abstract class SimpleCreatorProperty( valueType: Class ) : CreatorProperty(descriptor, context, valueType) { - private val options: Map? = makeOptionsList() + val options: Map? = makeOptionsList() private fun makeOptionsList(): Map? { val map = when (val options = descriptor.options) { @@ -78,37 +78,41 @@ abstract class SimpleCreatorProperty( override val graphProperty: GraphProperty by lazy { graph.property(defaultValue) } - override fun buildUi(panel: Panel) { - if (isDropdown) { - if (graphProperty.get() !in options!!.keys) { - graphProperty.set(defaultValue) + protected fun buildDropdownMenu(panel: Panel, options: Map?) { + if (graphProperty.get() !in options!!.keys) { + graphProperty.set(defaultValue) + } + + panel.row(descriptor.translatedLabel) { + if (descriptor.forceDropdown == true) { + comboBox(options.keys, DropdownAutoRenderer()) + .bindItem(graphProperty) + .enabled(descriptor.editable != false) + .also { + val component = it.component + ComboboxSpeedSearch.installOn(component) + val validation = + BuiltinValidations.isAnyOf(component::getSelectedItem, options.keys, component) + it.validationOnInput(validation) + it.validationOnApply(validation) + } + } else { + segmentedButton(options.keys) { text = options[it] ?: it.toString() } + .bind(graphProperty) + .enabled(descriptor.editable != false) + .maxButtonsCount(4) + .validation { + val message = MCDevBundle("creator.validation.invalid_option") + addInputRule(message) { it.selectedItem !in options.keys } + addApplyRule(message) { it.selectedItem !in options.keys } + } } + }.propertyVisibility() + } - panel.row(descriptor.translatedLabel) { - if (descriptor.forceDropdown == true) { - comboBox(options.keys, DropdownAutoRenderer()) - .bindItem(graphProperty) - .enabled(descriptor.editable != false) - .also { - val component = it.component - ComboboxSpeedSearch.installOn(component) - val validation = - BuiltinValidations.isAnyOf(component::getSelectedItem, options.keys, component) - it.validationOnInput(validation) - it.validationOnApply(validation) - } - } else { - segmentedButton(options.keys) { text = options[it] ?: it.toString() } - .bind(graphProperty) - .enabled(descriptor.editable != false) - .maxButtonsCount(4) - .validation { - val message = MCDevBundle("creator.validation.invalid_option") - addInputRule(message) { it.selectedItem !in options.keys } - addApplyRule(message) { it.selectedItem !in options.keys } - } - } - }.propertyVisibility() + override fun buildUi(panel: Panel) { + if (isDropdown) { + buildDropdownMenu(panel, options) } else { buildSimpleUi(panel) } @@ -125,7 +129,7 @@ abstract class SimpleCreatorProperty( isSelected: Boolean, cellHasFocus: Boolean ): Component { - val label = options!![value] ?: value.toString() + val label = options?.get(value) ?: value.toString() return super.getListCellRendererComponent(list, label, index, isSelected, cellHasFocus) } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 22f3b95d5..3f20251bd 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -214,6 +214,9 @@ + Date: Sat, 17 Jan 2026 14:45:57 +0100 Subject: [PATCH 6/9] Add more translations and make maven artifact version creator property a Set instead of a Collection to avoid duplicate entries --- .../custom/types/MavenArtifactVersionCreatorProperty.kt | 4 ++-- .../resources/messages/MinecraftDevelopment.properties | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/creator/custom/types/MavenArtifactVersionCreatorProperty.kt b/src/main/kotlin/creator/custom/types/MavenArtifactVersionCreatorProperty.kt index fcd9011ae..e0c59d2c0 100644 --- a/src/main/kotlin/creator/custom/types/MavenArtifactVersionCreatorProperty.kt +++ b/src/main/kotlin/creator/custom/types/MavenArtifactVersionCreatorProperty.kt @@ -54,7 +54,7 @@ class MavenArtifactVersionCreatorProperty( var versionFilter: (SemanticVersion) -> Boolean = { true } override val graphProperty: GraphProperty = graph.property(SemanticVersion(emptyList())) - private val versionsProperty = graph.property>(emptyList()) + private val versionsProperty = graph.property>(emptySet()) private val loadingVersionsProperty = graph.property(true) private val loadingVersionsStatusProperty = graph.property("") @@ -127,7 +127,7 @@ class MavenArtifactVersionCreatorProperty( descriptor.limit ?: 50 ) { result -> result.onSuccess { versions -> - versionsProperty.set(versions) + versionsProperty.set(versions.toSet()) loadingVersionsProperty.set(false) }.onFailure { exception -> loadingVersionsStatusProperty.set(exception.message ?: exception.javaClass.simpleName) diff --git a/src/main/resources/messages/MinecraftDevelopment.properties b/src/main/resources/messages/MinecraftDevelopment.properties index fa35a432b..71144b95d 100644 --- a/src/main/resources/messages/MinecraftDevelopment.properties +++ b/src/main/resources/messages/MinecraftDevelopment.properties @@ -111,13 +111,18 @@ creator.ui.velocity_version.label=Velocity Version\: creator.ui.versions_download.label=Downloading versions... creator.ui.use_version_catalog.label=Use Gradle version catalog\: creator.ui.include_plugin_bootstrap.label=Include plugin bootstrap file\: -creator.ui.use_gremlin.label=Use Gremlin for dependency resolution\: +creator.ui.use_gremlin.label=Use gremlin for dependency resolution\: +creator.ui.gremlin_version.label=gremlin version\: creator.ui.include_plugin_loader.label=Include plugin loader file\: creator.ui.java_plugin_loader.label=Generate the plugin loader in Java\: creator.ui.use_resource_factory_plugin.label=Use the resource-factory Gradle plugin\: +creator.ui.resource_factory_version.label=resource-factory version\: creator.ui.use_paperweight_userdev.label=Use the paperweight-userdev Gradle plugin\: +creator.ui.paperweight_version.label=paperweight-userdev version\: creator.ui.include_shadow_plugin.label=Include the shadow Gradle plugin\: +creator.ui.shadow_version.label=shadow version\: creator.ui.include_run_paper_plugin.label=Include the run-paper Gradle plugin\: +creator.ui.run_paper_version.label=run-paper version\: creator.ui.accept_eula.label=Add eula-agree runServer JVM flag\: creator.ui.accept_eula.warning=By using this feature, you agree to the Minecraft EULA. From 68f1b3c625340854f2f5fed7d57b67749b283db2 Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sat, 17 Jan 2026 15:18:12 +0100 Subject: [PATCH 7/9] Fetch Paper versions async --- .../types/PaperVersionCreatorProperty.kt | 89 ++++++++++++++----- .../custom/types/SimpleCreatorProperty.kt | 62 ++++++------- 2 files changed, 97 insertions(+), 54 deletions(-) diff --git a/src/main/kotlin/creator/custom/types/PaperVersionCreatorProperty.kt b/src/main/kotlin/creator/custom/types/PaperVersionCreatorProperty.kt index 191a3d208..90866490d 100644 --- a/src/main/kotlin/creator/custom/types/PaperVersionCreatorProperty.kt +++ b/src/main/kotlin/creator/custom/types/PaperVersionCreatorProperty.kt @@ -22,15 +22,23 @@ package com.demonwav.mcdev.creator.custom.types import com.demonwav.mcdev.creator.custom.CreatorContext import com.demonwav.mcdev.creator.custom.TemplatePropertyDescriptor +import com.demonwav.mcdev.creator.custom.TemplateValidationReporter +import com.demonwav.mcdev.update.PluginUtil import com.demonwav.mcdev.util.MinecraftVersions import com.demonwav.mcdev.util.SemanticVersion +import com.intellij.ui.ComboboxSpeedSearch +import com.intellij.ui.JBColor import com.intellij.ui.dsl.builder.Panel +import com.intellij.ui.dsl.builder.bindItem +import com.intellij.ui.dsl.builder.bindText +import com.intellij.util.ui.AsyncProcessIcon import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.serialization.json.Json import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject @@ -43,34 +51,73 @@ open class PaperVersionCreatorProperty( @OptIn(DelicateCoroutinesApi::class) companion object { - val paperVersions: Map? = loadPaperVersions()?.associate { it to it.toString() } + private var paperVersions: List? = null + + suspend fun getPaperVersions(): List { + paperVersions?.let { return it } - fun loadPaperVersions(): List? { val client = HttpClient() - return runBlocking { - val response = client.get("https://fill.papermc.io/v3/projects/paper") - if (response.status.isSuccess()) { - val element = Json.parseToJsonElement(response.bodyAsText()) - return@runBlocking element.jsonObject["versions"]?.jsonObject?.values - ?.asSequence() - ?.flatMap { it.jsonArray } - ?.map { it.jsonPrimitive.content } - ?.mapNotNull { SemanticVersion.tryParse(it) } - // only release versions - ?.filter { ver -> ver.parts.all { it is SemanticVersion.Companion.VersionPart.ReleasePart } } - // nothing lower than 1.18.2 should be selectable - ?.filter { it >= MinecraftVersions.MC1_18_2 } - ?.toList() - ?.sortedDescending() - } else { - return@runBlocking null + val response = client.get("https://fill.papermc.io/v3/projects/paper", block = { + this.header("User-Agent", "minecraft-dev/${PluginUtil.pluginVersion} (https://github.com/minecraft-dev/MinecraftDev)") + }) + if (response.status.isSuccess()) { + val element = Json.parseToJsonElement(response.bodyAsText()) + val result = element.jsonObject["versions"]?.jsonObject?.values + ?.asSequence() + ?.flatMap { it.jsonArray } + ?.map { it.jsonPrimitive.content } + ?.mapNotNull { SemanticVersion.tryParse(it) } + // only release versions + ?.filter { ver -> ver.parts.all { it is SemanticVersion.Companion.VersionPart.ReleasePart } } + // nothing lower than 1.18.2 should be selectable + ?.filter { it >= MinecraftVersions.MC1_18_2 } + ?.toList() + ?.sortedDescending() + + if (result != null) { + paperVersions = result + return result } } + + return emptyList() } } + private val versionsProperty = graph.property>(emptySet()) + private val loadingVersionsProperty = graph.property(true) + private val loadingVersionsStatusProperty = graph.property("") + override fun buildUi(panel: Panel) { - super.buildDropdownMenu(panel, paperVersions) + panel.row(descriptor.translatedLabel) { + val combobox = comboBox(versionsProperty.get()) + .bindItem(graphProperty) + .enabled(descriptor.editable != false) + .also { ComboboxSpeedSearch.installOn(it.component) } + + cell(AsyncProcessIcon(makeStorageKey("progress"))) + .visibleIf(loadingVersionsProperty) + label("").applyToComponent { foreground = JBColor.RED } + .bindText(loadingVersionsStatusProperty) + .visibleIf(loadingVersionsProperty) + + versionsProperty.afterChange { versions -> + combobox.component.removeAllItems() + for (version in versions) { + combobox.component.addItem(version) + } + } + }.propertyVisibility() + } + + override fun setupProperty(reporter: TemplateValidationReporter) { + super.setupProperty(reporter) + val scope = context.childScope("PaperVersionCreatorProperty") + scope.launch(Dispatchers.Default) { + val result = getPaperVersions() + versionsProperty.set(result.toSet()) + loadingVersionsProperty.set(false) + } } class Factory : CreatorPropertyFactory { diff --git a/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt b/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt index df99c215e..1b36c7215 100644 --- a/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt +++ b/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt @@ -78,41 +78,37 @@ abstract class SimpleCreatorProperty( override val graphProperty: GraphProperty by lazy { graph.property(defaultValue) } - protected fun buildDropdownMenu(panel: Panel, options: Map?) { - if (graphProperty.get() !in options!!.keys) { - graphProperty.set(defaultValue) - } - - panel.row(descriptor.translatedLabel) { - if (descriptor.forceDropdown == true) { - comboBox(options.keys, DropdownAutoRenderer()) - .bindItem(graphProperty) - .enabled(descriptor.editable != false) - .also { - val component = it.component - ComboboxSpeedSearch.installOn(component) - val validation = - BuiltinValidations.isAnyOf(component::getSelectedItem, options.keys, component) - it.validationOnInput(validation) - it.validationOnApply(validation) - } - } else { - segmentedButton(options.keys) { text = options[it] ?: it.toString() } - .bind(graphProperty) - .enabled(descriptor.editable != false) - .maxButtonsCount(4) - .validation { - val message = MCDevBundle("creator.validation.invalid_option") - addInputRule(message) { it.selectedItem !in options.keys } - addApplyRule(message) { it.selectedItem !in options.keys } - } - } - }.propertyVisibility() - } - override fun buildUi(panel: Panel) { if (isDropdown) { - buildDropdownMenu(panel, options) + if (graphProperty.get() !in options!!.keys) { + graphProperty.set(defaultValue) + } + + panel.row(descriptor.translatedLabel) { + if (descriptor.forceDropdown == true) { + comboBox(options.keys, DropdownAutoRenderer()) + .bindItem(graphProperty) + .enabled(descriptor.editable != false) + .also { + val component = it.component + ComboboxSpeedSearch.installOn(component) + val validation = + BuiltinValidations.isAnyOf(component::getSelectedItem, options.keys, component) + it.validationOnInput(validation) + it.validationOnApply(validation) + } + } else { + segmentedButton(options.keys) { text = options[it] ?: it.toString() } + .bind(graphProperty) + .enabled(descriptor.editable != false) + .maxButtonsCount(4) + .validation { + val message = MCDevBundle("creator.validation.invalid_option") + addInputRule(message) { it.selectedItem !in options.keys } + addApplyRule(message) { it.selectedItem !in options.keys } + } + } + }.propertyVisibility() } else { buildSimpleUi(panel) } From bb2acd611284142185d9119d52ed251c83e95910 Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sat, 17 Jan 2026 15:24:51 +0100 Subject: [PATCH 8/9] Remove unused diff --- src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt b/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt index 1b36c7215..bfe405792 100644 --- a/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt +++ b/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt @@ -38,7 +38,7 @@ abstract class SimpleCreatorProperty( valueType: Class ) : CreatorProperty(descriptor, context, valueType) { - val options: Map? = makeOptionsList() + private val options: Map? = makeOptionsList() private fun makeOptionsList(): Map? { val map = when (val options = descriptor.options) { From 78f08bc7f54eea053e6cc679c989c392f89fbd8f Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sun, 18 Jan 2026 12:12:04 +0100 Subject: [PATCH 9/9] Add GradlePluginSelectorCreatorProperty.kt for inline Gradle plugin enable/version selection --- .../GradlePluginSelectorCreatorProperty.kt | 256 ++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 3 + .../messages/MinecraftDevelopment.properties | 17 +- 3 files changed, 266 insertions(+), 10 deletions(-) create mode 100644 src/main/kotlin/creator/custom/types/GradlePluginSelectorCreatorProperty.kt diff --git a/src/main/kotlin/creator/custom/types/GradlePluginSelectorCreatorProperty.kt b/src/main/kotlin/creator/custom/types/GradlePluginSelectorCreatorProperty.kt new file mode 100644 index 000000000..df8a63523 --- /dev/null +++ b/src/main/kotlin/creator/custom/types/GradlePluginSelectorCreatorProperty.kt @@ -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 + } + } + } + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 376351abe..97e2605eb 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -199,6 +199,9 @@ + diff --git a/src/main/resources/messages/MinecraftDevelopment.properties b/src/main/resources/messages/MinecraftDevelopment.properties index 71144b95d..19e17f991 100644 --- a/src/main/resources/messages/MinecraftDevelopment.properties +++ b/src/main/resources/messages/MinecraftDevelopment.properties @@ -111,20 +111,17 @@ creator.ui.velocity_version.label=Velocity Version\: creator.ui.versions_download.label=Downloading versions... creator.ui.use_version_catalog.label=Use Gradle version catalog\: creator.ui.include_plugin_bootstrap.label=Include plugin bootstrap file\: -creator.ui.use_gremlin.label=Use gremlin for dependency resolution\: -creator.ui.gremlin_version.label=gremlin version\: creator.ui.include_plugin_loader.label=Include plugin loader file\: creator.ui.java_plugin_loader.label=Generate the plugin loader in Java\: -creator.ui.use_resource_factory_plugin.label=Use the resource-factory Gradle plugin\: -creator.ui.resource_factory_version.label=resource-factory version\: -creator.ui.use_paperweight_userdev.label=Use the paperweight-userdev Gradle plugin\: -creator.ui.paperweight_version.label=paperweight-userdev version\: -creator.ui.include_shadow_plugin.label=Include the shadow Gradle plugin\: -creator.ui.shadow_version.label=shadow version\: -creator.ui.include_run_paper_plugin.label=Include the run-paper Gradle plugin\: -creator.ui.run_paper_version.label=run-paper version\: + +creator.ui.run_paper_plugin.label=Use run-paper Gradle plugin\: creator.ui.accept_eula.label=Add eula-agree runServer JVM flag\: creator.ui.accept_eula.warning=By using this feature, you agree to the 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