From d20f5e7f61d2f846778c86634cecdb282c8a8b9a Mon Sep 17 00:00:00 2001 From: RedNesto Date: Wed, 3 Jul 2024 07:15:16 +0200 Subject: [PATCH 01/12] Extract lexer and parser tasks into their own classes This makes those tasks compatible with build and configuration cache Unfortunately two other issues prevent us from using the configuration cache, one is the licenser plugin, the other is the access of testLibs in the test task configuration --- build.gradle.kts | 5 +- buildSrc/src/main/kotlin/JFlexExec.kt | 84 +++++++++++++++++ buildSrc/src/main/kotlin/ParserExec.kt | 88 ++++++++++++++++++ buildSrc/src/main/kotlin/util.kt | 92 ++++--------------- .../grammars/TranslationTemplateLexer.flex | 2 +- .../TranslationTemplateLexerAdapter.kt | 2 +- 6 files changed, 198 insertions(+), 75 deletions(-) create mode 100644 buildSrc/src/main/kotlin/JFlexExec.kt create mode 100644 buildSrc/src/main/kotlin/ParserExec.kt diff --git a/build.gradle.kts b/build.gradle.kts index fd9b13534..3b2663f07 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -343,7 +343,10 @@ val generateNbttParser by parser("NbttParser", "com/demonwav/mcdev/nbt/lang/gen" val generateLangLexer by lexer("LangLexer", "com/demonwav/mcdev/translations/lang/gen") val generateLangParser by parser("LangParser", "com/demonwav/mcdev/translations/lang/gen") -val generateTranslationTemplateLexer by lexer("TranslationTemplateLexer", "com/demonwav/mcdev/translations/lang/gen") +val generateTranslationTemplateLexer by lexer( + "TranslationTemplateLexer", + "com/demonwav/mcdev/translations/template/gen" +) val generate by tasks.registering { group = "minecraft" diff --git a/buildSrc/src/main/kotlin/JFlexExec.kt b/buildSrc/src/main/kotlin/JFlexExec.kt new file mode 100644 index 000000000..dca469daa --- /dev/null +++ b/buildSrc/src/main/kotlin/JFlexExec.kt @@ -0,0 +1,84 @@ +/* + * 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 . + */ + +import java.io.ByteArrayOutputStream +import javax.inject.Inject +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.ConfigurableFileTree +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileCollection +import org.gradle.api.file.FileSystemOperations +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.JavaExec +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.OutputFile + +abstract class JFlexExec : JavaExec() { + + @get:InputFile + abstract val sourceFile: RegularFileProperty + + @get:InputFiles + abstract val jflex: ConfigurableFileCollection + + @get:InputFile + abstract val skeletonFile: RegularFileProperty + + @get:OutputDirectory + abstract val destinationDirectory: DirectoryProperty + + @get:OutputFile + abstract val destinationFile: RegularFileProperty + + @get:Internal + abstract val logFile: RegularFileProperty + + @get:Inject + abstract val fs: FileSystemOperations + + init { + mainClass.set("jflex.Main") + } + + override fun exec() { + classpath = jflex + + args( + "--skel", skeletonFile.get().asFile.absolutePath, + "-d", destinationDirectory.get().asFile.absolutePath, + sourceFile.get().asFile.absolutePath + ) + + fs.delete { delete(destinationDirectory) } + + val taskOutput = ByteArrayOutputStream() + standardOutput = taskOutput + errorOutput = taskOutput + + super.exec() + + val log = logFile.get().asFile + log.parentFile.mkdirs() + log.writeBytes(taskOutput.toByteArray()) + } +} diff --git a/buildSrc/src/main/kotlin/ParserExec.kt b/buildSrc/src/main/kotlin/ParserExec.kt new file mode 100644 index 000000000..adb38256d --- /dev/null +++ b/buildSrc/src/main/kotlin/ParserExec.kt @@ -0,0 +1,88 @@ +/* + * 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 . + */ + +import java.io.ByteArrayOutputStream +import javax.inject.Inject +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileSystemOperations +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.JavaExec +import org.gradle.api.tasks.OutputDirectory + +abstract class ParserExec : JavaExec() { + + @get:InputFile + abstract val sourceFile: RegularFileProperty + + @get:InputFiles + abstract val grammarKit: ConfigurableFileCollection + + @get:OutputDirectory + abstract val destinationRootDirectory: DirectoryProperty + + @get:OutputDirectory + abstract val destinationDirectory: DirectoryProperty + + @get:OutputDirectory + abstract val psiDirectory: DirectoryProperty + + @get:OutputDirectory + abstract val parserDirectory: DirectoryProperty + + @get:Internal + abstract val logFile: RegularFileProperty + + @get:Inject + abstract val fs: FileSystemOperations + + init { + mainClass.set("org.intellij.grammar.Main") + + jvmArgs( + "--add-opens", "java.base/java.lang=ALL-UNNAMED", + "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", + "--add-opens", "java.base/java.util=ALL-UNNAMED" + ) + } + + override fun exec() { + classpath = grammarKit + args( + destinationRootDirectory.get().asFile, + sourceFile.get().asFile + ) + + fs.delete { delete(psiDirectory, parserDirectory) } + + val taskOutput = ByteArrayOutputStream() + standardOutput = taskOutput + errorOutput = taskOutput + + super.exec() + + val log = logFile.get().asFile + log.parentFile.mkdirs() + log.writeBytes(taskOutput.toByteArray()) + } +} diff --git a/buildSrc/src/main/kotlin/util.kt b/buildSrc/src/main/kotlin/util.kt index 054a24cf7..7a6623b23 100644 --- a/buildSrc/src/main/kotlin/util.kt +++ b/buildSrc/src/main/kotlin/util.kt @@ -32,94 +32,42 @@ import org.gradle.kotlin.dsl.configure typealias TaskDelegate = RegisteringDomainObjectDelegateProviderWithTypeAndAction -fun Project.lexer(flex: String, pack: String): TaskDelegate { +fun Project.lexer(flex: String, pack: String): TaskDelegate { configure { exclude(pack.removeSuffix("/") + "/**") } - return tasks.registering(JavaExec::class) { - val src = layout.projectDirectory.file("src/main/grammars/$flex.flex") - val dst = layout.buildDirectory.dir("gen/$pack") - val output = layout.buildDirectory.file("gen/$pack/$flex.java") - val logOutout = layout.buildDirectory.file("logs/generate$flex.log") + return tasks.registering(JFlexExec::class) { + sourceFile.set(layout.projectDirectory.file("src/main/grammars/$flex.flex")) + destinationDirectory.set(layout.buildDirectory.dir("gen/$pack")) + destinationFile.set(layout.buildDirectory.file("gen/$pack/$flex.java")) + logFile.set(layout.buildDirectory.file("logs/generate$flex.log")) val jflex by project.configurations - val jflexSkeleton by project.configurations - - classpath = jflex - mainClass.set("jflex.Main") - - val taskOutput = ByteArrayOutputStream() - standardOutput = taskOutput - errorOutput = taskOutput - - doFirst { - args( - "--skel", jflexSkeleton.singleFile.absolutePath, - "-d", dst.get().asFile.absolutePath, - src.asFile.absolutePath - ) - - // Delete current lexer - project.delete(output) - logOutout.get().asFile.parentFile.mkdirs() - } - - doLast { - logOutout.get().asFile.writeBytes(taskOutput.toByteArray()) - } + this.jflex.setFrom(jflex) - inputs.files(src, jflexSkeleton) - outputs.file(output) + val jflexSkeleton by project.configurations + skeletonFile.set(jflexSkeleton.singleFile) } } -fun Project.parser(bnf: String, pack: String): TaskDelegate { +fun Project.parser(bnf: String, pack: String): TaskDelegate { configure { exclude(pack.removeSuffix("/") + "/**") } - return tasks.registering(JavaExec::class) { - val src = project.layout.projectDirectory.file("src/main/grammars/$bnf.bnf") - val dstRoot = project.layout.buildDirectory.dir("gen") - val dst = dstRoot.map { it.dir(pack) } - val psiDir = dst.map { it.dir("psi") } - val parserDir = dst.map { it.dir("parser") } - val logOutout = layout.buildDirectory.file("logs/generate$bnf.log") + return tasks.registering(ParserExec::class) { + val destRoot = project.layout.buildDirectory.dir("gen") + val dest = destRoot.map { it.dir(pack) } + sourceFile.set(project.layout.projectDirectory.file("src/main/grammars/$bnf.bnf")) + destinationRootDirectory.set(destRoot) + destinationDirectory.set(dest) + psiDirectory.set(dest.map { it.dir("psi") }) + parserDirectory.set(dest.map { it.dir("parser") }) + logFile.set(layout.buildDirectory.file("logs/generate$bnf.log")) val grammarKit by project.configurations - - val taskOutput = ByteArrayOutputStream() - standardOutput = taskOutput - errorOutput = taskOutput - - classpath = grammarKit - mainClass.set("org.intellij.grammar.Main") - - if (JavaVersion.current().isJava9Compatible) { - jvmArgs( - "--add-opens", "java.base/java.lang=ALL-UNNAMED", - "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", - "--add-opens", "java.base/java.util=ALL-UNNAMED" - ) - } - - doFirst { - project.delete(psiDir, parserDir) - args(dstRoot.get().asFile, src.asFile) - logOutout.get().asFile.parentFile.mkdirs() - } - doLast { - logOutout.get().asFile.writeBytes(taskOutput.toByteArray()) - } - - inputs.file(src) - outputs.dirs( - mapOf( - "psi" to psiDir, - "parser" to parserDir - ) - ) + this.grammarKit.setFrom(grammarKit) } } diff --git a/src/main/grammars/TranslationTemplateLexer.flex b/src/main/grammars/TranslationTemplateLexer.flex index b71ef2bc8..ba1831dcb 100644 --- a/src/main/grammars/TranslationTemplateLexer.flex +++ b/src/main/grammars/TranslationTemplateLexer.flex @@ -18,7 +18,7 @@ * along with this program. If not, see . */ -package com.demonwav.mcdev.translations.lang.gen; +package com.demonwav.mcdev.translations.template.gen; import com.intellij.lexer.*; import com.intellij.psi.tree.IElementType; diff --git a/src/main/kotlin/translations/sorting/TranslationTemplateLexerAdapter.kt b/src/main/kotlin/translations/sorting/TranslationTemplateLexerAdapter.kt index ad69540c4..b7413c898 100644 --- a/src/main/kotlin/translations/sorting/TranslationTemplateLexerAdapter.kt +++ b/src/main/kotlin/translations/sorting/TranslationTemplateLexerAdapter.kt @@ -20,7 +20,7 @@ package com.demonwav.mcdev.translations.sorting -import com.demonwav.mcdev.translations.lang.gen.TranslationTemplateLexer +import com.demonwav.mcdev.translations.template.gen.TranslationTemplateLexer import com.intellij.lexer.FlexAdapter class TranslationTemplateLexerAdapter : FlexAdapter(TranslationTemplateLexer()) From a3c6f09053af93bf3149fbdda7d91cb2df14c814 Mon Sep 17 00:00:00 2001 From: RedNesto Date: Thu, 4 Jul 2024 16:08:58 +0200 Subject: [PATCH 02/12] Convert event generation UI to Kotlin DSL --- .../generation/ui/EventListenerWizard.form | 64 -------------- .../generation/ui/EventListenerWizard.kt | 88 ++++++++++--------- .../BukkitEventGenerationPanel.form | 48 ---------- .../generation/BukkitEventGenerationPanel.kt | 41 ++++----- .../BungeeCordEventGenerationPanel.form | 40 --------- .../BungeeCordEventGenerationPanel.kt | 29 +++--- .../SpongeEventGenerationPanel.form | 48 ---------- .../generation/SpongeEventGenerationPanel.kt | 42 +++++---- .../VelocityEventGenerationPanel.form | 40 --------- .../VelocityEventGenerationPanel.kt | 29 +++--- .../messages/MinecraftDevelopment.properties | 3 + 11 files changed, 117 insertions(+), 355 deletions(-) delete mode 100644 src/main/kotlin/insight/generation/ui/EventListenerWizard.form delete mode 100644 src/main/kotlin/platform/bukkit/generation/BukkitEventGenerationPanel.form delete mode 100644 src/main/kotlin/platform/bungeecord/generation/BungeeCordEventGenerationPanel.form delete mode 100644 src/main/kotlin/platform/sponge/generation/SpongeEventGenerationPanel.form delete mode 100644 src/main/kotlin/platform/velocity/generation/VelocityEventGenerationPanel.form diff --git a/src/main/kotlin/insight/generation/ui/EventListenerWizard.form b/src/main/kotlin/insight/generation/ui/EventListenerWizard.form deleted file mode 100644 index c30595322..000000000 --- a/src/main/kotlin/insight/generation/ui/EventListenerWizard.form +++ /dev/null @@ -1,64 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/kotlin/insight/generation/ui/EventListenerWizard.kt b/src/main/kotlin/insight/generation/ui/EventListenerWizard.kt index 90daa78f1..1f8fabf67 100644 --- a/src/main/kotlin/insight/generation/ui/EventListenerWizard.kt +++ b/src/main/kotlin/insight/generation/ui/EventListenerWizard.kt @@ -22,60 +22,62 @@ package com.demonwav.mcdev.insight.generation.ui import com.intellij.ide.highlighter.JavaHighlightingColors import com.intellij.openapi.editor.ex.util.EditorUtil -import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy -import com.intellij.uiDesigner.core.GridConstraints +import com.intellij.openapi.observable.properties.PropertyGraph +import com.intellij.ui.dsl.builder.AlignX +import com.intellij.ui.dsl.builder.COLUMNS_LARGE +import com.intellij.ui.dsl.builder.bindText +import com.intellij.ui.dsl.builder.columns +import com.intellij.ui.dsl.builder.panel +import com.intellij.ui.dsl.builder.text import com.intellij.util.ui.UIUtil -import javax.swing.JLabel import javax.swing.JPanel -import javax.swing.JSeparator -import javax.swing.JTextField class EventListenerWizard(panel: JPanel?, className: String, defaultListenerName: String) { - lateinit var panel: JPanel - private lateinit var classNameTextField: JTextField - private lateinit var listenerNameTextField: JTextField - private lateinit var publicVoidLabel: JLabel - private lateinit var contentPanel: JPanel - private lateinit var separator: JSeparator - init { - classNameTextField.font = EditorUtil.getEditorFont() - listenerNameTextField.font = EditorUtil.getEditorFont() - publicVoidLabel.font = EditorUtil.getEditorFont() - if (UIUtil.isUnderDarcula()) { - publicVoidLabel.foreground = JavaHighlightingColors.KEYWORD.defaultAttributes.foregroundColor - } else { - publicVoidLabel.foreground = - JavaHighlightingColors.KEYWORD.fallbackAttributeKey!!.defaultAttributes.foregroundColor - } + private val graph = PropertyGraph("EventListenerWizard graph") - if (panel != null) { - separator.isVisible = true - contentPanel.add(panel, innerContentPanelConstraints) - } + private val listenerNameProperty = graph.property(defaultListenerName) + val chosenClassName: String by listenerNameProperty - classNameTextField.text = className - listenerNameTextField.text = defaultListenerName + val panel: JPanel by lazy { + panel { + row { + textField() + .text(className) + .align(AlignX.FILL) + .apply { + component.font = EditorUtil.getEditorFont() + component.isEditable = false + } + } - IdeFocusTraversalPolicy.getPreferredFocusedComponent(listenerNameTextField).requestFocus() - listenerNameTextField.requestFocus() - } + row { + label("public void").apply { + component.font = EditorUtil.getEditorFont() + if (UIUtil.isUnderDarcula()) { + component.foreground = JavaHighlightingColors.KEYWORD.defaultAttributes.foregroundColor + } else { + component.foreground = + JavaHighlightingColors.KEYWORD.fallbackAttributeKey!!.defaultAttributes.foregroundColor + } + } - val chosenClassName: String - get() = listenerNameTextField.text + textField() + .bindText(listenerNameProperty) + .columns(COLUMNS_LARGE) + .focused() + .apply { + component.font = EditorUtil.getEditorFont() + } + } - companion object { - private val innerContentPanelConstraints = GridConstraints() + if (panel != null) { + separator() - init { - innerContentPanelConstraints.row = 0 - innerContentPanelConstraints.column = 0 - innerContentPanelConstraints.rowSpan = 1 - innerContentPanelConstraints.colSpan = 1 - innerContentPanelConstraints.anchor = GridConstraints.ANCHOR_CENTER - innerContentPanelConstraints.fill = GridConstraints.FILL_BOTH - innerContentPanelConstraints.hSizePolicy = GridConstraints.SIZEPOLICY_FIXED - innerContentPanelConstraints.vSizePolicy = GridConstraints.SIZEPOLICY_FIXED + row { + cell(panel) + } + } } } } diff --git a/src/main/kotlin/platform/bukkit/generation/BukkitEventGenerationPanel.form b/src/main/kotlin/platform/bukkit/generation/BukkitEventGenerationPanel.form deleted file mode 100644 index 9ae1aed1f..000000000 --- a/src/main/kotlin/platform/bukkit/generation/BukkitEventGenerationPanel.form +++ /dev/null @@ -1,48 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/src/main/kotlin/platform/bukkit/generation/BukkitEventGenerationPanel.kt b/src/main/kotlin/platform/bukkit/generation/BukkitEventGenerationPanel.kt index 771e6750f..8a2230377 100644 --- a/src/main/kotlin/platform/bukkit/generation/BukkitEventGenerationPanel.kt +++ b/src/main/kotlin/platform/bukkit/generation/BukkitEventGenerationPanel.kt @@ -20,40 +20,41 @@ package com.demonwav.mcdev.platform.bukkit.generation +import com.demonwav.mcdev.asset.MCDevBundle import com.demonwav.mcdev.insight.generation.GenerationData import com.demonwav.mcdev.insight.generation.ui.EventGenerationPanel +import com.intellij.openapi.observable.properties.PropertyGraph import com.intellij.psi.PsiClass -import javax.swing.JCheckBox -import javax.swing.JComboBox +import com.intellij.ui.dsl.builder.bindItem +import com.intellij.ui.dsl.builder.bindSelected +import com.intellij.ui.dsl.builder.panel import javax.swing.JPanel class BukkitEventGenerationPanel(chosenClass: PsiClass) : EventGenerationPanel(chosenClass) { - private lateinit var ignoreCanceledCheckBox: JCheckBox - private lateinit var parentPanel: JPanel - private lateinit var eventPriorityComboBox: JComboBox + private val graph = PropertyGraph("BukkitEventGenerationPanel graph") - override val panel: JPanel - get() { - ignoreCanceledCheckBox.isSelected = true + private val ignoreCanceledProperty = graph.property(true) + private val eventPriorityProperty = graph.property("NORMAL") - // Not static because the form builder is not reliable - eventPriorityComboBox.addItem("MONITOR") - eventPriorityComboBox.addItem("HIGHEST") - eventPriorityComboBox.addItem("HIGH") - eventPriorityComboBox.addItem("NORMAL") - eventPriorityComboBox.addItem("LOW") - eventPriorityComboBox.addItem("LOWEST") + override val panel: JPanel by lazy { + panel { + row { + checkBox(MCDevBundle("generate.event_listener.ignore_if_canceled")) + .bindSelected(ignoreCanceledProperty) + } - eventPriorityComboBox.selectedIndex = 3 - - return parentPanel + row(MCDevBundle("generate.event_listener.event_priority")) { + comboBox(listOf("MONITOR", "HIGHEST", "HIGH", "NORMAL", "LOW", "LOWEST")) + .bindItem(eventPriorityProperty) + } } + } override fun gatherData(): GenerationData { return BukkitGenerationData( - ignoreCanceledCheckBox.isSelected, - eventPriorityComboBox.selectedItem?.toString() ?: error("No selected item") + ignoreCanceledProperty.get(), + eventPriorityProperty.get() ) } } diff --git a/src/main/kotlin/platform/bungeecord/generation/BungeeCordEventGenerationPanel.form b/src/main/kotlin/platform/bungeecord/generation/BungeeCordEventGenerationPanel.form deleted file mode 100644 index 45a1c7c42..000000000 --- a/src/main/kotlin/platform/bungeecord/generation/BungeeCordEventGenerationPanel.form +++ /dev/null @@ -1,40 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/src/main/kotlin/platform/bungeecord/generation/BungeeCordEventGenerationPanel.kt b/src/main/kotlin/platform/bungeecord/generation/BungeeCordEventGenerationPanel.kt index d950005e7..b04f92156 100644 --- a/src/main/kotlin/platform/bungeecord/generation/BungeeCordEventGenerationPanel.kt +++ b/src/main/kotlin/platform/bungeecord/generation/BungeeCordEventGenerationPanel.kt @@ -20,29 +20,28 @@ package com.demonwav.mcdev.platform.bungeecord.generation +import com.demonwav.mcdev.asset.MCDevBundle import com.demonwav.mcdev.insight.generation.ui.EventGenerationPanel +import com.intellij.openapi.observable.properties.PropertyGraph import com.intellij.psi.PsiClass -import javax.swing.JComboBox +import com.intellij.ui.dsl.builder.bindItem +import com.intellij.ui.dsl.builder.panel import javax.swing.JPanel class BungeeCordEventGenerationPanel(chosenClass: PsiClass) : EventGenerationPanel(chosenClass) { - private lateinit var eventPriorityComboBox: JComboBox - private lateinit var parentPanel: JPanel + private val graph = PropertyGraph("BungeeCordEventGenerationPanel graph") - override val panel: JPanel - get() { - // Not static because the form builder is not reliable - eventPriorityComboBox.addItem("HIGHEST") - eventPriorityComboBox.addItem("HIGH") - eventPriorityComboBox.addItem("NORMAL") - eventPriorityComboBox.addItem("LOW") - eventPriorityComboBox.addItem("LOWEST") + private val eventPriorityProperty = graph.property("NORMAL") - eventPriorityComboBox.selectedIndex = 2 - - return parentPanel + override val panel: JPanel by lazy { + panel { + row(MCDevBundle("generate.event_listener.event_priority")) { + comboBox(listOf("HIGHEST", "HIGH", "NORMAL", "LOW", "LOWEST")) + .bindItem(eventPriorityProperty) + } } + } - override fun gatherData() = BungeeCordGenerationData(eventPriorityComboBox.selectedItem.toString()) + override fun gatherData() = BungeeCordGenerationData(eventPriorityProperty.get()) } diff --git a/src/main/kotlin/platform/sponge/generation/SpongeEventGenerationPanel.form b/src/main/kotlin/platform/sponge/generation/SpongeEventGenerationPanel.form deleted file mode 100644 index b49fc8dbd..000000000 --- a/src/main/kotlin/platform/sponge/generation/SpongeEventGenerationPanel.form +++ /dev/null @@ -1,48 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/src/main/kotlin/platform/sponge/generation/SpongeEventGenerationPanel.kt b/src/main/kotlin/platform/sponge/generation/SpongeEventGenerationPanel.kt index d02630f9a..b3796684c 100644 --- a/src/main/kotlin/platform/sponge/generation/SpongeEventGenerationPanel.kt +++ b/src/main/kotlin/platform/sponge/generation/SpongeEventGenerationPanel.kt @@ -20,40 +20,38 @@ package com.demonwav.mcdev.platform.sponge.generation +import com.demonwav.mcdev.asset.MCDevBundle import com.demonwav.mcdev.insight.generation.GenerationData import com.demonwav.mcdev.insight.generation.ui.EventGenerationPanel +import com.intellij.openapi.observable.properties.PropertyGraph import com.intellij.psi.PsiClass -import javax.swing.JCheckBox -import javax.swing.JComboBox +import com.intellij.ui.dsl.builder.bindItem +import com.intellij.ui.dsl.builder.bindSelected +import com.intellij.ui.dsl.builder.panel import javax.swing.JPanel class SpongeEventGenerationPanel(chosenClass: PsiClass) : EventGenerationPanel(chosenClass) { - private lateinit var parentPanel: JPanel - private lateinit var eventOrderComboBox: JComboBox - private lateinit var ignoreCanceledCheckBox: JCheckBox + private val graph = PropertyGraph("SpongeEventGenerationPanel graph") - override val panel: JPanel - get() { - ignoreCanceledCheckBox.isSelected = true + private val ignoreCanceledProperty = graph.property(true) + private val eventOrderProperty = graph.property("DEFAULT") - // Not static because the form builder is not reliable - eventOrderComboBox.addItem("PRE") - eventOrderComboBox.addItem("AFTER_PRE") - eventOrderComboBox.addItem("FIRST") - eventOrderComboBox.addItem("EARLY") - eventOrderComboBox.addItem("DEFAULT") - eventOrderComboBox.addItem("LATE") - eventOrderComboBox.addItem("LAST") - eventOrderComboBox.addItem("BEFORE_POST") - eventOrderComboBox.addItem("POST") + override val panel: JPanel by lazy { + panel { + row { + checkBox(MCDevBundle("generate.event_listener.ignore_if_canceled")) + .bindSelected(ignoreCanceledProperty) + } - eventOrderComboBox.selectedIndex = 4 - - return parentPanel + row(MCDevBundle("generate.event_listener.event_order")) { + comboBox(listOf("PRE", "AFTER_PRE", "FIRST", "EARLY", "DEFAULT", "LATE", "LAST", "BEFORE_POST", "POST")) + .bindItem(eventOrderProperty) + } } + } override fun gatherData(): GenerationData { - return SpongeGenerationData(ignoreCanceledCheckBox.isSelected, eventOrderComboBox.selectedItem as String) + return SpongeGenerationData(ignoreCanceledProperty.get(), eventOrderProperty.get()) } } diff --git a/src/main/kotlin/platform/velocity/generation/VelocityEventGenerationPanel.form b/src/main/kotlin/platform/velocity/generation/VelocityEventGenerationPanel.form deleted file mode 100644 index d6653508a..000000000 --- a/src/main/kotlin/platform/velocity/generation/VelocityEventGenerationPanel.form +++ /dev/null @@ -1,40 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/src/main/kotlin/platform/velocity/generation/VelocityEventGenerationPanel.kt b/src/main/kotlin/platform/velocity/generation/VelocityEventGenerationPanel.kt index 503cd8ff5..f49225945 100644 --- a/src/main/kotlin/platform/velocity/generation/VelocityEventGenerationPanel.kt +++ b/src/main/kotlin/platform/velocity/generation/VelocityEventGenerationPanel.kt @@ -20,32 +20,31 @@ package com.demonwav.mcdev.platform.velocity.generation +import com.demonwav.mcdev.asset.MCDevBundle import com.demonwav.mcdev.insight.generation.GenerationData import com.demonwav.mcdev.insight.generation.ui.EventGenerationPanel +import com.intellij.openapi.observable.properties.PropertyGraph import com.intellij.psi.PsiClass -import javax.swing.JComboBox +import com.intellij.ui.dsl.builder.bindItem +import com.intellij.ui.dsl.builder.panel import javax.swing.JPanel class VelocityEventGenerationPanel(chosenClass: PsiClass) : EventGenerationPanel(chosenClass) { - private lateinit var parentPanel: JPanel - private lateinit var eventOrderComboBox: JComboBox + private val graph = PropertyGraph("VelocityEventGenerationPanel graph") - override val panel: JPanel - get() { - // Not static because the form builder is not reliable - eventOrderComboBox.addItem("FIRST") - eventOrderComboBox.addItem("EARLY") - eventOrderComboBox.addItem("NORMAL") - eventOrderComboBox.addItem("LATE") - eventOrderComboBox.addItem("LAST") + private val eventOrderProperty = graph.property("NORMAL") - eventOrderComboBox.selectedIndex = 2 - - return parentPanel + override val panel: JPanel by lazy { + panel { + row(MCDevBundle("generate.event_listener.event_order")) { + comboBox(listOf("FIRST", "EARLY", "NORMAL", "LATE", "LAST")) + .bindItem(eventOrderProperty) + } } + } override fun gatherData(): GenerationData { - return VelocityGenerationData(eventOrderComboBox.selectedItem as String) + return VelocityGenerationData(eventOrderProperty.get()) } } diff --git a/src/main/resources/messages/MinecraftDevelopment.properties b/src/main/resources/messages/MinecraftDevelopment.properties index a71c4539f..21e09cdfc 100644 --- a/src/main/resources/messages/MinecraftDevelopment.properties +++ b/src/main/resources/messages/MinecraftDevelopment.properties @@ -98,6 +98,9 @@ facet.reimport.failed.content.with_error=Failed to start project refresh, please generate.event_listener.title=Generate Event Listener generate.event_listener.settings=Event Listener Settings +generate.event_listener.event_priority=Event Priority +generate.event_listener.event_order=Event Order +generate.event_listener.ignore_if_canceled=Ignore if event is canceled generate.class.caption=Minecraft Class generate.class.description=Class generation for modders From 6aa9273da52cc76483f324f922eff23d2475d272 Mon Sep 17 00:00:00 2001 From: RedNesto Date: Thu, 4 Jul 2024 16:09:10 +0200 Subject: [PATCH 03/12] Only show "Generate Accessor/Invoke" in Mixin modules --- .../kotlin/platform/mixin/action/GenerateAccessorAction.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/platform/mixin/action/GenerateAccessorAction.kt b/src/main/kotlin/platform/mixin/action/GenerateAccessorAction.kt index 865eb6296..921b260ce 100644 --- a/src/main/kotlin/platform/mixin/action/GenerateAccessorAction.kt +++ b/src/main/kotlin/platform/mixin/action/GenerateAccessorAction.kt @@ -20,6 +20,7 @@ package com.demonwav.mcdev.platform.mixin.action +import com.demonwav.mcdev.platform.mixin.MixinModuleType import com.intellij.codeInsight.FileModificationService import com.intellij.codeInsight.generation.actions.BaseGenerateAction import com.intellij.openapi.application.ApplicationManager @@ -75,7 +76,7 @@ class GenerateAccessorAction : BaseGenerateAction(GenerateAccessorHandler()) { } override fun isValidForFile(project: Project, editor: Editor, file: PsiFile): Boolean { - if (file !is PsiJavaFile) { + if (file !is PsiJavaFile || !MixinModuleType.isInModule(file)) { return false } From ca003f90104183b54a82500a44edfe62bcc43082 Mon Sep 17 00:00:00 2001 From: RedNesto Date: Thu, 4 Jul 2024 18:49:09 +0200 Subject: [PATCH 04/12] Convert last forms to Kotlin UI DSL --- .../mixin/action/FindMixinsComponent.form | 26 ------ .../mixin/action/FindMixinsComponent.kt | 21 +++-- .../actions/TranslationSortOrderDialog.form | 89 ------------------- .../actions/TranslationSortOrderDialog.kt | 72 +++++++++------ .../TranslationTemplateConfigurable.form | 58 ------------ .../TranslationTemplateConfigurable.kt | 53 +++++++---- .../messages/MinecraftDevelopment.properties | 10 +++ 7 files changed, 99 insertions(+), 230 deletions(-) delete mode 100644 src/main/kotlin/platform/mixin/action/FindMixinsComponent.form delete mode 100644 src/main/kotlin/translations/actions/TranslationSortOrderDialog.form delete mode 100644 src/main/kotlin/translations/sorting/TranslationTemplateConfigurable.form diff --git a/src/main/kotlin/platform/mixin/action/FindMixinsComponent.form b/src/main/kotlin/platform/mixin/action/FindMixinsComponent.form deleted file mode 100644 index 8b9f602e7..000000000 --- a/src/main/kotlin/platform/mixin/action/FindMixinsComponent.form +++ /dev/null @@ -1,26 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/src/main/kotlin/platform/mixin/action/FindMixinsComponent.kt b/src/main/kotlin/platform/mixin/action/FindMixinsComponent.kt index bf06d7f6e..b0504e6e8 100644 --- a/src/main/kotlin/platform/mixin/action/FindMixinsComponent.kt +++ b/src/main/kotlin/platform/mixin/action/FindMixinsComponent.kt @@ -20,27 +20,26 @@ package com.demonwav.mcdev.platform.mixin.action -import com.demonwav.mcdev.util.toArray import com.intellij.ide.util.PsiClassListCellRenderer import com.intellij.psi.PsiClass import com.intellij.ui.components.JBList +import com.intellij.ui.dsl.builder.Align +import com.intellij.ui.dsl.builder.panel import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import javax.swing.JPanel -import javax.swing.ListModel class FindMixinsComponent(classes: List) : MouseAdapter() { - private lateinit var classList: JBList - lateinit var panel: JPanel - private set - - init { - @Suppress("UNCHECKED_CAST") - classList.model = JBList.createDefaultListModel(*classes.toArray()) as ListModel - classList.cellRenderer = PsiClassListCellRenderer() + private val classList = JBList(classes).apply { + cellRenderer = PsiClassListCellRenderer() + addMouseListener(this@FindMixinsComponent) + } - classList.addMouseListener(this) + val panel: JPanel = panel { + row { + cell(classList).align(Align.FILL) + } } override fun mouseClicked(e: MouseEvent) { diff --git a/src/main/kotlin/translations/actions/TranslationSortOrderDialog.form b/src/main/kotlin/translations/actions/TranslationSortOrderDialog.form deleted file mode 100644 index 477020b84..000000000 --- a/src/main/kotlin/translations/actions/TranslationSortOrderDialog.form +++ /dev/null @@ -1,89 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/src/main/kotlin/translations/actions/TranslationSortOrderDialog.kt b/src/main/kotlin/translations/actions/TranslationSortOrderDialog.kt index 2c284a5f5..c72b6817f 100644 --- a/src/main/kotlin/translations/actions/TranslationSortOrderDialog.kt +++ b/src/main/kotlin/translations/actions/TranslationSortOrderDialog.kt @@ -20,44 +20,58 @@ package com.demonwav.mcdev.translations.actions +import com.demonwav.mcdev.asset.MCDevBundle import com.demonwav.mcdev.translations.sorting.Ordering +import com.intellij.CommonBundle +import com.intellij.openapi.observable.properties.PropertyGraph +import com.intellij.ui.dsl.builder.AlignX +import com.intellij.ui.dsl.builder.bindIntValue +import com.intellij.ui.dsl.builder.bindItem +import com.intellij.ui.dsl.builder.panel import java.awt.Component import java.awt.event.KeyEvent import java.awt.event.WindowAdapter import java.awt.event.WindowEvent -import javax.swing.DefaultComboBoxModel import javax.swing.DefaultListCellRenderer -import javax.swing.JButton -import javax.swing.JComboBox import javax.swing.JComponent import javax.swing.JDialog import javax.swing.JList -import javax.swing.JPanel -import javax.swing.JSpinner import javax.swing.KeyStroke -import javax.swing.SpinnerNumberModel import javax.swing.WindowConstants class TranslationSortOrderDialog(excludeDefaultOption: Boolean, defaultSelection: Ordering) : JDialog() { - private lateinit var contentPane: JPanel - private lateinit var buttonOK: JButton - private lateinit var buttonCancel: JButton - private lateinit var comboSelection: JComboBox - private lateinit var spinnerComments: JSpinner - init { - setContentPane(contentPane) - isModal = true - title = "Select Sort Order" - getRootPane().defaultButton = buttonOK + private val graph = PropertyGraph("TranslationSortOrderDialog graph") + + private val orderProperty = graph.property(defaultSelection) + private val keepCommentsProperty = graph.property(0) - buttonOK.addActionListener { onOK() } - buttonCancel.addActionListener { onCancel() } - spinnerComments.model = SpinnerNumberModel(0, 0, Int.MAX_VALUE, 1) + private var canceled = false + + init { val availableOrderings = if (excludeDefaultOption) NON_DEFAULT_ORDERINGS else ALL_ORDERINGS - comboSelection.model = DefaultComboBoxModel(availableOrderings) - comboSelection.renderer = CellRenderer - comboSelection.selectedItem = defaultSelection + val panel = panel { + row(MCDevBundle("translation_sort.order")) { + comboBox(availableOrderings, CellRenderer) + .bindItem(orderProperty) + } + + row(MCDevBundle("translation_sort.keep_comment")) { + spinner(0..Int.MAX_VALUE) + .bindIntValue(keepCommentsProperty::get, keepCommentsProperty::set) + } + + row { + button(CommonBundle.message("button.ok")) { onOK() }.align(AlignX.RIGHT).also { + getRootPane().defaultButton = it.component + } + button(CommonBundle.message("button.cancel")) { onCancel() }.align(AlignX.RIGHT) + } + } + contentPane = panel + + isModal = true + title = MCDevBundle("translation_sort.title") // call onCancel() when cross is clicked defaultCloseOperation = WindowConstants.DO_NOTHING_ON_CLOSE @@ -70,7 +84,7 @@ class TranslationSortOrderDialog(excludeDefaultOption: Boolean, defaultSelection ) // call onCancel() on ESCAPE - contentPane.registerKeyboardAction( + panel.registerKeyboardAction( { onCancel() }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, @@ -82,7 +96,7 @@ class TranslationSortOrderDialog(excludeDefaultOption: Boolean, defaultSelection } private fun onCancel() { - comboSelection.selectedIndex = -1 + canceled = true dispose() } @@ -100,17 +114,17 @@ class TranslationSortOrderDialog(excludeDefaultOption: Boolean, defaultSelection } companion object { - private val ALL_ORDERINGS = Ordering.values() - private val NON_DEFAULT_ORDERINGS = Ordering.values() - .filterNot { it == Ordering.LIKE_DEFAULT }.toTypedArray() + private val ALL_ORDERINGS = Ordering.entries + private val NON_DEFAULT_ORDERINGS = Ordering.entries + .filterNot { it == Ordering.LIKE_DEFAULT } fun show(excludeDefaultOption: Boolean, defaultSelection: Ordering): Pair { val dialog = TranslationSortOrderDialog(excludeDefaultOption, defaultSelection) dialog.pack() dialog.setLocationRelativeTo(dialog.owner) dialog.isVisible = true - val order = dialog.comboSelection.selectedItem as? Ordering - val comments = dialog.spinnerComments.value as Int + val order = if (dialog.canceled) null else dialog.orderProperty.get() + val comments = dialog.keepCommentsProperty.get() return (order to comments) } } diff --git a/src/main/kotlin/translations/sorting/TranslationTemplateConfigurable.form b/src/main/kotlin/translations/sorting/TranslationTemplateConfigurable.form deleted file mode 100644 index 267a814de..000000000 --- a/src/main/kotlin/translations/sorting/TranslationTemplateConfigurable.form +++ /dev/null @@ -1,58 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/src/main/kotlin/translations/sorting/TranslationTemplateConfigurable.kt b/src/main/kotlin/translations/sorting/TranslationTemplateConfigurable.kt index 87c9200c2..e4e148a47 100644 --- a/src/main/kotlin/translations/sorting/TranslationTemplateConfigurable.kt +++ b/src/main/kotlin/translations/sorting/TranslationTemplateConfigurable.kt @@ -20,6 +20,7 @@ package com.demonwav.mcdev.translations.sorting +import com.demonwav.mcdev.asset.MCDevBundle import com.demonwav.mcdev.translations.lang.colors.LangSyntaxHighlighter import com.intellij.codeInsight.template.impl.TemplateEditorUtil import com.intellij.ide.DataManager @@ -30,6 +31,8 @@ import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.editor.ex.util.LexerEditorHighlighter import com.intellij.openapi.options.Configurable import com.intellij.openapi.project.Project +import com.intellij.ui.dsl.builder.Align +import com.intellij.ui.dsl.builder.panel import com.intellij.util.ui.JBUI import java.awt.BorderLayout import javax.swing.DefaultComboBoxModel @@ -39,32 +42,47 @@ import javax.swing.JPanel import org.jetbrains.annotations.Nls class TranslationTemplateConfigurable(private val project: Project) : Configurable { - private lateinit var panel: JPanel private lateinit var cmbScheme: JComboBox - private lateinit var editorPanel: JPanel - private lateinit var templateEditor: Editor + private var templateEditor: Editor? = null + + private val editorPanel = JPanel(BorderLayout()).apply { + preferredSize = JBUI.size(250, 450) + minimumSize = preferredSize + } + + private val panel = panel { + row(MCDevBundle("minecraft.settings.lang_template.scheme")) { + cmbScheme = comboBox(emptyList()).component + } + + row { + label(MCDevBundle("minecraft.settings.lang_template.comment")) + } + + row { + cell(editorPanel).align(Align.FILL) + } + } @Nls - override fun getDisplayName() = "Localization Template" + override fun getDisplayName() = MCDevBundle("minecraft.settings.lang_template.display_name") override fun getHelpTopic(): String? = null - override fun createComponent(): JComponent { - return panel - } + override fun createComponent(): JComponent = panel private fun getActiveTemplateText() = when { cmbScheme.selectedIndex == 0 -> TemplateManager.getGlobalTemplateText() !project.isDefault -> TemplateManager.getProjectTemplateText(project) - else -> "You must have selected a project for this!" + else -> MCDevBundle("minecraft.settings.lang_template.project_must_be_selected") } private fun init() { if (project.isDefault) { - cmbScheme.selectedIndex = 0 cmbScheme.model = DefaultComboBoxModel(arrayOf("Global")) - } else if (cmbScheme.selectedIndex == 0) { + cmbScheme.selectedIndex = 0 + } else { cmbScheme.model = DefaultComboBoxModel(arrayOf("Global", "Project")) } cmbScheme.addActionListener { @@ -82,24 +100,25 @@ class TranslationTemplateConfigurable(private val project: Project) : Configurab editorColorsScheme, ) (templateEditor as EditorEx).highlighter = highlighter - templateEditor.settings.isLineNumbersShown = true + templateEditor!!.settings.isLineNumbersShown = true - editorPanel.preferredSize = JBUI.size(250, 100) - editorPanel.minimumSize = editorPanel.preferredSize editorPanel.removeAll() - editorPanel.add(templateEditor.component, BorderLayout.CENTER) + editorPanel.add(templateEditor!!.component, BorderLayout.CENTER) } override fun isModified(): Boolean { - return templateEditor.document.text != getActiveTemplateText() + return templateEditor?.document?.text != getActiveTemplateText() != false } override fun apply() { + val editor = templateEditor + ?: return + val project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(panel)) if (cmbScheme.selectedIndex == 0) { - TemplateManager.writeGlobalTemplate(templateEditor.document.text) + TemplateManager.writeGlobalTemplate(editor.document.text) } else if (project != null) { - TemplateManager.writeProjectTemplate(project, templateEditor.document.text) + TemplateManager.writeProjectTemplate(project, editor.document.text) } } diff --git a/src/main/resources/messages/MinecraftDevelopment.properties b/src/main/resources/messages/MinecraftDevelopment.properties index 21e09cdfc..c21cade20 100644 --- a/src/main/resources/messages/MinecraftDevelopment.properties +++ b/src/main/resources/messages/MinecraftDevelopment.properties @@ -193,6 +193,10 @@ nbt.file.save_notify.parse_exception.content=An unexpected exception happened, { intention.error.cannot.create.class.message=Cannot create class ''{0}''\n{1} intention.error.cannot.create.class.title=Failed to Create Class +translation_sort.title=Select Sort Order +translation_sort.order=Sort Order +translation_sort.keep_comment=Keep Comment + minecraft.settings.display_name=Minecraft Development minecraft.settings.title=Minecraft Development Settings minecraft.settings.change_update_channel=Change Plugin Update Channel @@ -203,3 +207,9 @@ minecraft.settings.show_chat_color_underlines=Show chat color underlines minecraft.settings.chat_color_underline_style=Chat color underline style: minecraft.settings.mixin=Mixin minecraft.settings.mixin.shadow_annotation_same_line=@Shadow annotations on same line +minecraft.settings.lang_template.display_name=Localization Template +minecraft.settings.lang_template.scheme=Scheme: +minecraft.settings.lang_template.project_must_be_selected=You must have selected a project for this! +minecraft.settings.lang_template.comment=You may edit the template used for translation key sorting here.\ +
Each line may be empty, a comment (with #) or a glob pattern for matching translation keys (like "item.*").\ +
Note: Empty lines are respected and will be put into the sorting result. From f1a1e0ad3ff95ea185f0b32cd2805b23574cc058 Mon Sep 17 00:00:00 2001 From: RedNesto Date: Thu, 4 Jul 2024 20:32:39 +0200 Subject: [PATCH 05/12] Disable code instrumentation We should not need it anymore now that the last forms are gone That should shorten the compilation time by a bit! --- build.gradle.kts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 3b2663f07..ecb6e12c7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -398,3 +398,11 @@ tasks.buildSearchableOptions { // not working atm enabled = false } + +tasks.instrumentCode { + enabled = false +} + +tasks.instrumentedJar { + enabled = false +} From 322cf27619bae830fcab97607dd6a1a2074cf67a Mon Sep 17 00:00:00 2001 From: RedNesto Date: Thu, 4 Jul 2024 20:34:43 +0200 Subject: [PATCH 06/12] Fix build It didn't get included in the previous commit --- build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle.kts b/build.gradle.kts index ecb6e12c7..a47d000e1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -377,6 +377,7 @@ tasks.register("cleanSandbox", Delete::class) { } tasks.withType { + pluginJar.set(tasks.jar.get().archiveFile) from(externalAnnotationsJar) { into("Minecraft Development/lib/resources") } From 838067d05f6eaa1521fcbc16f6c7ba4022401f4a Mon Sep 17 00:00:00 2001 From: RedNesto Date: Fri, 5 Jul 2024 10:44:56 +0200 Subject: [PATCH 07/12] Fix #2310 Translations aren't detected for enum constructors --- .../kotlin/translations/identification/TranslationIdentifier.kt | 2 +- src/main/kotlin/util/call-utils.kt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/translations/identification/TranslationIdentifier.kt b/src/main/kotlin/translations/identification/TranslationIdentifier.kt index 2b74bfe1d..f5be4bd04 100644 --- a/src/main/kotlin/translations/identification/TranslationIdentifier.kt +++ b/src/main/kotlin/translations/identification/TranslationIdentifier.kt @@ -69,7 +69,7 @@ abstract class TranslationIdentifier { if (container !is PsiExpressionList) { return null } - val call = container.parent as? PsiCallExpression ?: return null + val call = container.parent as? PsiCall ?: return null val index = container.expressions.indexOf(element) val method = call.referencedMethod ?: return null diff --git a/src/main/kotlin/util/call-utils.kt b/src/main/kotlin/util/call-utils.kt index 8e45a5a3c..90cb1ac6d 100644 --- a/src/main/kotlin/util/call-utils.kt +++ b/src/main/kotlin/util/call-utils.kt @@ -21,6 +21,7 @@ package com.demonwav.mcdev.util import com.intellij.psi.PsiCall +import com.intellij.psi.PsiEnumConstant import com.intellij.psi.PsiExpression import com.intellij.psi.PsiMethod import com.intellij.psi.PsiMethodCallExpression @@ -32,6 +33,7 @@ val PsiCall.referencedMethod: PsiMethod? get() = when (this) { is PsiMethodCallExpression -> this.methodExpression.advancedResolve(false).element as PsiMethod? is PsiNewExpression -> this.resolveMethod() + is PsiEnumConstant -> this.resolveMethod() else -> null } From 8f06f6002549f2f6e1f828ec5fc7102a5259b4f3 Mon Sep 17 00:00:00 2001 From: RedNesto Date: Fri, 5 Jul 2024 10:57:51 +0200 Subject: [PATCH 08/12] Fix #2260 expects varargs as return value for varargs target --- ...InvalidInjectorMethodSignatureInspection.kt | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/platform/mixin/inspection/injector/InvalidInjectorMethodSignatureInspection.kt b/src/main/kotlin/platform/mixin/inspection/injector/InvalidInjectorMethodSignatureInspection.kt index 5d636de7f..a51fd6db3 100644 --- a/src/main/kotlin/platform/mixin/inspection/injector/InvalidInjectorMethodSignatureInspection.kt +++ b/src/main/kotlin/platform/mixin/inspection/injector/InvalidInjectorMethodSignatureInspection.kt @@ -34,6 +34,7 @@ import com.demonwav.mcdev.platform.mixin.util.isConstructor import com.demonwav.mcdev.platform.mixin.util.isMixinExtrasSugar import com.demonwav.mcdev.util.Parameter import com.demonwav.mcdev.util.fullQualifiedName +import com.demonwav.mcdev.util.normalize import com.demonwav.mcdev.util.synchronize import com.intellij.codeInsight.intention.FileModifier.SafeFieldForPreview import com.intellij.codeInsight.intention.QuickFixFactory @@ -46,6 +47,7 @@ import com.intellij.psi.JavaElementVisitor import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiClassType import com.intellij.psi.PsiElementVisitor +import com.intellij.psi.PsiEllipsisType import com.intellij.psi.PsiMethod import com.intellij.psi.PsiModifier import com.intellij.psi.PsiNameHelper @@ -55,7 +57,6 @@ import com.intellij.psi.PsiType import com.intellij.psi.codeStyle.JavaCodeStyleManager import com.intellij.psi.codeStyle.VariableKind import com.intellij.psi.util.PsiUtil -import com.intellij.psi.util.TypeConversionUtil import org.objectweb.asm.Opcodes class InvalidInjectorMethodSignatureInspection : MixinInspection() { @@ -195,13 +196,18 @@ class InvalidInjectorMethodSignatureInspection : MixinInspection() { ) { reportedSignature = true + val normalizedExpected = when (expectedReturnType) { + is PsiEllipsisType -> expectedReturnType.toArrayType() + else -> expectedReturnType + } + holder.registerProblem( method.returnTypeElement ?: identifier, - "Expected return type '${expectedReturnType.presentableText}' " + + "Expected return type '${normalizedExpected.presentableText}' " + "for $annotationName method", QuickFixFactory.getInstance().createMethodReturnFix( method, - expectedReturnType, + normalizedExpected, false, ), ) @@ -218,9 +224,9 @@ class InvalidInjectorMethodSignatureInspection : MixinInspection() { method: PsiMethod, allowCoerce: Boolean, ): Boolean { - val expectedErasure = TypeConversionUtil.erasure(expectedReturnType) - val returnErasure = TypeConversionUtil.erasure(methodReturnType) - if (expectedErasure == returnErasure) { + val normalizedExpected = expectedReturnType.normalize() + val normalizedReturn = methodReturnType.normalize() + if (normalizedExpected == normalizedReturn) { return true } if (!allowCoerce || !method.hasAnnotation(COERCE)) { From f798d6d3544971f546cb1d13231edc021b74bf89 Mon Sep 17 00:00:00 2001 From: RedNesto Date: Fri, 5 Jul 2024 12:11:06 +0200 Subject: [PATCH 09/12] Remove unused import --- .../kotlin/translations/identification/TranslationIdentifier.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/translations/identification/TranslationIdentifier.kt b/src/main/kotlin/translations/identification/TranslationIdentifier.kt index f5be4bd04..fc5b5a0b1 100644 --- a/src/main/kotlin/translations/identification/TranslationIdentifier.kt +++ b/src/main/kotlin/translations/identification/TranslationIdentifier.kt @@ -38,7 +38,6 @@ import com.intellij.openapi.project.Project import com.intellij.psi.CommonClassNames import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiCall -import com.intellij.psi.PsiCallExpression import com.intellij.psi.PsiElement import com.intellij.psi.PsiEllipsisType import com.intellij.psi.PsiExpression From caca374c623d9f3315f3c9f9fc3e40597c40b28f Mon Sep 17 00:00:00 2001 From: RedNesto Date: Sat, 6 Jul 2024 11:37:38 +0200 Subject: [PATCH 10/12] Fix #2325 Make lang annotator fixes bulk compatible --- .../intentions/RemoveDuplicatesIntention.kt | 22 ++++++++++----- .../RemoveUnmatchedEntryIntention.kt | 19 +++++++------ .../intentions/TranslationFileAnnotator.kt | 6 ++--- .../intentions/TrimKeyIntention.kt | 27 +++++++++++++------ 4 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/main/kotlin/translations/intentions/RemoveDuplicatesIntention.kt b/src/main/kotlin/translations/intentions/RemoveDuplicatesIntention.kt index 52d4dcac5..30f227b40 100644 --- a/src/main/kotlin/translations/intentions/RemoveDuplicatesIntention.kt +++ b/src/main/kotlin/translations/intentions/RemoveDuplicatesIntention.kt @@ -23,24 +23,32 @@ package com.demonwav.mcdev.translations.intentions import com.demonwav.mcdev.translations.Translation import com.demonwav.mcdev.translations.TranslationFiles import com.demonwav.mcdev.translations.index.TranslationInverseIndex -import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction +import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile import com.intellij.psi.search.GlobalSearchScope -class RemoveDuplicatesIntention(private val translation: Translation) : PsiElementBaseIntentionAction() { +class RemoveDuplicatesIntention( + private val translation: Translation, + element: PsiElement +) : LocalQuickFixAndIntentionActionOnPsiElement(element) { override fun getText() = "Remove duplicates (keep this translation)" override fun getFamilyName() = "Minecraft localization" - override fun isAvailable(project: Project, editor: Editor?, element: PsiElement) = true - - override fun invoke(project: Project, editor: Editor?, element: PsiElement) { - val keep = TranslationFiles.seekTranslation(element) ?: return + override fun invoke( + project: Project, + file: PsiFile, + editor: Editor?, + startElement: PsiElement, + endElement: PsiElement + ) { + val keep = TranslationFiles.seekTranslation(startElement) ?: return val entries = TranslationInverseIndex.findElements( translation.key, - GlobalSearchScope.fileScope(element.containingFile), + GlobalSearchScope.fileScope(file), ) for (other in entries) { if (other !== keep) { diff --git a/src/main/kotlin/translations/intentions/RemoveUnmatchedEntryIntention.kt b/src/main/kotlin/translations/intentions/RemoveUnmatchedEntryIntention.kt index 6185d6f2c..78884869e 100644 --- a/src/main/kotlin/translations/intentions/RemoveUnmatchedEntryIntention.kt +++ b/src/main/kotlin/translations/intentions/RemoveUnmatchedEntryIntention.kt @@ -21,21 +21,24 @@ package com.demonwav.mcdev.translations.intentions import com.demonwav.mcdev.translations.TranslationFiles -import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction +import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement -import com.intellij.util.IncorrectOperationException +import com.intellij.psi.PsiFile -class RemoveUnmatchedEntryIntention : PsiElementBaseIntentionAction() { +class RemoveUnmatchedEntryIntention(element: PsiElement) : LocalQuickFixAndIntentionActionOnPsiElement(element) { override fun getText() = "Remove translation" - override fun isAvailable(project: Project, editor: Editor, element: PsiElement) = true - override fun getFamilyName() = "Minecraft" - @Throws(IncorrectOperationException::class) - override fun invoke(project: Project, editor: Editor, element: PsiElement) { - TranslationFiles.remove(TranslationFiles.seekTranslation(element) ?: return) + override fun invoke( + project: Project, + file: PsiFile, + editor: Editor?, + startElement: PsiElement, + endElement: PsiElement + ) { + TranslationFiles.remove(TranslationFiles.seekTranslation(startElement) ?: return) } } diff --git a/src/main/kotlin/translations/intentions/TranslationFileAnnotator.kt b/src/main/kotlin/translations/intentions/TranslationFileAnnotator.kt index 535d3fe6e..302384d1d 100644 --- a/src/main/kotlin/translations/intentions/TranslationFileAnnotator.kt +++ b/src/main/kotlin/translations/intentions/TranslationFileAnnotator.kt @@ -49,7 +49,7 @@ class TranslationFileAnnotator : Annotator { if (translation.key != translation.trimmedKey) { annotations.newAnnotation(HighlightSeverity.WARNING, "Translation key contains whitespace at start or end.") .range(element) - .newFix(TrimKeyIntention()).registerFix() + .newFix(TrimKeyIntention(element)).universal().registerFix() .create() } } @@ -58,7 +58,7 @@ class TranslationFileAnnotator : Annotator { val count = TranslationIndex.getTranslations(element.containingFile).count { it.key == translation.key } if (count > 1) { annotations.newAnnotation(HighlightSeverity.WARNING, "Duplicate translation keys \"${translation.key}\".") - .newFix(RemoveDuplicatesIntention(translation)).registerFix() + .newFix(RemoveDuplicatesIntention(translation, element)).universal().registerFix() .create() } } @@ -71,7 +71,7 @@ class TranslationFileAnnotator : Annotator { } val warningText = "Translation key not included in default localization file." annotations.newAnnotation(HighlightSeverity.WARNING, warningText) - .newFix(RemoveUnmatchedEntryIntention()).registerFix() + .newFix(RemoveUnmatchedEntryIntention(element)).universal().registerFix() .create() } } diff --git a/src/main/kotlin/translations/intentions/TrimKeyIntention.kt b/src/main/kotlin/translations/intentions/TrimKeyIntention.kt index 0e4a9851b..9e532db28 100644 --- a/src/main/kotlin/translations/intentions/TrimKeyIntention.kt +++ b/src/main/kotlin/translations/intentions/TrimKeyIntention.kt @@ -22,28 +22,39 @@ package com.demonwav.mcdev.translations.intentions import com.demonwav.mcdev.translations.TranslationFiles import com.intellij.codeInsight.FileModificationService -import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction +import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement -import com.intellij.util.IncorrectOperationException +import com.intellij.psi.PsiFile -class TrimKeyIntention : PsiElementBaseIntentionAction() { +class TrimKeyIntention(element: PsiElement) : LocalQuickFixAndIntentionActionOnPsiElement(element) { override fun getText() = "Trim translation key" override fun getFamilyName() = "Minecraft" - override fun isAvailable(project: Project, editor: Editor, element: PsiElement): Boolean { + override fun isAvailable( + project: Project, + file: PsiFile, + editor: Editor?, + startElement: PsiElement, + endElement: PsiElement + ): Boolean { val translation = TranslationFiles.toTranslation( - TranslationFiles.seekTranslation(element) ?: return false, + TranslationFiles.seekTranslation(startElement) ?: return false, ) ?: return false return translation.key != translation.trimmedKey } - @Throws(IncorrectOperationException::class) - override fun invoke(project: Project, editor: Editor, element: PsiElement) { - val entry = TranslationFiles.seekTranslation(element) ?: return + override fun invoke( + project: Project, + file: PsiFile, + editor: Editor?, + startElement: PsiElement, + endElement: PsiElement + ) { + val entry = TranslationFiles.seekTranslation(startElement) ?: return if (!FileModificationService.getInstance().preparePsiElementForWrite(entry)) { return } From 336924bed50a7b31ab01929da3477c5742cf9b02 Mon Sep 17 00:00:00 2001 From: Moulberry Date: Fri, 3 May 2024 17:26:57 +0800 Subject: [PATCH 11/12] Translation: option to force json and configurable default i18n call --- src/main/kotlin/TranslationSettings.kt | 71 +++++++++++++++++++ .../mcp/mappings/HardcodedYarnToMojmap.kt | 9 +++ .../kotlin/platform/mcp/mappings/Mappings.kt | 7 ++ .../kotlin/translations/TranslationFiles.kt | 54 ++++++++++++-- .../ConvertToTranslationIntention.kt | 24 ++++++- .../TranslationTemplateConfigurable.kt | 33 ++++++++- src/main/resources/META-INF/plugin.xml | 1 + .../messages/MinecraftDevelopment.properties | 3 + 8 files changed, 191 insertions(+), 11 deletions(-) create mode 100644 src/main/kotlin/TranslationSettings.kt diff --git a/src/main/kotlin/TranslationSettings.kt b/src/main/kotlin/TranslationSettings.kt new file mode 100644 index 000000000..f2b5cf483 --- /dev/null +++ b/src/main/kotlin/TranslationSettings.kt @@ -0,0 +1,71 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev + +import com.intellij.openapi.components.PersistentStateComponent +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project + +@State(name = "TranslationSettings", storages = [Storage("minecraft_dev.xml")]) +class TranslationSettings : PersistentStateComponent { + + data class State( + var isForceJsonTranslationFile: Boolean = false, + var isUseCustomConvertToTranslationTemplate: Boolean = false, + var convertToTranslationTemplate: String = "net.minecraft.client.resources.I18n.format(\"\$key\")", + ) + + private var state = State() + + override fun getState(): State { + return state + } + + override fun loadState(state: State) { + this.state = state + } + + // State mappings + var isForceJsonTranslationFile: Boolean + get() = state.isForceJsonTranslationFile + set(forceJsonTranslationFile) { + state.isForceJsonTranslationFile = forceJsonTranslationFile + } + + var isUseCustomConvertToTranslationTemplate: Boolean + get() = state.isUseCustomConvertToTranslationTemplate + set(useCustomConvertToTranslationTemplate) { + state.isUseCustomConvertToTranslationTemplate = useCustomConvertToTranslationTemplate + } + + var convertToTranslationTemplate: String + get() = state.convertToTranslationTemplate + set(convertToTranslationTemplate) { + state.convertToTranslationTemplate = convertToTranslationTemplate + } + + companion object { + @JvmStatic + fun getInstance(project: Project): TranslationSettings = project.service() + } +} diff --git a/src/main/kotlin/platform/mcp/mappings/HardcodedYarnToMojmap.kt b/src/main/kotlin/platform/mcp/mappings/HardcodedYarnToMojmap.kt index 5f3182872..e2919e8e0 100644 --- a/src/main/kotlin/platform/mcp/mappings/HardcodedYarnToMojmap.kt +++ b/src/main/kotlin/platform/mcp/mappings/HardcodedYarnToMojmap.kt @@ -43,6 +43,15 @@ object HardcodedYarnToMojmap { owner = "net.minecraft.network.chat.Component", name = "translatableEscape", descriptor = "(Ljava/lang/String;[Ljava/lang/Object;)Lnet/minecraft/network/chat/MutableComponent;" + ), + MemberReference( + owner = "net.minecraft.client.resource.language.I18n", + name = "translate", + descriptor = "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;" + ) mapTo MemberReference( + owner = "net.minecraft.client.resources.language.I18n", + name = "get", + descriptor = "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;" ) ), hashMapOf(), diff --git a/src/main/kotlin/platform/mcp/mappings/Mappings.kt b/src/main/kotlin/platform/mcp/mappings/Mappings.kt index 9ddf8e6a6..1135d40e9 100644 --- a/src/main/kotlin/platform/mcp/mappings/Mappings.kt +++ b/src/main/kotlin/platform/mcp/mappings/Mappings.kt @@ -119,6 +119,13 @@ fun Module.getMappedMethod(mojangClass: String, mojangMethod: String, mojangDesc return getMappedMethod(MemberReference(mojangMethod, mojangDescriptor, mojangClass)) } +fun Module.getMappedMethodCall(mojangClass: String, mojangMethod: String, mojangDescriptor: String, p: String): String { + val mappedMethodRef = namedToMojang?.tryGetMappedMethod( + MemberReference(mojangMethod, mojangDescriptor, mojangClass) + ) ?: return "$mojangClass.$mojangMethod($p)" + return "${mappedMethodRef.owner}.${mappedMethodRef.name}($p)" +} + fun Module.getMojangMethod(mappedMethod: MemberReference): String { return namedToMojang?.getIntermediaryMethod(mappedMethod)?.name ?: return mappedMethod.name } diff --git a/src/main/kotlin/translations/TranslationFiles.kt b/src/main/kotlin/translations/TranslationFiles.kt index b2fd47f71..8b8f74027 100644 --- a/src/main/kotlin/translations/TranslationFiles.kt +++ b/src/main/kotlin/translations/TranslationFiles.kt @@ -20,6 +20,7 @@ package com.demonwav.mcdev.translations +import com.demonwav.mcdev.TranslationSettings import com.demonwav.mcdev.translations.index.TranslationIndex import com.demonwav.mcdev.translations.index.TranslationInverseIndex import com.demonwav.mcdev.translations.lang.LangFile @@ -110,12 +111,43 @@ object TranslationFiles { element.delete() } + fun findTranslationKeyForText(context: PsiElement, text: String): String? { + val module = context.findModule() + ?: throw IllegalArgumentException("Cannot add translation for element outside of module") + var jsonVersion = true + if (!TranslationSettings.getInstance(context.project).isForceJsonTranslationFile) { + val version = + context.mcVersion ?: throw IllegalArgumentException("Cannot determine MC version for element $context") + jsonVersion = version > MC_1_12_2 + } + + if (!jsonVersion) { + // This feature only supports JSON translation files + return null + } + + val files = FileTypeIndex.getFiles( + JsonFileType.INSTANCE, + GlobalSearchScope.moduleScope(module), + ).filter { getLocale(it) == TranslationConstants.DEFAULT_LOCALE } + + for (file in files) { + val psiFile = PsiManager.getInstance(context.project).findFile(file) ?: continue + psiFile.findKeyForTextAsJson(text)?.let { return it } + } + + return null + } + fun add(context: PsiElement, key: String, text: String) { val module = context.findModule() ?: throw IllegalArgumentException("Cannot add translation for element outside of module") - val version = - context.mcVersion ?: throw IllegalArgumentException("Cannot determine MC version for element $context") - val jsonVersion = version > MC_1_12_2 + var jsonVersion = true + if (!TranslationSettings.getInstance(context.project).isForceJsonTranslationFile) { + val version = + context.mcVersion ?: throw IllegalArgumentException("Cannot determine MC version for element $context") + jsonVersion = version > MC_1_12_2 + } fun write(files: Iterable) { for (file in files) { @@ -223,6 +255,13 @@ object TranslationFiles { doc.insertString(rootObject.lastChild.prevSibling.textOffset, content) } + private fun PsiFile.findKeyForTextAsJson(text: String): String? { + val rootObject = this.firstChild as? JsonObject ?: return null + return rootObject.propertyList.firstOrNull { + (it.value as? JsonStringLiteral)?.value == text + }?.name + } + private fun generateJsonFile( leadingComma: Boolean, indent: CharSequence, @@ -292,9 +331,12 @@ object TranslationFiles { fun buildSortingTemplateFromDefault(context: PsiElement, domain: String? = null): Template? { val module = context.findModule() ?: throw IllegalArgumentException("Cannot add translation for element outside of module") - val version = - context.mcVersion ?: throw IllegalArgumentException("Cannot determine MC version for element $context") - val jsonVersion = version > MC_1_12_2 + var jsonVersion = true + if (!TranslationSettings.getInstance(context.project).isForceJsonTranslationFile) { + val version = + context.mcVersion ?: throw IllegalArgumentException("Cannot determine MC version for element $context") + jsonVersion = version > MC_1_12_2 + } val defaultTranslationFile = FileBasedIndex.getInstance() .getContainingFiles( diff --git a/src/main/kotlin/translations/intentions/ConvertToTranslationIntention.kt b/src/main/kotlin/translations/intentions/ConvertToTranslationIntention.kt index aa3046dc5..9db73187d 100644 --- a/src/main/kotlin/translations/intentions/ConvertToTranslationIntention.kt +++ b/src/main/kotlin/translations/intentions/ConvertToTranslationIntention.kt @@ -20,7 +20,10 @@ package com.demonwav.mcdev.translations.intentions +import com.demonwav.mcdev.TranslationSettings +import com.demonwav.mcdev.platform.mcp.mappings.getMappedMethodCall import com.demonwav.mcdev.translations.TranslationFiles +import com.demonwav.mcdev.util.findModule import com.demonwav.mcdev.util.runWriteAction import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction import com.intellij.lang.java.JavaLanguage @@ -42,6 +45,9 @@ class ConvertToTranslationIntention : PsiElementBaseIntentionAction() { override fun invoke(project: Project, editor: Editor, element: PsiElement) { if (element.parent is PsiLiteral) { val value = (element.parent as PsiLiteral).value as? String ?: return + + val existingKey = TranslationFiles.findTranslationKeyForText(element, value) + val result = Messages.showInputDialogWithCheckBox( "Enter translation key:", "Convert String Literal to Translation", @@ -49,7 +55,7 @@ class ConvertToTranslationIntention : PsiElementBaseIntentionAction() { true, true, Messages.getQuestionIcon(), - null, + existingKey, object : InputValidatorEx { override fun getErrorText(inputString: String): String? { if (inputString.isEmpty()) { @@ -73,12 +79,24 @@ class ConvertToTranslationIntention : PsiElementBaseIntentionAction() { val key = result.first ?: return val replaceLiteral = result.second try { - TranslationFiles.add(element, key, value) + if (existingKey != key) { + TranslationFiles.add(element, key, value) + } if (replaceLiteral) { + val translationSettings = TranslationSettings.getInstance(project) val psi = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return psi.runWriteAction { val expression = JavaPsiFacade.getElementFactory(project).createExpressionFromText( - "net.minecraft.client.resources.I18n.format(\"$key\")", + if (translationSettings.isUseCustomConvertToTranslationTemplate) { + translationSettings.convertToTranslationTemplate.replace("\$key", key) + } else { + element.findModule()?.getMappedMethodCall( + "net.minecraft.client.resource.language.I18n", + "translate", + "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;", + "\"$key\"" + ) ?: "net.minecraft.client.resource.I18n.get(\"$key\")" + }, element.context, ) if (psi.language === JavaLanguage.INSTANCE) { diff --git a/src/main/kotlin/translations/sorting/TranslationTemplateConfigurable.kt b/src/main/kotlin/translations/sorting/TranslationTemplateConfigurable.kt index e4e148a47..20daa77ee 100644 --- a/src/main/kotlin/translations/sorting/TranslationTemplateConfigurable.kt +++ b/src/main/kotlin/translations/sorting/TranslationTemplateConfigurable.kt @@ -20,6 +20,7 @@ package com.demonwav.mcdev.translations.sorting +import com.demonwav.mcdev.TranslationSettings import com.demonwav.mcdev.asset.MCDevBundle import com.demonwav.mcdev.translations.lang.colors.LangSyntaxHighlighter import com.intellij.codeInsight.template.impl.TemplateEditorUtil @@ -32,7 +33,13 @@ import com.intellij.openapi.editor.ex.util.LexerEditorHighlighter import com.intellij.openapi.options.Configurable import com.intellij.openapi.project.Project import com.intellij.ui.dsl.builder.Align +import com.intellij.ui.dsl.builder.COLUMNS_LARGE +import com.intellij.ui.dsl.builder.bindSelected +import com.intellij.ui.dsl.builder.bindText +import com.intellij.ui.dsl.builder.columns import com.intellij.ui.dsl.builder.panel +import com.intellij.ui.dsl.builder.selected +import com.intellij.ui.layout.ComponentPredicate import com.intellij.util.ui.JBUI import java.awt.BorderLayout import javax.swing.DefaultComboBoxModel @@ -46,7 +53,7 @@ class TranslationTemplateConfigurable(private val project: Project) : Configurab private var templateEditor: Editor? = null private val editorPanel = JPanel(BorderLayout()).apply { - preferredSize = JBUI.size(250, 450) + preferredSize = JBUI.size(250, 350) minimumSize = preferredSize } @@ -62,6 +69,25 @@ class TranslationTemplateConfigurable(private val project: Project) : Configurab row { cell(editorPanel).align(Align.FILL) } + + val translationSettings = TranslationSettings.getInstance(project) + row { + checkBox(MCDevBundle("minecraft.settings.translation.force_json_translation_file")) + .bindSelected(translationSettings::isForceJsonTranslationFile) + } + + lateinit var allowConvertToTranslationTemplate: ComponentPredicate + row { + val checkBox = checkBox(MCDevBundle("minecraft.settings.translation.use_custom_convert_template")) + .bindSelected(translationSettings::isUseCustomConvertToTranslationTemplate) + allowConvertToTranslationTemplate = checkBox.selected + } + + row { + textField().bindText(translationSettings::convertToTranslationTemplate) + .enabledIf(allowConvertToTranslationTemplate) + .columns(COLUMNS_LARGE) + } } @Nls @@ -107,7 +133,7 @@ class TranslationTemplateConfigurable(private val project: Project) : Configurab } override fun isModified(): Boolean { - return templateEditor?.document?.text != getActiveTemplateText() != false + return templateEditor?.document?.text != getActiveTemplateText() != false || panel.isModified() } override fun apply() { @@ -120,9 +146,12 @@ class TranslationTemplateConfigurable(private val project: Project) : Configurab } else if (project != null) { TemplateManager.writeProjectTemplate(project, editor.document.text) } + + panel.apply() } override fun reset() { init() + panel.reset() } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 4f18c81e1..5be203414 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -285,6 +285,7 @@ + diff --git a/src/main/resources/messages/MinecraftDevelopment.properties b/src/main/resources/messages/MinecraftDevelopment.properties index c21cade20..7ab481b10 100644 --- a/src/main/resources/messages/MinecraftDevelopment.properties +++ b/src/main/resources/messages/MinecraftDevelopment.properties @@ -213,3 +213,6 @@ minecraft.settings.lang_template.project_must_be_selected=You must have selected minecraft.settings.lang_template.comment=You may edit the template used for translation key sorting here.\
Each line may be empty, a comment (with #) or a glob pattern for matching translation keys (like "item.*").\
Note: Empty lines are respected and will be put into the sorting result. +minecraft.settings.translation=Translation +minecraft.settings.translation.force_json_translation_file=Force JSON translation file (1.13+) +minecraft.settings.translation.use_custom_convert_template=Use custom template for convert literal to translation From fbbb2a2162bdf2a56241a81d8b2b317c11063232 Mon Sep 17 00:00:00 2001 From: RedNesto Date: Sun, 7 Jul 2024 17:52:45 +0200 Subject: [PATCH 12/12] Provide Minecraft version in Loom-based projects --- .../FabricLoomModelBuilderImpl.groovy | 3 ++- .../fabricloom/FabricLoomModelImpl.groovy | 1 + .../tooling/fabricloom/FabricLoomModel.java | 2 ++ .../FabricLoomProjectResolverExtension.kt | 20 +++++++++++++++++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/gradle-tooling-extension/groovy/com/demonwav/mcdev/platform/mcp/gradle/tooling/fabricloom/FabricLoomModelBuilderImpl.groovy b/src/gradle-tooling-extension/groovy/com/demonwav/mcdev/platform/mcp/gradle/tooling/fabricloom/FabricLoomModelBuilderImpl.groovy index 9cfd8f225..c751e8998 100644 --- a/src/gradle-tooling-extension/groovy/com/demonwav/mcdev/platform/mcp/gradle/tooling/fabricloom/FabricLoomModelBuilderImpl.groovy +++ b/src/gradle-tooling-extension/groovy/com/demonwav/mcdev/platform/mcp/gradle/tooling/fabricloom/FabricLoomModelBuilderImpl.groovy @@ -50,6 +50,7 @@ class FabricLoomModelBuilderImpl implements ModelBuilderService { } FabricLoomModel build(Project project, Object loomExtension) { + def minecraftVersion = loomExtension.minecraftProvider.minecraftVersion() def tinyMappings = loomExtension.mappingsFile def splitMinecraftJar = loomExtension.areEnvironmentSourceSetsSplit() @@ -70,7 +71,7 @@ class FabricLoomModelBuilderImpl implements ModelBuilderService { } //noinspection GroovyAssignabilityCheck - return new FabricLoomModelImpl(tinyMappings, decompilers, splitMinecraftJar, modSourceSets) + return new FabricLoomModelImpl(minecraftVersion, tinyMappings, decompilers, splitMinecraftJar, modSourceSets) } List getDecompilers(Object loomExtension, boolean client) { diff --git a/src/gradle-tooling-extension/groovy/com/demonwav/mcdev/platform/mcp/gradle/tooling/fabricloom/FabricLoomModelImpl.groovy b/src/gradle-tooling-extension/groovy/com/demonwav/mcdev/platform/mcp/gradle/tooling/fabricloom/FabricLoomModelImpl.groovy index 1a04fab1e..d54149dbf 100644 --- a/src/gradle-tooling-extension/groovy/com/demonwav/mcdev/platform/mcp/gradle/tooling/fabricloom/FabricLoomModelImpl.groovy +++ b/src/gradle-tooling-extension/groovy/com/demonwav/mcdev/platform/mcp/gradle/tooling/fabricloom/FabricLoomModelImpl.groovy @@ -24,6 +24,7 @@ import groovy.transform.Immutable @Immutable(knownImmutableClasses = [File]) class FabricLoomModelImpl implements FabricLoomModel, Serializable { + String minecraftVersion File tinyMappings Map> decompilers boolean splitMinecraftJar diff --git a/src/gradle-tooling-extension/java/com/demonwav/mcdev/platform/mcp/gradle/tooling/fabricloom/FabricLoomModel.java b/src/gradle-tooling-extension/java/com/demonwav/mcdev/platform/mcp/gradle/tooling/fabricloom/FabricLoomModel.java index 6198402bf..e984864a2 100644 --- a/src/gradle-tooling-extension/java/com/demonwav/mcdev/platform/mcp/gradle/tooling/fabricloom/FabricLoomModel.java +++ b/src/gradle-tooling-extension/java/com/demonwav/mcdev/platform/mcp/gradle/tooling/fabricloom/FabricLoomModel.java @@ -26,6 +26,8 @@ public interface FabricLoomModel { + String getMinecraftVersion(); + File getTinyMappings(); Map> getDecompilers(); diff --git a/src/main/kotlin/platform/mcp/fabricloom/FabricLoomProjectResolverExtension.kt b/src/main/kotlin/platform/mcp/fabricloom/FabricLoomProjectResolverExtension.kt index 82ec68855..b89478dcc 100644 --- a/src/main/kotlin/platform/mcp/fabricloom/FabricLoomProjectResolverExtension.kt +++ b/src/main/kotlin/platform/mcp/fabricloom/FabricLoomProjectResolverExtension.kt @@ -20,10 +20,13 @@ package com.demonwav.mcdev.platform.mcp.fabricloom +import com.demonwav.mcdev.platform.mcp.McpModuleSettings +import com.demonwav.mcdev.platform.mcp.gradle.McpModelData import com.demonwav.mcdev.platform.mcp.gradle.tooling.fabricloom.FabricLoomModel import com.intellij.openapi.externalSystem.model.DataNode import com.intellij.openapi.externalSystem.model.project.ModuleData import org.gradle.tooling.model.idea.IdeaModule +import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData import org.jetbrains.plugins.gradle.service.project.AbstractProjectResolverExtension class FabricLoomProjectResolverExtension : AbstractProjectResolverExtension() { @@ -50,6 +53,23 @@ class FabricLoomProjectResolverExtension : AbstractProjectResolverExtension() { loomData.modSourceSets ) ideModule.createChild(FabricLoomData.KEY, data) + + val mcpData = McpModelData( + ideModule.data, + McpModuleSettings.State( + minecraftVersion = loomData.minecraftVersion, + ), + null, + null + ) + ideModule.createChild(McpModelData.KEY, mcpData) + + for (child in ideModule.children) { + val childData = child.data + if (childData is GradleSourceSetData) { + child.createChild(McpModelData.KEY, mcpData.copy(module = childData)) + } + } } super.populateModuleExtraModels(gradleModule, ideModule)