User: rednesto Date: 10 Jul 24 15:23 Revision: b5dc84ca20a2c4009d0365b568245e5a20d9f77c Summary: Translation: ConvertToTranslationIntention to UAST TeamCity URL: https://ci.mcdev.io/viewModification.html?tab=vcsModificationFiles&modId=9399&personal=false Index: src/main/kotlin/translations/intentions/ConvertToTranslationIntention.kt =================================================================== --- src/main/kotlin/translations/intentions/ConvertToTranslationIntention.kt (revision 52c1f16da708b061c214b18bbda62014f38c038b) +++ src/main/kotlin/translations/intentions/ConvertToTranslationIntention.kt (revision b5dc84ca20a2c4009d0365b568245e5a20d9f77c) @@ -26,101 +26,114 @@ import com.demonwav.mcdev.util.findModule import com.demonwav.mcdev.util.runWriteAction import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction -import com.intellij.lang.java.JavaLanguage import com.intellij.notification.Notification import com.intellij.notification.NotificationType import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.ui.InputValidatorEx import com.intellij.openapi.ui.Messages -import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement -import com.intellij.psi.PsiLiteral -import com.intellij.psi.codeStyle.JavaCodeStyleManager import com.intellij.util.IncorrectOperationException +import org.jetbrains.uast.ULiteralExpression +import org.jetbrains.uast.UReferenceExpression +import org.jetbrains.uast.evaluateString +import org.jetbrains.uast.findUElementAt +import org.jetbrains.uast.generate.generationPlugin +import org.jetbrains.uast.textRange +import org.jetbrains.uast.toUElementOfType class ConvertToTranslationIntention : PsiElementBaseIntentionAction() { @Throws(IncorrectOperationException::class) 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 literal = element.parent.toUElementOfType() ?: return + val value = literal.evaluateString() ?: return - val existingKey = TranslationFiles.findTranslationKeyForText(element, value) + val existingKey = TranslationFiles.findTranslationKeyForText(element, value) - val result = Messages.showInputDialogWithCheckBox( - "Enter translation key:", - "Convert String Literal to Translation", - "Replace literal with call to I18n (only works on clients!)", - true, - true, - Messages.getQuestionIcon(), - existingKey, - object : InputValidatorEx { - override fun getErrorText(inputString: String): String? { - if (inputString.isEmpty()) { - return "Key must not be empty" - } - if (inputString.contains('=')) { - return "Key must not contain separator character ('=')" - } - return null - } + val result = Messages.showInputDialogWithCheckBox( + "Enter translation key:", + "Convert String Literal to Translation", + "Replace literal with call to I18n (only works on clients!)", + true, + true, + Messages.getQuestionIcon(), + existingKey, + object : InputValidatorEx { + override fun getErrorText(inputString: String): String? { + if (inputString.isEmpty()) { + return "Key must not be empty" + } + if (inputString.contains('=')) { + return "Key must not contain separator character ('=')" + } + return null + } - override fun checkInput(inputString: String): Boolean { - return inputString.isNotEmpty() && !inputString.contains('=') - } + override fun checkInput(inputString: String): Boolean { + return inputString.isNotEmpty() && !inputString.contains('=') + } - override fun canClose(inputString: String): Boolean { - return inputString.isNotEmpty() && !inputString.contains('=') - } - }, - ) - val key = result.first ?: return - val replaceLiteral = result.second - try { - if (existingKey != key) { - TranslationFiles.add(element, key, value) - } - if (replaceLiteral) { - val translationSettings = TranslationSettings.getInstance(project) + override fun canClose(inputString: String): Boolean { + return inputString.isNotEmpty() && !inputString.contains('=') + } + }, + ) + val key = result.first ?: return + val replaceLiteral = result.second + try { + 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( - if (translationSettings.isUseCustomConvertToTranslationTemplate) { + val documentManager = PsiDocumentManager.getInstance(project) + val psi = documentManager.getPsiFile(editor.document) ?: return + val callCode = 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\")" + 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) { - JavaCodeStyleManager.getInstance(project) - .shortenClassReferences(element.parent.replace(expression)) - } else { - element.parent.replace(expression) - } + } + + val replaceRange = when (literal.lang.id) { + // Special case because in Kotlin, the sourcePsi is a template entry, not the literal itself + "kotlin" -> literal.sourcePsi?.parent?.textRange + else -> literal.textRange + } ?: return + + psi.runWriteAction { + // There is no convenient way to generate a qualified call expression with the UAST factory + // so we simply put the raw code there and assume it's correct + editor.document.replaceString(replaceRange.startOffset, replaceRange.endOffset, callCode) + documentManager.commitDocument(editor.document) + + val callOffset = replaceRange.startOffset + callCode.indexOf('(') + val newExpr = psi.findUElementAt(callOffset - 1, UReferenceExpression::class.java) + if (newExpr != null) { + literal.generationPlugin?.shortenReference(newExpr) } } + } - } catch (e: Exception) { - Notification( - "Translation support error", - "Error while adding translation", - e.message ?: e.stackTraceToString(), - NotificationType.WARNING, - ).notify(project) - } - } + } catch (e: Exception) { + Notification( + "Translation support error", + "Error while adding translation", + e.message ?: e.stackTraceToString(), + NotificationType.WARNING, + ).notify(project) + } + } + + override fun isAvailable(project: Project, editor: Editor, element: PsiElement): Boolean { + val literal = element.parent.toUElementOfType() + return literal?.evaluateString() is String } - override fun isAvailable(project: Project, editor: Editor, element: PsiElement) = - (element.parent as? PsiLiteral)?.value is String - override fun getFamilyName() = "Convert string literal to translation" override fun getText() = "Convert string literal to translation" Index: src/main/resources/META-INF/plugin.xml =================================================================== --- src/main/resources/META-INF/plugin.xml (revision 52c1f16da708b061c214b18bbda62014f38c038b) +++ src/main/resources/META-INF/plugin.xml (revision b5dc84ca20a2c4009d0365b568245e5a20d9f77c) @@ -267,7 +267,7 @@ - JAVA + UAST com.demonwav.mcdev.translations.intentions.ConvertToTranslationIntention Minecraft convertToTranslation