Skip to content

Commit

Permalink
Repo-based creator templates (#2304)
Browse files Browse the repository at this point in the history
* Initial custom template system

* Add remember, editable and property derivation

* Add hidden properties

* Implement property derivation for all types

* Actual types implementation

Also fix template condition evaluation

* Some more stuff

* Some more refactoring to get things working nicely

* Move CreatorProperties to an EP

* Add property UI order

* Move custom template to a separate module builder

* Add default values to template descriptor

* Add option to output null value if default

* Add group/collapsibleGroup support

* Dropdown labels

* Use segmented buttons for options by default

* Support comma separated string lists

* Add TemplateProviders

* WIP Sponge creator

* Support built-in templates

* Support multiple templates per provider

* Remove commented code

* Remember used templates

* Move CustomPlatformStep to the appropriate package

* Fix recent template provider being saved in the recent list

Also always show the templates list in recent templates

* Switch BuiltInTemplateProvider to flat dir

* Add NeoForge specific stuff

* Add TemplateApi marker annotation for template models

* Move RecentProjectTemplates out of the models package

* Remove old commented code

* Replace usage of kotlin plugin function by stdlib one

* Always refresh template files

* Add fabric_versions

* Add license property

* Handle template descriptor deserialization errors

* Basic template inheritance and template labels

* Add basic versioning

* Display all yarn/fabric api versions if none match the selected game version

* Add property validation support

* Don't even call buildUi if property is hidden

* Add "select" derivation

* Fix templates not getting access to builtin properties

* Include license displayname in LicenseData

* Add 1.16 & 1.20.6 to MinecraftVersions

* Remove unused class

* Some ClassFqn doc & withClassName

* Add ForgeVersions

* Allow to get template from outside the template root

* Builtin templates update

* Add templates repo as resource modules

Helps with template completion using velocity implicit hints

* Flatten a bit the builtin template update code

* Ktlint fixes

* Add licenses

* Revert unneeded change

* Make properties & files properly optional

Also log when a template cannot be loaded because of and unhandled version

* Restore required nonnull assert

* Run gradle wrapper task after project import

* Add .gitignore and git add generated files after gradle wrapper task

* Architectury template

* Add paper manifest warning

* Fix ktlint warnings

* Include templates repo as submodule

* Include templates in publish workflow

* Bump templates submodule

* Switch builtin url to org repo

* Fix directory name in builtin provider

* Explicitly import Gradle and Maven projects

* Remove unused imports

* Use org repo

* Promote new wizard

I'd like to keep the old one for some time until all the new templates are proven to be fully working

* Actually use the correct org name

I swear...

* Get rid of AbstractLongRunningAssetsStep usage

Also improve robustness of the creator

* Reformat and open main files

* Remove unused import

* Specify TemplateApi target and retention

* Improve loading UI/UX

* Localization support

* Display validation and unhandled errors to user

* Split templates into groups

* Bump templates

* Add user-configurable repositories instead of raw providers

* Add back builtin provider

* Remove recent templates related code

* Convert recursive virtualfile loop into visitor

* Make provider label a property and localize it

* Move repo table code outside of MinecraftConfigurable

* Fix differences in creator properties naming and ctor parameters order

* Some work towards more extensible derivations

* Add missing licenses

* Remove unused imports

* Get rid of builtin sponge specific derivation

* Rework how all derivations work, with validation now!

* Fix imports, again

* MavenArtifactVersion should load versions in setupProperty

* Invert hidden -> visible and added custom visibility conditions

* Add build coords default group and version

* Add rawVersionFilter to maven artifact version property

* Add versionFilter to maven artifact version property

* Fix dropdown default values and add validation to ensure selection is valid

* Add Bungeecord and Spigot Kotlin templates

Also fix IntegerCreatorProperty default value

* Fix Parchment property not matching first release of a major mc version

* NeoForge Kotlin templates

* Add $version placeholder to remote url

* Fixup github archive matcher

* Add Fabric split sources

* Use Neo's ModDev plugin in 1.21

* Improve template error reporting

* Fix Loom's default version selection

* No longer unzip remote templates

Instead read directly inside them, and allow to configure a different
  repo root in cases like GitHub's
  zips, that have a root directory
  named after the repo and branch name

* Cache downloaded versions

* Remove superfluous blank line

* Actually add the builtin repo by default

* Hide the repositories row if only one repo is configured

* Proper module generation for finalizers

* Update templates submodule

* Add customizable storage keys

* Rename FabricApi -> Fabric API and ArchitecturyApi -> Architectury API

* Remove dead code

* Add versions download indicator
  • Loading branch information
RedNesto authored Jul 12, 2024
1 parent 85e1bcd commit 85e493a
Show file tree
Hide file tree
Showing 81 changed files with 6,633 additions and 25 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: Fetch latest submodule updates
run: git submodule update --remote
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
Expand Down
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[submodule "templates"]
path = templates
branch = main
url = https://github.com/minecraft-dev/templates
27 changes: 25 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,26 @@ val gradleToolingExtensionJar = tasks.register<Jar>(gradleToolingExtensionSource
archiveClassifier.set("gradle-tooling-extension")
}

val templatesSourceSet: SourceSet = sourceSets.create("templates") {
resources {
srcDir("templates")
compileClasspath += sourceSets.main.get().output
}
}

val templateSourceSets: List<SourceSet> = (file("templates").listFiles() ?: emptyArray()).mapNotNull { file ->
if (file.isDirectory() && (file.listFiles() ?: emptyArray()).any { it.name.endsWith(".mcdev.template.json") }) {
sourceSets.create("templates-${file.name}") {
resources {
srcDir(file)
compileClasspath += sourceSets.main.get().output
}
}
} else {
null
}
}

val externalAnnotationsJar = tasks.register<Jar>("externalAnnotationsJar") {
from("externalAnnotations")
destinationDirectory.set(layout.buildDirectory.dir("externalAnnotations"))
Expand Down Expand Up @@ -381,6 +401,9 @@ tasks.withType<PrepareSandboxTask> {
from(externalAnnotationsJar) {
into("Minecraft Development/lib/resources")
}
from("templates") {
into("Minecraft Development/lib/resources/builtin-templates")
}
}

tasks.runIde {
Expand All @@ -391,8 +414,8 @@ tasks.runIde {
systemProperty("idea.debug.mode", "true")
}
// Set these properties to test different languages
// systemProperty("user.language", "en")
// systemProperty("user.country", "US")
systemProperty("user.language", "fr")
systemProperty("user.country", "FR")
}

tasks.buildSearchableOptions {
Expand Down
15 changes: 15 additions & 0 deletions src/main/kotlin/MinecraftConfigurable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package com.demonwav.mcdev

import com.demonwav.mcdev.asset.MCDevBundle
import com.demonwav.mcdev.asset.PlatformAssets
import com.demonwav.mcdev.creator.custom.templateRepoTable
import com.demonwav.mcdev.update.ConfigurePluginUpdatesDialog
import com.intellij.ide.projectView.ProjectView
import com.intellij.openapi.options.Configurable
Expand All @@ -31,6 +32,7 @@ import com.intellij.ui.EnumComboBoxModel
import com.intellij.ui.components.Label
import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.BottomGap
import com.intellij.ui.dsl.builder.MutableProperty
import com.intellij.ui.dsl.builder.bindItem
import com.intellij.ui.dsl.builder.bindSelected
import com.intellij.ui.dsl.builder.panel
Expand Down Expand Up @@ -91,6 +93,19 @@ class MinecraftConfigurable : Configurable {
}
}

group(MCDevBundle("minecraft.settings.creator")) {
row(MCDevBundle("minecraft.settings.creator.repos")) {}

row {
templateRepoTable(
MutableProperty(
{ settings.creatorTemplateRepos.toMutableList() },
{ settings.creatorTemplateRepos = it }
)
)
}.resizableRow()
}

onApply {
for (project in ProjectManager.getInstance().openProjects) {
ProjectView.getInstance(project).refresh()
Expand Down
34 changes: 34 additions & 0 deletions src/main/kotlin/MinecraftSettings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@

package com.demonwav.mcdev

import com.demonwav.mcdev.asset.MCDevBundle
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.openapi.editor.markup.EffectType
import com.intellij.util.xmlb.annotations.Attribute
import com.intellij.util.xmlb.annotations.Tag
import com.intellij.util.xmlb.annotations.Text

@State(name = "MinecraftSettings", storages = [Storage("minecraft_dev.xml")])
class MinecraftSettings : PersistentStateComponent<MinecraftSettings.State> {
Expand All @@ -37,8 +41,29 @@ class MinecraftSettings : PersistentStateComponent<MinecraftSettings.State> {
var underlineType: UnderlineType = UnderlineType.DOTTED,

var isShadowAnnotationsSameLine: Boolean = true,

var creatorTemplateRepos: List<TemplateRepo> = listOf(TemplateRepo.makeBuiltinRepo()),
)

@Tag("repo")
data class TemplateRepo(
@get:Attribute("name")
var name: String,
@get:Attribute("provider")
var provider: String,
@get:Text
var data: String
) {
constructor() : this("", "", "")

companion object {

fun makeBuiltinRepo(): TemplateRepo {
return TemplateRepo(MCDevBundle("minecraft.settings.creator.repo.builtin_name"), "builtin", "true")
}
}
}

private var state = State()

override fun getState(): State {
Expand All @@ -47,6 +72,9 @@ class MinecraftSettings : PersistentStateComponent<MinecraftSettings.State> {

override fun loadState(state: State) {
this.state = state
if (state.creatorTemplateRepos.isEmpty()) {
state.creatorTemplateRepos = listOf()
}
}

// State mappings
Expand Down Expand Up @@ -86,6 +114,12 @@ class MinecraftSettings : PersistentStateComponent<MinecraftSettings.State> {
state.isShadowAnnotationsSameLine = shadowAnnotationsSameLine
}

var creatorTemplateRepos: List<TemplateRepo>
get() = state.creatorTemplateRepos.map { it.copy() }
set(creatorTemplateRepos) {
state.creatorTemplateRepos = creatorTemplateRepos.map { it.copy() }
}

enum class UnderlineType(private val regular: String, val effectType: EffectType) {

NORMAL("Normal", EffectType.LINE_UNDERSCORE),
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/creator/MinecraftModuleBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import com.intellij.openapi.roots.ModifiableRootModel

class MinecraftModuleBuilder : AbstractNewProjectWizardBuilder() {

override fun getPresentableName() = "Minecraft"
override fun getPresentableName() = "Minecraft (Old Wizard)"
override fun getNodeIcon() = PlatformAssets.MINECRAFT_ICON
override fun getGroupName() = "Minecraft"
override fun getBuilderId() = "MINECRAFT_MODULE"
Expand Down
4 changes: 3 additions & 1 deletion src/main/kotlin/creator/ProjectSetupFinalizerWizardStep.kt
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ class JdkProjectSetupFinalizer(
private var preferredJdkLabel: Placeholder? = null
private var preferredJdkReason = MCDevBundle("creator.validation.jdk_preferred_default_reason")

var preferredJdk: JavaSdkVersion = JavaSdkVersion.JDK_17
val preferredJdkProperty = propertyGraph.property(JavaSdkVersion.JDK_17)

var preferredJdk: JavaSdkVersion by preferredJdkProperty
private set

fun setPreferredJdk(value: JavaSdkVersion, reason: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ abstract class AbstractBuildSystemStep(

override val self get() = this
override val label
get() = MCDevBundle("creator.ui.build_system.label.generic")
get() = MCDevBundle("creator.ui.build_system.label")

override fun initSteps(): LinkedHashMap<String, NewProjectWizardStep> {
context.putUserData(PLATFORM_NAME_KEY, platformName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class BuildSystemPropertiesStep<ParentStep>(private val parent: ParentStep) : Ab
val groupIdProperty = propertyGraph.property("org.example")
.bindStorage("${javaClass.name}.groupId")
val artifactIdProperty = propertyGraph.lazyProperty(::suggestArtifactId)
private val versionProperty = propertyGraph.property("1.0-SNAPSHOT")
val versionProperty = propertyGraph.property("1.0-SNAPSHOT")
.bindStorage("${javaClass.name}.version")

var groupId by groupIdProperty
Expand Down
16 changes: 16 additions & 0 deletions src/main/kotlin/creator/creator-utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,15 @@ import com.demonwav.mcdev.creator.step.LicenseStep
import com.demonwav.mcdev.util.MinecraftTemplates
import com.intellij.ide.fileTemplates.FileTemplateManager
import com.intellij.ide.starters.local.GeneratorTemplateFile
import com.intellij.ide.util.projectWizard.WizardContext
import com.intellij.ide.wizard.AbstractNewProjectWizardStep
import com.intellij.ide.wizard.AbstractWizard
import com.intellij.ide.wizard.GitNewProjectWizardData
import com.intellij.ide.wizard.NewProjectWizardStep
import com.intellij.notification.Notification
import com.intellij.notification.NotificationType
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.observable.properties.ObservableMutableProperty
import com.intellij.openapi.observable.properties.ObservableProperty
import com.intellij.openapi.project.Project
Expand Down Expand Up @@ -160,3 +164,15 @@ fun notifyCreatedProjectNotOpened() {
NotificationType.ERROR,
).notify(null)
}

val WizardContext.modalityState: ModalityState
get() {
val contentPanel = this.getUserData(AbstractWizard.KEY)?.contentPanel

if (contentPanel == null) {
thisLogger().error("Wizard content panel is null, using default modality state")
return ModalityState.defaultModalityState()
}

return ModalityState.stateForComponent(contentPanel)
}
78 changes: 78 additions & 0 deletions src/main/kotlin/creator/custom/BuiltinValidations.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Minecraft Development for IntelliJ
*
* https://mcdev.io/
*
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
*/

package com.demonwav.mcdev.creator.custom

import com.demonwav.mcdev.asset.MCDevBundle
import com.demonwav.mcdev.platform.fabric.util.FabricVersions
import com.demonwav.mcdev.util.SemanticVersion
import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.ui.validation.DialogValidation
import com.intellij.openapi.ui.validation.validationErrorIf
import com.intellij.openapi.util.text.StringUtil
import javax.swing.JComponent

object BuiltinValidations {
val nonBlank = validationErrorIf<String>(MCDevBundle("creator.validation.blank")) { it.isBlank() }

val validVersion = validationErrorIf<String>(MCDevBundle("creator.validation.semantic_version")) {
SemanticVersion.tryParse(it) == null
}

val nonEmptyVersion = DialogValidation.WithParameter<ComboBox<SemanticVersion>> { combobox ->
DialogValidation {
if (combobox.item?.parts.isNullOrEmpty()) {
ValidationInfo(MCDevBundle("creator.validation.semantic_version"))
} else {
null
}
}
}

val nonEmptyYarnVersion = DialogValidation.WithParameter<ComboBox<FabricVersions.YarnVersion>> { combobox ->
DialogValidation {
if (combobox.item == null) {
ValidationInfo(MCDevBundle("creator.validation.semantic_version"))
} else {
null
}
}
}

val validClassFqn = validationErrorIf<String>(MCDevBundle("creator.validation.class_fqn")) {
it.isBlank() || it.split('.').any { part -> !StringUtil.isJavaIdentifier(part) }
}

fun byRegex(regex: Regex): DialogValidation.WithParameter<() -> String> =
validationErrorIf<String>(MCDevBundle("creator.validation.regex", regex)) { !it.matches(regex) }

fun <T> isAnyOf(
selectionGetter: () -> T,
options: Collection<T>,
component: JComponent? = null
): DialogValidation = DialogValidation {
if (selectionGetter() !in options) {
return@DialogValidation ValidationInfo(MCDevBundle("creator.validation.invalid_option"), component)
}

return@DialogValidation null
}
}
58 changes: 58 additions & 0 deletions src/main/kotlin/creator/custom/CreatorProgressIndicator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Minecraft Development for IntelliJ
*
* https://mcdev.io/
*
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
*/

package com.demonwav.mcdev.creator.custom

import com.intellij.openapi.observable.properties.GraphProperty
import com.intellij.openapi.progress.TaskInfo
import com.intellij.openapi.progress.util.ProgressIndicatorBase

class CreatorProgressIndicator(
val loadingProperty: GraphProperty<Boolean>? = null,
val textProperty: GraphProperty<String>? = null,
val text2Property: GraphProperty<String>? = null,
) : ProgressIndicatorBase(false, false) {

init {
loadingProperty?.set(false)
textProperty?.set("")
text2Property?.set("")
}

override fun start() {
super.start()
loadingProperty?.set(true)
}

override fun finish(task: TaskInfo) {
super.finish(task)
loadingProperty?.set(false)
}

override fun setText(text: String?) {
super.setText(text)
textProperty?.set(text ?: "")
}

override fun setText2(text: String?) {
super.setText2(text)
text2Property?.set(text ?: "")
}
}
Loading

0 comments on commit 85e493a

Please sign in to comment.