diff --git a/build.gradle.kts b/build.gradle.kts index 0e8cf8ae6..8005fa282 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -362,7 +362,10 @@ val generateLangParser by parser("LangParser", "com/demonwav/mcdev/translations/ val generateMEExpressionLexer by lexer("MEExpressionLexer", "com/demonwav/mcdev/platform/mixin/expression/gen") val generateMEExpressionParser by parser("MEExpressionParser", "com/demonwav/mcdev/platform/mixin/expression/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" @@ -395,6 +398,7 @@ tasks.register("cleanSandbox", Delete::class) { } tasks.withType { + pluginJar.set(tasks.jar.get().archiveFile) from(externalAnnotationsJar) { into("Minecraft Development/lib/resources") } @@ -416,3 +420,11 @@ tasks.buildSearchableOptions { // not working atm enabled = false } + +tasks.instrumentCode { + enabled = false +} + +tasks.instrumentedJar { + enabled = false +} 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/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/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/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/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/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) 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/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/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 } diff --git a/src/main/kotlin/platform/mixin/inspection/injector/InvalidInjectorMethodSignatureInspection.kt b/src/main/kotlin/platform/mixin/inspection/injector/InvalidInjectorMethodSignatureInspection.kt index cd25a23a3..bc168040a 100644 --- a/src/main/kotlin/platform/mixin/inspection/injector/InvalidInjectorMethodSignatureInspection.kt +++ b/src/main/kotlin/platform/mixin/inspection/injector/InvalidInjectorMethodSignatureInspection.kt @@ -60,6 +60,7 @@ import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiClassType import com.intellij.psi.PsiElement import com.intellij.psi.PsiElementVisitor +import com.intellij.psi.PsiEllipsisType import com.intellij.psi.PsiFile import com.intellij.psi.PsiMethod import com.intellij.psi.PsiModifier @@ -183,12 +184,16 @@ class InvalidInjectorMethodSignatureInspection : MixinInspection() { if (!isValid) { val (expectedParameters, expectedReturnType, intLikeTypePositions) = possibleSignatures[0] + val normalizedReturnType = when (expectedReturnType) { + is PsiEllipsisType -> expectedReturnType.toArrayType() + else -> expectedReturnType + } val paramsCheck = checkParameters(parameters, expectedParameters, handler.allowCoerce) val isWarning = paramsCheck == CheckResult.WARNING val methodReturnType = method.returnType val returnTypeOk = methodReturnType != null && - checkReturnType(expectedReturnType, methodReturnType, method, handler.allowCoerce) + checkReturnType(normalizedReturnType, methodReturnType, method, handler.allowCoerce) val isError = paramsCheck == CheckResult.ERROR || !returnTypeOk if (isWarning || isError) { reportedSignature = true @@ -198,7 +203,7 @@ class InvalidInjectorMethodSignatureInspection : MixinInspection() { val quickFix = SignatureQuickFix( method, expectedParameters.takeUnless { paramsCheck == CheckResult.OK }, - expectedReturnType.takeUnless { returnTypeOk }, + normalizedReturnType.takeUnless { returnTypeOk }, intLikeTypePositions ) val highlightType = 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/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/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/identification/TranslationIdentifier.kt b/src/main/kotlin/translations/identification/TranslationIdentifier.kt index 2b74bfe1d..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 @@ -69,7 +68,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/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/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 } 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..20daa77ee 100644 --- a/src/main/kotlin/translations/sorting/TranslationTemplateConfigurable.kt +++ b/src/main/kotlin/translations/sorting/TranslationTemplateConfigurable.kt @@ -20,6 +20,8 @@ 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 import com.intellij.ide.DataManager @@ -30,6 +32,14 @@ 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.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 @@ -39,32 +49,66 @@ 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, 350) + 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) + } + + 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 - 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,28 +126,32 @@ 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 || panel.isModified() } 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) } + + panel.apply() } override fun reset() { init() + panel.reset() } } 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()) 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 } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 273df3975..7a71f159b 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -291,6 +291,7 @@ + diff --git a/src/main/resources/messages/MinecraftDevelopment.properties b/src/main/resources/messages/MinecraftDevelopment.properties index 707f14ec5..647e1703d 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 @@ -190,9 +193,19 @@ 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.change_update_channel=Change Plugin Update Channel minecraft.settings.chat_color_underline_style=Chat color underline style: minecraft.settings.display_name=Minecraft Development +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. minecraft.settings.mixin.definition_pos_relative_to_expression=@Definition position relative to @Expression minecraft.settings.mixin.shadow_annotation_same_line=@Shadow annotations on same line minecraft.settings.mixin=Mixin @@ -202,6 +215,9 @@ minecraft.settings.show_chat_color_underlines=Show chat color underlines minecraft.settings.show_event_listener_gutter_icons=Show event listener gutter icons minecraft.settings.show_project_platform_icons=Show project platform icons minecraft.settings.title=Minecraft Development Settings +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 minecraft.before=Before minecraft.after=After