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/CreatorTemplateProcessor.kt b/src/main/kotlin/creator/custom/CreatorTemplateProcessor.kt index f43ee2afe..0c0e66009 100644 --- a/src/main/kotlin/creator/custom/CreatorTemplateProcessor.kt +++ b/src/main/kotlin/creator/custom/CreatorTemplateProcessor.kt @@ -176,7 +176,7 @@ class CreatorTemplateProcessor( val factory = Consumer { panel -> val label = descriptor.translatedLabel - if (descriptor.collapsible == false) { + val group = if (descriptor.collapsible == false) { panel.group(label) { for (childFactory in childrenUiFactories) { childFactory.accept(this@group) @@ -190,7 +190,11 @@ class CreatorTemplateProcessor( } group.expanded = descriptor.default as? Boolean ?: false + group } + + val isVisible = CreatorProperty.setupVisibleProperty(context.graph, context.properties, reporter, descriptor.visible) + group.visibleIf(isVisible) } val order = descriptor.order ?: 0 @@ -201,10 +205,8 @@ class CreatorTemplateProcessor( reporter.fatal("Duplicate property name ${descriptor.name}") } - val prop = CreatorPropertyFactory.createFromType(descriptor.type, descriptor, context) - if (prop == null) { - reporter.fatal("Unknown template property type ${descriptor.type}") - } + val prop = CreatorPropertyFactory.createFromType(descriptor.type, descriptor, context, reporter) + ?: reporter.fatal("Unknown template property type ${descriptor.type}") prop.setupProperty(reporter) diff --git a/src/main/kotlin/creator/custom/TemplateDescriptor.kt b/src/main/kotlin/creator/custom/TemplateDescriptor.kt index 54890a8e3..ffca3e3ac 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 } } @@ -56,6 +56,7 @@ data class TemplatePropertyDescriptor( val groupProperties: List? = null, val remember: Any? = null, val visible: Any? = null, + val forceValue: Any? = null, val editable: Boolean? = null, val collapsible: Boolean? = null, val warning: String? = null, 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..de2c57940 --- /dev/null +++ b/src/main/kotlin/creator/custom/derivation/ExtractPaperApiVersionPropertyDerivation.kt @@ -0,0 +1,60 @@ +/* + * 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.MinecraftVersions +import com.demonwav.mcdev.util.SemanticVersion + +class ExtractPaperApiVersionPropertyDerivation : ExtractVersionMajorMinorPropertyDerivation() { + + override fun derive(parentValues: List): Any? { + val from = parentValues[0] as SemanticVersion + if (from >= MinecraftVersions.MC1_20_5) { + return from + } + + return super.derive(parentValues); + } + + 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/finalizers/AddGradleRunConfigFinalizer.kt b/src/main/kotlin/creator/custom/finalizers/AddGradleRunConfigFinalizer.kt new file mode 100644 index 000000000..ff124143b --- /dev/null +++ b/src/main/kotlin/creator/custom/finalizers/AddGradleRunConfigFinalizer.kt @@ -0,0 +1,43 @@ +package com.demonwav.mcdev.creator.custom.finalizers + +import com.intellij.execution.RunManager +import com.intellij.ide.util.projectWizard.WizardContext +import com.intellij.openapi.project.Project +import org.jetbrains.plugins.gradle.service.execution.GradleExternalTaskConfigurationType +import org.jetbrains.plugins.gradle.service.execution.GradleRunConfiguration + +class AddGradleRunConfigFinalizer : AddRunConfigFinalizer { + + override val executablesName: String = "tasks" + + override suspend fun execute( + context: WizardContext, + project: Project, + properties: Map, + templateProperties: Map + ) { + val tasks = properties.executables + val projectDir = context.projectFileDirectory + + val gradleType = GradleExternalTaskConfigurationType.getInstance() + + val runManager = RunManager.getInstance(project) + val runConfigName = properties["name"] as String + + val runConfiguration = GradleRunConfiguration(project, gradleType.factory, runConfigName) + + runConfiguration.settings.externalProjectPath = projectDir + runConfiguration.settings.executionName = runConfigName + runConfiguration.settings.taskNames = tasks + + val settings = runManager.createConfiguration(runConfiguration, gradleType.factory) + settings.isActivateToolWindowBeforeRun = true + settings.storeInLocalWorkspace() + + runManager.addConfiguration(settings) + + if (properties["select"] == true || runManager.selectedConfiguration == null) { + runManager.selectedConfiguration = settings + } + } +} diff --git a/src/main/kotlin/creator/custom/finalizers/AddMavenRunConfigFinalizer.kt b/src/main/kotlin/creator/custom/finalizers/AddMavenRunConfigFinalizer.kt new file mode 100644 index 000000000..01c470db4 --- /dev/null +++ b/src/main/kotlin/creator/custom/finalizers/AddMavenRunConfigFinalizer.kt @@ -0,0 +1,41 @@ +package com.demonwav.mcdev.creator.custom.finalizers + +import com.intellij.execution.RunManager +import com.intellij.ide.util.projectWizard.WizardContext +import com.intellij.openapi.project.Project +import org.jetbrains.idea.maven.execution.MavenRunConfigurationType +import org.jetbrains.idea.maven.execution.MavenRunnerParameters +import org.jetbrains.idea.maven.execution.MavenRunnerSettings + +class AddMavenRunConfigFinalizer : AddRunConfigFinalizer { + + override val executablesName: String = "goals" + + override suspend fun execute( + context: WizardContext, + project: Project, + properties: Map, + templateProperties: Map + ) { + val goals = properties.executables + val projectDir = context.projectFileDirectory + + val params = MavenRunnerParameters().also { + it.goals = goals + it.workingDirPath = projectDir + } + val settings = MavenRunConfigurationType.createRunnerAndConfigurationSettings(null, null, params, project) + + settings.name = properties["name"] as String + settings.isActivateToolWindowBeforeRun = true + settings.storeInLocalWorkspace() + + val runManager = RunManager.getInstance(project) + + runManager.addConfiguration(settings) + + if (properties["select"] == true || runManager.selectedConfiguration == null) { + runManager.selectedConfiguration = settings + } + } +} diff --git a/src/main/kotlin/creator/custom/finalizers/AddRunConfigFinalizer.kt b/src/main/kotlin/creator/custom/finalizers/AddRunConfigFinalizer.kt new file mode 100644 index 000000000..8c833ff32 --- /dev/null +++ b/src/main/kotlin/creator/custom/finalizers/AddRunConfigFinalizer.kt @@ -0,0 +1,28 @@ +package com.demonwav.mcdev.creator.custom.finalizers + +import com.demonwav.mcdev.creator.custom.TemplateValidationReporter + +interface AddRunConfigFinalizer : CreatorFinalizer { + + val executablesName: String + val Map.executables: List + @Suppress("UNCHECKED_CAST") + get() = this[executablesName] as List + + override fun validate( + reporter: TemplateValidationReporter, + properties: Map + ) { + @Suppress("UNCHECKED_CAST") + val executables = properties[executablesName] as? List + if (executables == null) { + reporter.warn("Missing list of '$executables' to execute") + } + + @Suppress("UNCHECKED_CAST") + val name = properties["name"] as? String + if (name == null) { + reporter.warn("Missing task name") + } + } +} diff --git a/src/main/kotlin/creator/custom/finalizers/ImportGradleProjectFinalizer.kt b/src/main/kotlin/creator/custom/finalizers/ImportGradleProjectFinalizer.kt index bda11d303..650e8aad8 100644 --- a/src/main/kotlin/creator/custom/finalizers/ImportGradleProjectFinalizer.kt +++ b/src/main/kotlin/creator/custom/finalizers/ImportGradleProjectFinalizer.kt @@ -21,8 +21,15 @@ package com.demonwav.mcdev.creator.custom.finalizers import com.intellij.ide.util.projectWizard.WizardContext +import com.intellij.openapi.application.EDT import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.project.Project +import com.intellij.openapi.wm.ToolWindowManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext import org.jetbrains.plugins.gradle.service.project.open.canLinkAndRefreshGradleProject import org.jetbrains.plugins.gradle.service.project.open.linkAndSyncGradleProject @@ -33,13 +40,40 @@ class ImportGradleProjectFinalizer : CreatorFinalizer { project: Project, properties: Map, templateProperties: Map - ) { + ) = coroutineScope { val projectDir = context.projectFileDirectory val canLink = canLinkAndRefreshGradleProject(projectDir, project, showValidationDialog = false) thisLogger().info("canLink = $canLink projectDir = $projectDir") + if (canLink) { - linkAndSyncGradleProject(project, projectDir) + val link = async { + linkAndSyncGradleProject(project, projectDir) + } + + openBuildToolWindow(project) + + link.await() + thisLogger().info("Linking done") } } } + +suspend fun openBuildToolWindow(project: Project) = coroutineScope { + runCatching { + // Try to open the tool window after starting the sync. + // Having the tool window open will provide better context to the user about what's going on. + // The Build tool window isn't registered until a build is running, so we can't just open the tool window + // like normal here, we have to wait until after the build starts. + val manager = ToolWindowManager.getInstance(project) + for (i in 0 until 5) { + delay(250) + manager.getToolWindow("Build")?.let { + withContext(Dispatchers.EDT) { + it.show() + } + break + } + } + } +} diff --git a/src/main/kotlin/creator/custom/finalizers/ImportMavenProjectFinalizer.kt b/src/main/kotlin/creator/custom/finalizers/ImportMavenProjectFinalizer.kt index 36eb7bf57..9befc51d1 100644 --- a/src/main/kotlin/creator/custom/finalizers/ImportMavenProjectFinalizer.kt +++ b/src/main/kotlin/creator/custom/finalizers/ImportMavenProjectFinalizer.kt @@ -25,6 +25,8 @@ import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VfsUtil import java.nio.file.Path +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope import org.jetbrains.idea.maven.buildtool.MavenSyncSpec import org.jetbrains.idea.maven.project.MavenProjectsManager @@ -35,15 +37,22 @@ class ImportMavenProjectFinalizer : CreatorFinalizer { project: Project, properties: Map, templateProperties: Map - ) { + ) = coroutineScope { val projectDir = context.projectFileDirectory val pomFile = VfsUtil.findFile(Path.of(projectDir).resolve("pom.xml"), true) - ?: return + ?: return@coroutineScope thisLogger().info("Invoking import on EDT pomFile = ${pomFile.path}") val projectsManager = MavenProjectsManager.getInstance(project) projectsManager.addManagedFiles(listOf(pomFile)) - projectsManager.updateAllMavenProjects(MavenSyncSpec.incremental("ImportMavenProjectFinalizer", false)) + + val import = async { + projectsManager.updateAllMavenProjects(MavenSyncSpec.incremental("ImportMavenProjectFinalizer", false)) + } + + openBuildToolWindow(project) + + import.await() thisLogger().info("Import finished") } diff --git a/src/main/kotlin/creator/custom/finalizers/RunGradleTasksFinalizer.kt b/src/main/kotlin/creator/custom/finalizers/RunGradleTasksFinalizer.kt index 126696fc3..b169d3086 100644 --- a/src/main/kotlin/creator/custom/finalizers/RunGradleTasksFinalizer.kt +++ b/src/main/kotlin/creator/custom/finalizers/RunGradleTasksFinalizer.kt @@ -50,7 +50,7 @@ class RunGradleTasksFinalizer : CreatorFinalizer { val projectDir = context.projectDirectory thisLogger().info("tasks = $tasks projectDir = $projectDir") - runGradleTaskAndWait(project, projectDir) { settings -> + runGradleTaskAndWait(project, projectDir, toolWindow = true) { settings -> settings.taskNames = tasks } 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/ArchitecturyVersionsCreatorProperty.kt b/src/main/kotlin/creator/custom/types/ArchitecturyVersionsCreatorProperty.kt index c6539de91..b026e47b2 100644 --- a/src/main/kotlin/creator/custom/types/ArchitecturyVersionsCreatorProperty.kt +++ b/src/main/kotlin/creator/custom/types/ArchitecturyVersionsCreatorProperty.kt @@ -21,7 +21,6 @@ package com.demonwav.mcdev.creator.custom.types import com.demonwav.mcdev.asset.MCDevBundle -import com.demonwav.mcdev.asset.MCDevBundle.invoke import com.demonwav.mcdev.creator.collectMavenVersions import com.demonwav.mcdev.creator.custom.BuiltinValidations import com.demonwav.mcdev.creator.custom.CreatorContext diff --git a/src/main/kotlin/creator/custom/types/BooleanCreatorProperty.kt b/src/main/kotlin/creator/custom/types/BooleanCreatorProperty.kt index 6b554cca8..8548e02b2 100644 --- a/src/main/kotlin/creator/custom/types/BooleanCreatorProperty.kt +++ b/src/main/kotlin/creator/custom/types/BooleanCreatorProperty.kt @@ -22,6 +22,7 @@ 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.intellij.icons.AllIcons import com.intellij.ui.content.AlertIcon import com.intellij.ui.dsl.builder.Panel @@ -39,6 +40,9 @@ class BooleanCreatorProperty( override fun deserialize(string: String): Boolean = string.toBoolean() + override fun handleForceValueProperty(original: Boolean, string: String): Boolean = + string.toBooleanStrictOrNull() ?: original + override fun buildSimpleUi(panel: Panel) { val label = descriptor.translatedLabel panel.row(label) { @@ -49,16 +53,21 @@ class BooleanCreatorProperty( .comment(descriptor.translate(warning)) } - this.checkBox(label.removeSuffix(":").trim()) + val checkbox = this.checkBox(label.removeSuffix(":").trim()) .bindSelected(graphProperty) .enabled(descriptor.editable != false) + + val checkboxUpdater = createCheckboxEnabledToggleMethod(graphProperty, checkbox) + checkboxUpdater.update(forceValueProperty?.get()) + forceValueProperty?.afterChange(checkboxUpdater::update) }.propertyVisibility() } class Factory : CreatorPropertyFactory { override fun create( descriptor: TemplatePropertyDescriptor, - context: CreatorContext - ): CreatorProperty<*> = BooleanCreatorProperty(descriptor, context) + context: CreatorContext, + reporter: TemplateValidationReporter + ): CreatorProperty<*> = BooleanCreatorProperty(descriptor, context).initForceValueProperty(reporter) } } diff --git a/src/main/kotlin/creator/custom/types/CreatorProperty.kt b/src/main/kotlin/creator/custom/types/CreatorProperty.kt index 94a536367..962692f1e 100644 --- a/src/main/kotlin/creator/custom/types/CreatorProperty.kt +++ b/src/main/kotlin/creator/custom/types/CreatorProperty.kt @@ -35,6 +35,8 @@ import com.intellij.openapi.observable.properties.ObservableMutableProperty import com.intellij.openapi.observable.properties.PropertyGraph import com.intellij.openapi.observable.util.bindStorage import com.intellij.openapi.observable.util.transform +import com.intellij.ui.components.JBCheckBox +import com.intellij.ui.dsl.builder.Cell import com.intellij.ui.dsl.builder.Panel import com.intellij.ui.dsl.builder.Row @@ -55,6 +57,8 @@ abstract class CreatorProperty( private var derivation: PreparedDerivation? = null private lateinit var visibleProperty: GraphProperty + var forceValueProperty: GraphProperty? = null + abstract val graphProperty: GraphProperty abstract fun createDefaultValue(raw: Any?): T @@ -63,11 +67,18 @@ abstract class CreatorProperty( abstract fun deserialize(string: String): T + open fun handleForceValueProperty(original: T, string: String): T = original + open fun toStringProperty(graphProperty: GraphProperty): ObservableMutableProperty = graphProperty.transform(::serialize, ::deserialize) open fun get(): T? { - val value = graphProperty.get() + val forcedValue = forceValueProperty?.get() + var value = graphProperty.get() + if (forcedValue != null) { + value = handleForceValueProperty(value, forcedValue) + } + if (descriptor.nullIfDefault == true) { val default = createDefaultValue(descriptor.default) if (value == default) { @@ -227,75 +238,193 @@ abstract class CreatorProperty( protected fun Row.propertyVisibility(): Row = this.visibleIf(visibleProperty) - private fun setupVisibleProperty( + fun setupVisibleProperty( reporter: TemplateValidationReporter, visibility: Any? ): GraphProperty { - val prop = graph.property(true) - if (visibility == null || visibility is Boolean) { - prop.set(visibility != false) - return prop - } + return setupVisibleProperty(graph, properties, reporter, visibility) + } - if (visibility !is Map<*, *>) { - reporter.error("Visibility can only be a boolean or an object") - return prop - } + fun initForceValueProperty(reporter: TemplateValidationReporter): CreatorProperty { + this.forceValueProperty = setupForceValueProperty(graph, properties, reporter, descriptor.forceValue) + return this + } - var dependsOn = visibility["dependsOn"] - if (dependsOn !is String && (dependsOn !is List<*> || dependsOn.any { it !is String })) { - reporter.error( - "Expected 'visible' to have a 'dependsOn' value that is either a string or a list of strings" - ) - return prop + fun createCheckboxEnabledToggleMethod( + property: ObservableMutableProperty, + checkbox: Cell + ): CheckboxUpdater { + class Inner : CheckboxUpdater { + private var previousEnabledStatus = property.get() + override fun update(str: String?) { + if (str == null) { + checkbox.enabled(descriptor.editable != false) + property.set(previousEnabledStatus) + } else { + str.toBooleanStrictOrNull()?.let { + checkbox.enabled(false) + previousEnabledStatus = property.get() + property.set(it) + } + } + } } - val dependenciesNames = when (dependsOn) { - is String -> setOf(dependsOn) - is Collection<*> -> dependsOn.filterIsInstance().toSet() - else -> throw IllegalStateException("Should not be reached") - } - val dependencies = dependenciesNames.mapNotNull { - val dependency = this.properties[it] - if (dependency == null) { - reporter.error("Visibility dependency '$it' does not exist") + return Inner() + } + + companion object { + private fun obtainDependencies( + properties: Map>, + reporter: TemplateValidationReporter, + property: Map<*, *>, + name: String + ): List>? { + val dependsOn = property["dependsOn"] + if (dependsOn !is String && (dependsOn !is List<*> || dependsOn.any { it !is String })) { + reporter.error( + "Expected '$name' to have a 'dependsOn' value that is either a string or a list of strings" + ) + return null + } + + val dependenciesNames = when (dependsOn) { + is String -> setOf(dependsOn) + is Collection<*> -> dependsOn.filterIsInstance().toSet() + else -> throw IllegalStateException("Should not be reached") + } + val dependencies = dependenciesNames.mapNotNull { + val dependency = properties[it] + if (dependency == null) { + reporter.error("'$name' dependency '$it' does not exist") + } + dependency + } + if (dependencies.size != dependenciesNames.size) { + // Errors have already been reported + return null } - dependency + + return dependencies } - if (dependencies.size != dependenciesNames.size) { - // Errors have already been reported + + private fun setupDependableProperty( + dependencies: List>, + reporter: TemplateValidationReporter, + property: Map<*, *>, + name: String, + prop: GraphProperty + ): GraphProperty { + val condition = property["condition"] + if (condition !is String) { + reporter.error("Expected '$name' to have a 'condition' string") + return prop + } + + var didInitialUpdate = false + val update: () -> Boolean = { + val conditionProperties = dependencies.associate { prop -> prop.descriptor.name to prop.get() } + val result = TemplateEvaluator.condition(conditionProperties, condition) + val exception = result.exceptionOrNull() + if (exception != null) { + if (!didInitialUpdate) { + didInitialUpdate = true + reporter.error("Failed to compute initial $name-property: ${exception.message}") + thisLogger().info("Failed to compute initial $name-property: ${exception.message}", exception) + } else { + thisLogger().error("Failed to compute initial $name-property: ${exception.message}", exception) + } + } + + result.getOrDefault(true) + } + + prop.set(update()) + for (dependency in dependencies) { + prop.dependsOn(dependency.graphProperty, deleteWhenModified = false, update) + } + return prop } - val condition = visibility["condition"] - if (condition !is String) { - reporter.error("Expected 'visible' to have a 'condition' string") - return prop + fun setupVisibleProperty( + graph: PropertyGraph, + properties: Map>, + reporter: TemplateValidationReporter, + visibility: Any? + ): GraphProperty { + val prop = graph.property(true) + if (visibility == null || visibility is Boolean) { + prop.set(visibility != false) + return prop + } + + if (visibility !is Map<*, *>) { + reporter.error("Visibility can only be a boolean or an object") + return prop + } + + val dependencies = obtainDependencies(properties, reporter, visibility, "visible") + ?: return prop // The error has already been reported + + return setupDependableProperty(dependencies, reporter, visibility, "visible", prop) } - var didInitialUpdate = false - val update: () -> Boolean = { - val conditionProperties = dependencies.associate { prop -> prop.descriptor.name to prop.get() } - val result = TemplateEvaluator.condition(conditionProperties, condition) - val exception = result.exceptionOrNull() - if (exception != null) { - if (!didInitialUpdate) { - didInitialUpdate = true - reporter.error("Failed to compute initial visibility: ${exception.message}") - thisLogger().info("Failed to compute initial visibility: ${exception.message}", exception) + fun setupForceValueProperty( + graph: PropertyGraph, + properties: Map>, + reporter: TemplateValidationReporter, + forceValue: Any? + ): GraphProperty { + val out = graph.property(null) + + if (forceValue == null) { + return out; + } + + if (forceValue !is Map<*, *>) { + reporter.error("ForceValue can only be a boolean or an object") + return out + } + + val dependencies = obtainDependencies(properties, reporter, forceValue, "forceValue") + ?: return out // The error has already been reported + + val value = forceValue["value"] + if (value !is String) { + reporter.error("Expected 'forceValue' to have a 'value' string") + return out + } + + val conditionProperty = + setupDependableProperty(dependencies, reporter, forceValue, "forceValue", graph.property(false)) + + val update: () -> String? = { + if (conditionProperty.get()) { + val conditionProperties = dependencies.associate { prop -> prop.descriptor.name to prop.get() } + val result = TemplateEvaluator.template(conditionProperties, value) + val exception = result.exceptionOrNull() + if (exception != null) { + thisLogger().error("Failed to compute forceValue-property: ${exception.message}", exception) + } + + result.getOrNull() } else { - thisLogger().error("Failed to compute initial visibility: ${exception.message}", exception) + null } } - result.getOrDefault(true) - } + out.set(update()) + for (dependency in dependencies) { + out.dependsOn(dependency.graphProperty, deleteWhenModified = false, update) + } - prop.set(update()) - for (dependency in dependencies) { - prop.dependsOn(dependency.graphProperty, deleteWhenModified = false, update) + return out } + } - return prop + @FunctionalInterface + interface CheckboxUpdater { + fun update(str: String?) } } diff --git a/src/main/kotlin/creator/custom/types/CreatorPropertyFactory.kt b/src/main/kotlin/creator/custom/types/CreatorPropertyFactory.kt index af1ccad69..ddb150d9a 100644 --- a/src/main/kotlin/creator/custom/types/CreatorPropertyFactory.kt +++ b/src/main/kotlin/creator/custom/types/CreatorPropertyFactory.kt @@ -22,6 +22,7 @@ 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.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.extensions.RequiredElement import com.intellij.openapi.util.KeyedExtensionCollector @@ -42,13 +43,20 @@ interface CreatorPropertyFactory { fun createFromType( type: String, descriptor: TemplatePropertyDescriptor, - context: CreatorContext + context: CreatorContext, + reporter: TemplateValidationReporter ): CreatorProperty<*>? { - return COLLECTOR.findSingle(type)?.create(descriptor, context) + return COLLECTOR.findSingle(type)?.create(descriptor, context, reporter) } } - fun create(descriptor: TemplatePropertyDescriptor, context: CreatorContext): CreatorProperty<*> + fun create(descriptor: TemplatePropertyDescriptor, context: CreatorContext, reporter: TemplateValidationReporter): CreatorProperty<*> { + return create(descriptor, context) + } + + fun create(descriptor: TemplatePropertyDescriptor, context: CreatorContext): CreatorProperty<*> { + throw UnsupportedOperationException("No create method has been overridden.") + } } class CreatorPropertyFactoryBean : 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..e0c81576c --- /dev/null +++ b/src/main/kotlin/creator/custom/types/GradlePluginSelectorCreatorProperty.kt @@ -0,0 +1,270 @@ +/* + * 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("") + + private val defaultValue = createDefaultValue(descriptor.default) + + override val graphProperty: GraphProperty = graph.property(defaultValue) + 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 Boolean) { + return Holder(SemanticVersion(emptyList()), raw) + } + 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 handleForceValueProperty( + original: Holder, + string: String + ): Holder = original.copy(enabled = string.toBooleanStrictOrNull() ?: original.enabled) + + override fun buildUi(panel: Panel) { + val label = descriptor.translatedLabel + panel.row(label) { + val checkbox = checkBox("") + .bindSelected(enabledProperty) + .enabled(descriptor.editable != false) + + val checkboxUpdater = createCheckboxEnabledToggleMethod(enabledProperty, checkbox) + checkboxUpdater.update(forceValueProperty?.get()) + forceValueProperty?.afterChange(checkboxUpdater::update) + + 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, + reporter: TemplateValidationReporter + ): CreatorProperty<*> = + GradlePluginSelectorCreatorProperty(descriptor, context).initForceValueProperty(reporter) + } + + @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/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/kotlin/creator/custom/types/PaperVersionCreatorProperty.kt b/src/main/kotlin/creator/custom/types/PaperVersionCreatorProperty.kt new file mode 100644 index 000000000..90866490d --- /dev/null +++ b/src/main/kotlin/creator/custom/types/PaperVersionCreatorProperty.kt @@ -0,0 +1,129 @@ +/* + * 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.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.Dispatchers +import kotlinx.coroutines.launch +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 { + private var paperVersions: List? = null + + suspend fun getPaperVersions(): List { + paperVersions?.let { return it } + + val client = HttpClient() + 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) { + 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 { + override fun create( + descriptor: TemplatePropertyDescriptor, + context: CreatorContext + ): CreatorProperty<*> = PaperVersionCreatorProperty(descriptor, context) + } +} 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) } diff --git a/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt b/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt index 5f5d2a5a7..bfe405792 100644 --- a/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt +++ b/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt @@ -125,7 +125,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/kotlin/platform/mcp/fabricloom/FabricLoomDecompileSourceProvider.kt b/src/main/kotlin/platform/mcp/fabricloom/FabricLoomDecompileSourceProvider.kt index b09cb0b5c..8039faf5b 100644 --- a/src/main/kotlin/platform/mcp/fabricloom/FabricLoomDecompileSourceProvider.kt +++ b/src/main/kotlin/platform/mcp/fabricloom/FabricLoomDecompileSourceProvider.kt @@ -92,6 +92,7 @@ class FabricLoomDecompileSourceProvider : AttachSourcesProvider { runGradleTaskWithCallback( project, Paths.get(projectPath), + toolWindow = false, { settings -> settings.taskNames = listOf(decompiler.taskName) }, taskCallback, ) diff --git a/src/main/kotlin/platform/mcp/vanillagradle/VanillaGradleDecompileSourceProvider.kt b/src/main/kotlin/platform/mcp/vanillagradle/VanillaGradleDecompileSourceProvider.kt index a54f8b8b1..8b5b3ad99 100644 --- a/src/main/kotlin/platform/mcp/vanillagradle/VanillaGradleDecompileSourceProvider.kt +++ b/src/main/kotlin/platform/mcp/vanillagradle/VanillaGradleDecompileSourceProvider.kt @@ -87,6 +87,7 @@ class VanillaGradleDecompileSourceProvider : AttachSourcesProvider { runGradleTaskWithCallback( project, Paths.get(projectPath), + toolWindow = false, { settings -> settings.taskNames = listOf(decompileTaskName) }, taskCallback, ) diff --git a/src/main/kotlin/util/gradle-util.kt b/src/main/kotlin/util/gradle-util.kt index e6038fe61..efaf3fedb 100644 --- a/src/main/kotlin/util/gradle-util.kt +++ b/src/main/kotlin/util/gradle-util.kt @@ -30,14 +30,24 @@ import java.nio.file.Path import java.util.concurrent.CountDownLatch import org.jetbrains.plugins.gradle.util.GradleConstants -fun runGradleTask(project: Project, dir: Path, func: (ExternalSystemTaskExecutionSettings) -> Unit) { - runGradleTaskWithCallback(project, dir, func, GradleCallback(null)) +fun runGradleTask( + project: Project, + dir: Path, + toolWindow: Boolean = false, + func: (ExternalSystemTaskExecutionSettings) -> Unit, +) { + runGradleTaskWithCallback(project, dir, toolWindow, func, GradleCallback(null)) } -fun runGradleTaskAndWait(project: Project, dir: Path, func: (ExternalSystemTaskExecutionSettings) -> Unit) { +fun runGradleTaskAndWait( + project: Project, + dir: Path, + toolWindow: Boolean = false, + func: (ExternalSystemTaskExecutionSettings) -> Unit, +) { val latch = CountDownLatch(1) - runGradleTaskWithCallback(project, dir, func, GradleCallback(latch)) + runGradleTaskWithCallback(project, dir, toolWindow, func, GradleCallback(latch)) latch.await() } @@ -45,6 +55,7 @@ fun runGradleTaskAndWait(project: Project, dir: Path, func: (ExternalSystemTaskE fun runGradleTaskWithCallback( project: Project, dir: Path, + toolWindow: Boolean = false, func: (ExternalSystemTaskExecutionSettings) -> Unit, callback: TaskCallback, ) { @@ -62,7 +73,7 @@ fun runGradleTaskWithCallback( GradleConstants.SYSTEM_ID, callback, ProgressExecutionMode.IN_BACKGROUND_ASYNC, - false, + toolWindow, ) } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 62af2b7a1..83e623b79 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -199,6 +199,9 @@ + @@ -214,6 +217,9 @@ + + + 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