⁠
joseph burton: Minecraft new project wizard (#1933 )
* Forge creator almost working
* Store authors and website for next time the user uses the creator
* Don't allow for dialog submission before a latent step is finished
* Fabric mod creator
* Add modid.mixins.json to Forge project creator
* Start with Architectury creator
* Fix long-running tasks that need to run after one another
* Apply website in Forge creator
* Set the correct gradle version in the Forge project creator
* Architectury should be finished but it's not working, not sure why
* Add support for custom build systems via extension points
* Sponge project creator, first maven implementation
* Remove unsupported platforms from the readme
* Add missing platforms to readme
* Replace some silly code with less silly code
* Add Spigot and Paper creators
* Add Velocity creator
* Add BungeeCord and Waterfall creator
* Delete unused code
* ktlint format
* Apply the correct JDK version depending on the platform and version
* Fix license years after merge
* Improvements and fixes to JDK selector UI
* Fix architectury template
* Fix issues with Sponge creator
* Fix IncorrectOperationException in AbstractLatentStep
* Add option to create a git repo (which also creates a gitignore)
* UI improvements
* Add a message encouraging users to report outdated templates
* Add note to project wizard outdated form warning not to request new platforms
* Paper before Spigot
* Remove dependency on TemplateMakerFabric
* Reorganize creators into multiple files and repackage some things
* Improve documentation
* Improve ergonomics for build system properties, main class name, repository, issue tracker
* Limit Sponge API version to 8 and above in dropdown box
* Prevent class name from messing up when the project name contains dots
* Forge creator almost working
* Store authors and website for next time the user uses the creator
* Don't allow for dialog submission before a latent step is finished
* Fabric mod creator
* Add modid.mixins.json to Forge project creator
* Start with Architectury creator
* Fix long-running tasks that need to run after one another
* Apply website in Forge creator
* Set the correct gradle version in the Forge project creator
* Architectury should be finished but it's not working, not sure why
* Add support for custom build systems via extension points
* Sponge project creator, first maven implementation
* Remove unsupported platforms from the readme
* Add missing platforms to readme
* Replace some silly code with less silly code
* Add Spigot and Paper creators
* Add Velocity creator
* Add BungeeCord and Waterfall creator
* Delete unused code
* ktlint format
* Apply the correct JDK version depending on the platform and version
* Fix license years after merge
* Improvements and fixes to JDK selector UI
* Fix architectury template
* Fix issues with Sponge creator
* Fix IncorrectOperationException in AbstractLatentStep
* Add option to create a git repo (which also creates a gitignore)
* UI improvements
* Add a message encouraging users to report outdated templates
* Add note to project wizard outdated form warning not to request new platforms
* Paper before Spigot
* Remove dependency on TemplateMakerFabric
* Reorganize creators into multiple files and repackage some things
* Improve documentation
* Improve ergonomics for build system properties, main class name, repository, issue tracker
* Limit Sponge API version to 8 and above in dropdown box
* Prevent class name from messing up when the project name contains dots
- /*
- * Minecraft Dev for IntelliJ
- *
- * https://minecraftdev.org
- *
- * Copyright (c) 2023 minecraft-dev
- *
- * MIT License
- */
- package com.demonwav.mcdev.creator
- import com.demonwav.mcdev.creator.buildsystem.gradle.GradleBuildSystem
- import com.demonwav.mcdev.creator.buildsystem.gradle.GradleCreator
- import com.demonwav.mcdev.util.SemanticVersion
- import com.demonwav.mcdev.util.VersionRange
- import com.intellij.ide.util.projectWizard.ModuleWizardStep
- import com.intellij.ide.util.projectWizard.WizardContext
- import com.intellij.openapi.observable.properties.PropertyGraph
- import com.intellij.openapi.projectRoots.JavaSdk
- import com.intellij.openapi.projectRoots.JavaSdkVersion
- import com.intellij.openapi.projectRoots.Sdk
- import com.intellij.openapi.roots.ui.configuration.JdkComboBox
- import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel
- import com.intellij.ui.SortedComboBoxModel
- import com.intellij.ui.components.Label
- import com.intellij.ui.dsl.builder.Panel
- import com.intellij.ui.dsl.builder.Row
- import com.intellij.ui.dsl.builder.bindItem
- import com.intellij.ui.dsl.builder.panel
- import com.intellij.util.ui.UIUtil
- import javax.swing.JComponent
- import org.gradle.util.GradleVersion
- class ProjectSetupFinalizerWizardStep(
- val creator: MinecraftProjectCreator,
- val context: WizardContext
- ) : ModuleWizardStep() {
- private val finalizers: List<ProjectSetupFinalizer> =
- listOf(JdkProjectSetupFinalizer(), GradleProjectSetupFinalizer())
- private val finalizersWithRow: MutableMap<ProjectSetupFinalizer, Row> = linkedMapOf()
- private val applicableFinalizers: MutableSet<ProjectSetupFinalizer> = linkedSetOf()
- private val panel by lazy {
- panel {
- row(Label("<html><font size=\"6\">Project finalization</size></html>")) {}
- finalizers.forEach { finalizer ->
- val row = row("<html><font size=\"5\">${finalizer.title}</size></html>") {
- panel {
- with(finalizer) {
- buildComponent(creator, context)
- }
- }
- }
- finalizersWithRow[finalizer] = row
- }
- }
- }
- override fun isStepVisible(): Boolean = true
- override fun getComponent(): JComponent = panel
- override fun updateStep() {
- applicableFinalizers.clear()
- for ((finalizer, row) in finalizersWithRow) {
- if (finalizer.isApplicable(creator, context)) {
- applicableFinalizers.add(finalizer)
- finalizer.validateConfigs(creator, context)
- row.visible(true)
- } else {
- row.visible(false)
- }
- }
- }
- override fun updateDataModel(): Unit = applicableFinalizers.forEach { it.apply(creator, context) }
- override fun validate(): Boolean = applicableFinalizers.all { it.validateChanges(creator, context) }
- }
- /**
- * Used to adjust project configurations before project creation begins, or simply display a summary.
- * Can also block project creation if problems are found with the configurations (such as version incompatibilities.)
- */
- interface ProjectSetupFinalizer {
- val title: String
- /**
- * Builds the component to display in a titled row ([title])
- */
- fun Panel.buildComponent(creator: MinecraftProjectCreator, context: WizardContext)
- /**
- * Whether this finalizer makes sense to appear in the given context.
- *
- * If `false` is returned the component of this finalizer will not be shown, and [validateConfigs],
- * [validateChanges] and [apply] won't be called until it returns `true`.
- *
- * @return `true` if this finalizer applies to the given context, `false` otherwise
- */
- fun isApplicable(creator: MinecraftProjectCreator, context: WizardContext): Boolean
- /**
- * Validates the existing [ProjectConfig]s of this wizard. You can also initialize
- *
- * Finalizers are expected to display errors in their own component.
- *
- * @return `true` if the project setup is valid, `false` otherwise.
- */
- fun validateConfigs(creator: MinecraftProjectCreator, context: WizardContext): Boolean
- /**
- * Validates the changes made in this finalizer's component.
- *
- * @return `true` if the changes are valid, `false` otherwise.
- */
- fun validateChanges(creator: MinecraftProjectCreator, context: WizardContext): Boolean
- /**
- * Applies the changes validated in [validateChanges] to the project configuration.
- */
- fun apply(creator: MinecraftProjectCreator, context: WizardContext)
- }
- class JdkProjectSetupFinalizer : ProjectSetupFinalizer {
- private val errorLabel = Label("", fontColor = UIUtil.FontColor.BRIGHTER)
- .apply {
- icon = UIUtil.getErrorIcon()
- isVisible = false
- }
- private val sdksModel = ProjectSdksModel()
- private lateinit var jdkBox: JdkComboBox
- private var minimumVersion: JavaSdkVersion = JavaSdkVersion.JDK_1_8
- private fun highestJDKVersionRequired(creator: MinecraftProjectCreator): JavaSdkVersion? {
- val javaVersionRequired = creator.config?.javaVersion ?: return null
- return JavaSdkVersion.fromJavaVersion(javaVersionRequired).also {
- minimumVersion = it ?: JavaSdkVersion.JDK_1_8
- }
- }
- private fun isUsingCompatibleJdk(creator: MinecraftProjectCreator, sdk: Sdk): Boolean {
- val requiredJdkVersion = highestJDKVersionRequired(creator) ?: return false
- return JavaSdk.getInstance().isOfVersionOrHigher(sdk, requiredJdkVersion)
- }
- override val title: String = "JDK"
- override fun Panel.buildComponent(creator: MinecraftProjectCreator, context: WizardContext) {
- row(errorLabel) {}
- jdkBox = JdkComboBox(
- context.project,
- sdksModel,
- { it is JavaSdk },
- { JavaSdk.getInstance().isOfVersionOrHigher(it, minimumVersion) },
- null,
- null,
- )
- reloadJdkBox(context)
- if (jdkBox.itemCount > 0) {
- jdkBox.selectedIndex = 0
- }
- row("JDK version:") {
- cell(jdkBox)
- }
- }
- override fun isApplicable(creator: MinecraftProjectCreator, context: WizardContext): Boolean {
- reloadJdkBox(context)
- return true
- }
- private fun reloadJdkBox(context: WizardContext) {
- sdksModel.syncSdks()
- sdksModel.reset(context.project)
- jdkBox.reloadModel()
- }
- private fun updateUi(usingCompatibleJdk: Boolean) {
- if (usingCompatibleJdk) {
- errorLabel.text = ""
- errorLabel.isVisible = false
- return
- }
- errorLabel.text = "Project requires at least Java ${minimumVersion.description}"
- errorLabel.isVisible = true
- }
- override fun validateConfigs(creator: MinecraftProjectCreator, context: WizardContext): Boolean {
- val projectJdk = context.projectJdk ?: return true
- val usingCompatibleJdk = isUsingCompatibleJdk(creator, projectJdk)
- if (!usingCompatibleJdk) {
- jdkBox.setInvalidJdk(projectJdk.name)
- } else {
- jdkBox.selectedJdk = sdksModel.findSdk(projectJdk.name)
- }
- updateUi(usingCompatibleJdk)
- return usingCompatibleJdk
- }
- override fun validateChanges(creator: MinecraftProjectCreator, context: WizardContext): Boolean {
- return isUsingCompatibleJdk(creator, jdkBox.selectedJdk ?: return false)
- }
- override fun apply(creator: MinecraftProjectCreator, context: WizardContext) {
- val selectedJdk = jdkBox.selectedJdk
- if (selectedJdk != null) {
- context.projectJdk = selectedJdk
- }
- }
- }
- class GradleProjectSetupFinalizer : ProjectSetupFinalizer {
- private val model = SortedComboBoxModel<SemanticVersion>(Comparator.naturalOrder())
- private val propertyGraph = PropertyGraph("GradleProjectSetupFinalizer graph")
- var gradleVersionProperty = propertyGraph.lazyProperty { SemanticVersion.release() }
- var gradleVersion: SemanticVersion by gradleVersionProperty
- private var config: ProjectConfig? = null
- private var gradleVersionRange: VersionRange? = null
- override val title: String = "Gradle"
- override fun Panel.buildComponent(creator: MinecraftProjectCreator, context: WizardContext) {
- row("Gradle version:") {
- comboBox(model)
- .bindItem(gradleVersionProperty)
- .enabled(false) // TODO load compatible Gradle versions list
- }
- }
- override fun isApplicable(creator: MinecraftProjectCreator, context: WizardContext): Boolean {
- val buildSystem = creator.buildSystem
- return buildSystem is GradleBuildSystem
- }
- override fun validateConfigs(creator: MinecraftProjectCreator, context: WizardContext): Boolean {
- config = creator.config
- if (creator.buildSystem !is GradleBuildSystem) {
- return true
- }
- val range = (creator.config as? GradleCreator)?.compatibleGradleVersions
- gradleVersionRange = range
- gradleVersion = range?.upper ?: SemanticVersion.parse(GradleVersion.current().version)
- model.clear()
- model.add(gradleVersion)
- model.selectedItem = gradleVersion
- return true
- }
- override fun validateChanges(creator: MinecraftProjectCreator, context: WizardContext): Boolean {
- if (creator.buildSystem !is GradleBuildSystem) {
- return true
- }
- return gradleVersionRange == null || gradleVersion.parts.isNotEmpty()
- }
- override fun apply(creator: MinecraftProjectCreator, context: WizardContext) {
- (creator.buildSystem as? GradleBuildSystem)?.gradleVersion = gradleVersion
- }
- }
- /*
- * Minecraft Dev for IntelliJ
- *
- * https://minecraftdev.org
- *
- * Copyright (c) 2023 minecraft-dev
- *
- * MIT License
- */
- package com.demonwav.mcdev.creator
- import com.demonwav.mcdev.util.mapFirstNotNull
- import com.demonwav.mcdev.util.toTypedArray
- import com.intellij.ide.wizard.AbstractNewProjectWizardStep
- import com.intellij.ide.wizard.NewProjectWizardStep
- import com.intellij.ide.wizard.stepSequence
- import com.intellij.openapi.extensions.ExtensionPointName
- import com.intellij.openapi.observable.properties.GraphProperty
- import com.intellij.openapi.project.Project
- import com.intellij.openapi.projectRoots.JavaSdk
- import com.intellij.openapi.projectRoots.JavaSdkVersion
- import com.intellij.openapi.projectRoots.Sdk
- import com.intellij.openapi.ui.validation.AFTER_GRAPH_PROPAGATION
- import com.intellij.openapi.ui.validation.validationErrorFor
- import com.intellij.ui.JBColor
- import com.intellij.ui.dsl.builder.Panel
- import com.intellij.ui.dsl.builder.Placeholder
- import javax.swing.JLabel
- import javax.swing.JPanel
- class ProjectSetupFinalizerWizardStep(parent: NewProjectWizardStep) : AbstractNewProjectWizardStep(parent) {
- private val finalizers: List<ProjectSetupFinalizer> by lazy {
- val factories = ProjectSetupFinalizer.EP_NAME.extensionList
- val result = mutableListOf<ProjectSetupFinalizer>()
- if (factories.isNotEmpty()) {
- var par: NewProjectWizardStep = this
- for (factory in factories) {
- val finalizer = factory.create(par)
- result += finalizer
- par = finalizer
- }
- }
- result
- }
- private val step by lazy {
- if (finalizers.isEmpty()) {
- null
- } else {
- stepSequence(finalizers[0], *finalizers.asSequence().drop(1).toTypedArray())
- }
- }
- override fun setupUI(builder: Panel) {
- step?.setupUI(builder)
- if (finalizers.isNotEmpty()) {
- builder.row {
- cell(JPanel())
- .validationRequestor(AFTER_GRAPH_PROPAGATION(propertyGraph))
- .validation(
- validationErrorFor<JPanel> {
- finalizers.mapFirstNotNull(ProjectSetupFinalizer::validate)
- }
- )
- }
- }
- }
- override fun setupProject(project: Project) {
- step?.setupProject(project)
- }
- }
- /**
- * A step applied after all other steps for all Minecraft project creators. These steps can also block project creation
- * by providing extra validations.
- *
- * To add custom project setup finalizers, register a [Factory] to the
- * `com.demonwav.minecraft-dev.projectSetupFinalizer` extension point.
- */
- interface ProjectSetupFinalizer : NewProjectWizardStep {
- companion object {
- val EP_NAME = ExtensionPointName<Factory>("com.demonwav.minecraft-dev.projectSetupFinalizer")
- }
- /**
- * Validates the existing settings of this wizard.
- *
- * @return `null` if the settings are valid, or an error message if they are invalid.
- */
- fun validate(): String? = null
- interface Factory {
- fun create(parent: NewProjectWizardStep): ProjectSetupFinalizer
- }
- }
- class JdkProjectSetupFinalizer(
- parent: NewProjectWizardStep
- ) : AbstractNewProjectWizardStep(parent), ProjectSetupFinalizer {
- private val sdkProperty: GraphProperty<Sdk?> = propertyGraph.property(null)
- private var sdk by sdkProperty
- private var sdkComboBox: JdkComboBoxWithPreference? = null
- private var preferredJdkLabel: Placeholder? = null
- private var preferredJdkReason = "these settings"
- var preferredJdk: JavaSdkVersion = JavaSdkVersion.JDK_17
- private set
- fun setPreferredJdk(value: JavaSdkVersion, reason: String) {
- preferredJdk = value
- preferredJdkReason = reason
- sdkComboBox?.setPreferredJdk(value)
- updatePreferredJdkLabel()
- }
- init {
- storeToData()
- sdkProperty.afterChange {
- updatePreferredJdkLabel()
- }
- }
- private fun updatePreferredJdkLabel() {
- val sdk = this.sdk ?: return
- val version = JavaSdk.getInstance().getVersion(sdk) ?: return
- if (version == preferredJdk) {
- preferredJdkLabel?.component = null
- } else {
- preferredJdkLabel?.component =
- JLabel("Java ${preferredJdk.description} is recommended for $preferredJdkReason")
- .also { it.foreground = JBColor.YELLOW }
- }
- }
- override fun setupUI(builder: Panel) {
- with(builder) {
- row("JDK:") {
- val sdkComboBox = jdkComboBoxWithPreference(context, sdkProperty, "${javaClass.name}.sdk")
- [email protected] = sdkComboBox.component
- [email protected] = placeholder()
- updatePreferredJdkLabel()
- }
- }
- }
- class Factory : ProjectSetupFinalizer.Factory {
- override fun create(parent: NewProjectWizardStep) = JdkProjectSetupFinalizer(parent)
- }
- }