User: joe Date: 11 Nov 25 01:01 Revision: 1d88fee8c10f5f8387a006a90fa507492138cc72 Summary: Weaken assertions in TranslationFiles, things can actually go wrong. Refactor showBalloon so it can be used elsewhere. Fixes MCDEV-B TeamCity URL: http://ci.mcdev.io:80/viewModification.html?tab=vcsModificationFiles&modId=10259&personal=false Index: src/main/kotlin/platform/mcp/actions/CopyAtAction.kt =================================================================== --- src/main/kotlin/platform/mcp/actions/CopyAtAction.kt (revision 7f1fc627d3b4ab54b567c008133d9c5aa4fae7a9) +++ src/main/kotlin/platform/mcp/actions/CopyAtAction.kt (revision 1d88fee8c10f5f8387a006a90fa507492138cc72) @@ -24,6 +24,8 @@ import com.demonwav.mcdev.util.ActionData import com.demonwav.mcdev.util.descriptor import com.demonwav.mcdev.util.fullQualifiedName +import com.demonwav.mcdev.util.showBalloon +import com.demonwav.mcdev.util.showSuccessBalloon import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.editor.Editor import com.intellij.psi.PsiClass @@ -38,7 +40,10 @@ if (srgMap == null) { when (parent) { is PsiField -> { - val className = parent.containingClass?.fullQualifiedName ?: return showBalloon("No containing class found", e) + val className = parent.containingClass?.fullQualifiedName ?: return showBalloon( + e, + "No containing class found" + ) copyToClipboard( data.editor, data.element, @@ -46,7 +51,10 @@ ) } is PsiMethod -> { - val className = parent.containingClass?.fullQualifiedName ?: return showBalloon("No containing class found", e) + val className = parent.containingClass?.fullQualifiedName ?: return showBalloon( + e, + "No containing class found" + ) copyToClipboard( data.editor, data.element, @@ -54,7 +62,7 @@ ) } is PsiClass -> { - val className = parent.fullQualifiedName ?: return showBalloon("Could not get FQN", e) + val className = parent.fullQualifiedName ?: return showBalloon(e, "Could not get FQN") copyToClipboard( data.editor, data.element, @@ -65,9 +73,9 @@ } else { when (parent) { is PsiField -> { - val containing = parent.containingClass ?: return showBalloon("No SRG name found", e) - val classSrg = srgMap.getIntermediaryClass(containing) ?: return showBalloon("No SRG name found", e) - val srg = srgMap.getIntermediaryField(parent) ?: return showBalloon("No SRG name found", e) + val containing = parent.containingClass ?: return showBalloon(e, "No SRG name found") + val classSrg = srgMap.getIntermediaryClass(containing) ?: return showBalloon(e, "No SRG name found") + val srg = srgMap.getIntermediaryField(parent) ?: return showBalloon(e, "No SRG name found") copyToClipboard( data.editor, data.element, @@ -76,9 +84,9 @@ } is PsiMethod -> { - val containing = parent.containingClass ?: return showBalloon("No SRG name found", e) - val classSrg = srgMap.getIntermediaryClass(containing) ?: return showBalloon("No SRG name found", e) - val srg = srgMap.getIntermediaryMethod(parent) ?: return showBalloon("No SRG name found", e) + val containing = parent.containingClass ?: return showBalloon(e, "No SRG name found") + val classSrg = srgMap.getIntermediaryClass(containing) ?: return showBalloon(e, "No SRG name found") + val srg = srgMap.getIntermediaryMethod(parent) ?: return showBalloon(e, "No SRG name found") copyToClipboard( data.editor, data.element, @@ -88,11 +96,11 @@ is PsiClass -> { val classMcpToSrg = - srgMap.getIntermediaryClass(parent) ?: return showBalloon("No SRG name found", e) + srgMap.getIntermediaryClass(parent) ?: return showBalloon(e, "No SRG name found") copyToClipboard(data.editor, data.element, classMcpToSrg) } - else -> showBalloon("Not a valid element", e) + else -> showBalloon(e, "Not a valid element") } } } Index: src/main/kotlin/platform/mcp/actions/CopyAwAction.kt =================================================================== --- src/main/kotlin/platform/mcp/actions/CopyAwAction.kt (revision 7f1fc627d3b4ab54b567c008133d9c5aa4fae7a9) +++ src/main/kotlin/platform/mcp/actions/CopyAwAction.kt (revision 1d88fee8c10f5f8387a006a90fa507492138cc72) @@ -20,11 +20,11 @@ package com.demonwav.mcdev.platform.mcp.actions -import com.demonwav.mcdev.platform.mcp.actions.SrgActionBase.Companion.showBalloon -import com.demonwav.mcdev.platform.mcp.actions.SrgActionBase.Companion.showSuccessBalloon import com.demonwav.mcdev.util.descriptor import com.demonwav.mcdev.util.getDataFromActionEvent import com.demonwav.mcdev.util.internalName +import com.demonwav.mcdev.util.showBalloon +import com.demonwav.mcdev.util.showSuccessBalloon import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.editor.Editor @@ -41,12 +41,12 @@ class CopyAwAction : AnAction() { override fun actionPerformed(e: AnActionEvent) { - val data = getDataFromActionEvent(e) ?: return showBalloon("Unknown failure", e) + val data = getDataFromActionEvent(e) ?: return showBalloon(e, "Unknown failure") val editor = data.editor val element = data.element if (element !is PsiIdentifier) { - showBalloon("Invalid element", e) + showBalloon(e, "Invalid element") return } @@ -54,7 +54,7 @@ is PsiMember -> parent is PsiReference -> parent.resolve() else -> null - } ?: return showBalloon("Invalid element", e) + } ?: return showBalloon(e, "Invalid element") doCopy(target, element, editor, e) } @@ -69,19 +69,19 @@ } is PsiField -> { val containing = target.containingClass?.internalName - ?: return maybeShow("Could not get owner of field", e) + ?: return maybeShow(e, "Could not get owner of field") val desc = target.type.descriptor val text = "accessible field $containing ${target.name} $desc" copyToClipboard(editor, element, text) } is PsiMethod -> { val containing = target.containingClass?.internalName - ?: return maybeShow("Could not get owner of method", e) - val desc = target.descriptor ?: return maybeShow("Could not get descriptor of method", e) + ?: return maybeShow(e, "Could not get owner of method") + val desc = target.descriptor ?: return maybeShow(e, "Could not get descriptor of method") val text = "accessible method $containing ${target.internalName} $desc" copyToClipboard(editor, element, text) } - else -> maybeShow("Invalid element", e) + else -> maybeShow(e, "Invalid element") } } @@ -94,9 +94,9 @@ } } - private fun maybeShow(text: String, e: AnActionEvent?) { + private fun maybeShow(e: AnActionEvent?, text: String) { if (e != null) { - showBalloon(text, e) + showBalloon(e, text) } } } Index: src/main/kotlin/platform/mcp/actions/CopyCoremodTargetAction.kt =================================================================== --- src/main/kotlin/platform/mcp/actions/CopyCoremodTargetAction.kt (revision 7f1fc627d3b4ab54b567c008133d9c5aa4fae7a9) +++ src/main/kotlin/platform/mcp/actions/CopyCoremodTargetAction.kt (revision 1d88fee8c10f5f8387a006a90fa507492138cc72) @@ -24,6 +24,8 @@ import com.demonwav.mcdev.util.ActionData import com.demonwav.mcdev.util.descriptor import com.demonwav.mcdev.util.fullQualifiedName +import com.demonwav.mcdev.util.showBalloon +import com.demonwav.mcdev.util.showSuccessBalloon import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.editor.Editor import com.intellij.psi.PsiClass @@ -39,8 +41,11 @@ override fun withSrgTarget(parent: PsiElement, srgMap: Mappings?, e: AnActionEvent, data: ActionData) { when (parent) { is PsiField -> { - val containing = parent.containingClass ?: return showBalloon("No containing class", e) - val classSrg = srgMap?.getIntermediaryClass(containing) ?: containing.fullQualifiedName ?: return showBalloon("No containing class found", e) + val containing = parent.containingClass ?: return showBalloon(e, "No containing class") + val classSrg = srgMap?.getIntermediaryClass(containing) ?: containing.fullQualifiedName ?: return showBalloon( + e, + "No containing class found" + ) val srg = srgMap?.getIntermediaryField(parent)?.name ?: parent.name copyToClipboard( data.editor, @@ -51,13 +56,16 @@ ) } is PsiMethod -> { - val containing = parent.containingClass ?: return showBalloon("No containing class", e) - val classSrg = srgMap?.getIntermediaryClass(containing) ?: containing.fullQualifiedName ?: return showBalloon("No containing class found", e) + val containing = parent.containingClass ?: return showBalloon(e, "No containing class") + val classSrg = srgMap?.getIntermediaryClass(containing) ?: containing.fullQualifiedName ?: return showBalloon( + e, + "No containing class found" + ) val (srgName, srgDescriptor) = srgMap?.getIntermediaryMethod(parent)?.let { it.name to it.descriptor } ?: (parent.name to parent.descriptor) if (srgDescriptor == null) { - return showBalloon("No method descriptor found", e) + return showBalloon(e, "No method descriptor found") } copyToClipboard( data.editor, @@ -69,7 +77,10 @@ ) } is PsiClass -> { - val classSrg = srgMap?.getIntermediaryClass(parent) ?: parent.fullQualifiedName ?: return showBalloon("No FQN found", e) + val classSrg = srgMap?.getIntermediaryClass(parent) ?: parent.fullQualifiedName ?: return showBalloon( + e, + "No FQN found" + ) copyToClipboard( data.editor, data.element, @@ -77,7 +88,7 @@ Pair("name", classSrg), ) } - else -> showBalloon("Not a valid element", e) + else -> showBalloon(e, "Not a valid element") } } Index: src/main/kotlin/platform/mcp/actions/FindSrgMappingAction.kt =================================================================== --- src/main/kotlin/platform/mcp/actions/FindSrgMappingAction.kt (revision 7f1fc627d3b4ab54b567c008133d9c5aa4fae7a9) +++ src/main/kotlin/platform/mcp/actions/FindSrgMappingAction.kt (revision 1d88fee8c10f5f8387a006a90fa507492138cc72) @@ -22,6 +22,8 @@ import com.demonwav.mcdev.platform.mcp.mappings.Mappings import com.demonwav.mcdev.util.ActionData +import com.demonwav.mcdev.util.showBalloon +import com.demonwav.mcdev.util.showSuccessBalloon import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement @@ -32,23 +34,23 @@ override fun withSrgTarget(parent: PsiElement, srgMap: Mappings?, e: AnActionEvent, data: ActionData) { if (srgMap == null) { - return showBalloon("No mappings found", e) + return showBalloon(e, "No mappings found") } when (parent) { is PsiField -> { - val srg = srgMap.getIntermediaryField(parent) ?: return showBalloon("No SRG name found", e) + val srg = srgMap.getIntermediaryField(parent) ?: return showBalloon(e, "No SRG name found") showSuccessBalloon(data.editor, data.element, "SRG name: " + srg.name) } is PsiMethod -> { - val srg = srgMap.getIntermediaryMethod(parent) ?: return showBalloon("No SRG name found", e) + val srg = srgMap.getIntermediaryMethod(parent) ?: return showBalloon(e, "No SRG name found") showSuccessBalloon(data.editor, data.element, "SRG name: " + srg.name + srg.descriptor) } is PsiClass -> { - val classMcpToSrg = srgMap.getIntermediaryClass(parent) ?: return showBalloon("No SRG name found", e) + val classMcpToSrg = srgMap.getIntermediaryClass(parent) ?: return showBalloon(e, "No SRG name found") showSuccessBalloon(data.editor, data.element, "SRG name: $classMcpToSrg") } - else -> showBalloon("Not a valid element", e) + else -> showBalloon(e, "Not a valid element") } } } Index: src/main/kotlin/platform/mcp/actions/GotoAtEntryAction.kt =================================================================== --- src/main/kotlin/platform/mcp/actions/GotoAtEntryAction.kt (revision 7f1fc627d3b4ab54b567c008133d9c5aa4fae7a9) +++ src/main/kotlin/platform/mcp/actions/GotoAtEntryAction.kt (revision 1d88fee8c10f5f8387a006a90fa507492138cc72) @@ -28,6 +28,7 @@ import com.demonwav.mcdev.util.getDataFromActionEvent import com.demonwav.mcdev.util.gotoTargetElement import com.demonwav.mcdev.util.qualifiedMemberReference +import com.demonwav.mcdev.util.showBalloon import com.demonwav.mcdev.util.simpleQualifiedMemberReference import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent @@ -111,6 +112,6 @@ } private fun showBalloon(e: AnActionEvent) { - SrgActionBase.showBalloon("No access transformer entry found", e) + showBalloon(e, "No access transformer entry found") } } Index: src/main/kotlin/platform/mcp/actions/SrgActionBase.kt =================================================================== --- src/main/kotlin/platform/mcp/actions/SrgActionBase.kt (revision 7f1fc627d3b4ab54b567c008133d9c5aa4fae7a9) +++ src/main/kotlin/platform/mcp/actions/SrgActionBase.kt (revision 1d88fee8c10f5f8387a006a90fa507492138cc72) @@ -25,31 +25,21 @@ import com.demonwav.mcdev.platform.mixin.handlers.ShadowHandler import com.demonwav.mcdev.util.ActionData import com.demonwav.mcdev.util.getDataFromActionEvent -import com.demonwav.mcdev.util.invokeLater +import com.demonwav.mcdev.util.showBalloon import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.editor.VisualPosition -import com.intellij.openapi.ui.popup.Balloon -import com.intellij.openapi.ui.popup.JBPopupFactory -import com.intellij.openapi.wm.WindowManager import com.intellij.psi.PsiElement import com.intellij.psi.PsiIdentifier import com.intellij.psi.PsiMember import com.intellij.psi.PsiReference -import com.intellij.psi.createSmartPointer -import com.intellij.ui.LightColors -import com.intellij.ui.awt.RelativePoint -import java.awt.Point -import javax.swing.JComponent abstract class SrgActionBase : AnAction() { override fun actionPerformed(e: AnActionEvent) { - val data = getDataFromActionEvent(e) ?: return showBalloon("Unknown failure", e) + val data = getDataFromActionEvent(e) ?: return showBalloon(e, "Unknown failure") if (data.element !is PsiIdentifier) { - showBalloon("Not a valid element", e) + showBalloon(e, "Not a valid element") return } @@ -59,7 +49,7 @@ mappingsManager.mappings.onSuccess { srgMap -> performWithMappings(e, data, data.element, srgMap) }.onError { - showBalloon(it.message ?: "No MCP data available", e) + showBalloon(e, it.message ?: "No MCP data available") } } @@ -69,7 +59,7 @@ element: PsiIdentifier, srgMap: Mappings?, ) { - var parent = element.parent ?: return showBalloon("Not a valid element", e) + var parent = element.parent ?: return showBalloon(e, "Not a valid element") if (parent is PsiMember) { val shadowTarget = ShadowHandler.getInstance()?.findFirstShadowTargetForReference(parent)?.element @@ -79,73 +69,11 @@ } if (parent is PsiReference) { - parent = parent.resolve() ?: return showBalloon("Not a valid element", e) + parent = parent.resolve() ?: return showBalloon(e, "Not a valid element") } withSrgTarget(parent, srgMap, e, data) } abstract fun withSrgTarget(parent: PsiElement, srgMap: Mappings?, e: AnActionEvent, data: ActionData) - - companion object { - fun showBalloon(message: String, e: AnActionEvent) { - val balloon = JBPopupFactory.getInstance() - .createHtmlTextBalloonBuilder(message, null, LightColors.YELLOW, null) - .setHideOnAction(true) - .setHideOnClickOutside(true) - .setHideOnKeyOutside(true) - .createBalloon() - - val project = e.project ?: return - - val elementPointer = getDataFromActionEvent(e)?.element?.createSmartPointer() - val editor = getDataFromActionEvent(e)?.editor - invokeLater { - val element = elementPointer?.element - if (element != null && editor != null) { - val pos = editor.offsetToVisualPosition(element.textRange.endOffset - element.textLength / 2) - val at = RelativePoint( - editor.contentComponent, - editor.visualPositionToXY(VisualPosition(pos.line + 1, pos.column)), - ) - balloon.show(at, Balloon.Position.below) - return@invokeLater - } +} - - val statusBar = WindowManager.getInstance().getStatusBar(project) - val statusBarComponent = statusBar.component - if (statusBarComponent != null) { - balloon.show(RelativePoint.getCenterOf(statusBarComponent), Balloon.Position.below) - return@invokeLater - } - - val focused = WindowManager.getInstance().getFocusedComponent(project) - if (focused is JComponent) { - balloon.show(RelativePoint.getCenterOf(focused), Balloon.Position.below) - return@invokeLater - } - - balloon.show(RelativePoint.fromScreen(Point()), Balloon.Position.below) - } - } - - fun showSuccessBalloon(editor: Editor, element: PsiElement, text: String) { - val balloon = JBPopupFactory.getInstance() - .createHtmlTextBalloonBuilder(text, null, LightColors.SLIGHTLY_GREEN, null) - .setHideOnAction(true) - .setHideOnClickOutside(true) - .setHideOnKeyOutside(true) - .createBalloon() - - invokeLater { - val pos = editor.offsetToVisualPosition(element.textRange.endOffset - element.textLength / 2) - val at = RelativePoint( - editor.contentComponent, - editor.visualPositionToXY(VisualPosition(pos.line + 1, pos.column)), - ) - - balloon.show(at, Balloon.Position.below) - } - } - } -} Index: src/main/kotlin/translations/TranslationFiles.kt =================================================================== --- src/main/kotlin/translations/TranslationFiles.kt (revision 7f1fc627d3b4ab54b567c008133d9c5aa4fae7a9) +++ src/main/kotlin/translations/TranslationFiles.kt (revision 1d88fee8c10f5f8387a006a90fa507492138cc72) @@ -112,19 +112,19 @@ element.delete() } - fun findTranslationKeyForText(context: PsiElement, text: String): String? { + fun findTranslationKeyForText(context: PsiElement, text: String): Result { val module = context.findModule() - ?: throw IllegalArgumentException("Cannot add translation for element outside of module") + ?: return Result.failure(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") + context.mcVersion ?: return Result.failure(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 + return Result.success(null) } val files = FileTypeIndex.getFiles( @@ -134,19 +134,19 @@ for (file in files) { val psiFile = PsiManager.getInstance(context.project).findFile(file) ?: continue - psiFile.findKeyForTextAsJson(text)?.let { return it } + psiFile.findKeyForTextAsJson(text)?.let { return Result.success(it) } } - return null + return Result.success(null) } - fun add(context: PsiElement, key: String, text: String) { + fun add(context: PsiElement, key: String, text: String): Result { val module = context.findModule() - ?: throw IllegalArgumentException("Cannot add translation for element outside of module") + ?: return Result.failure(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") + context.mcVersion ?: return Result.failure(IllegalArgumentException("Cannot determine MC version for element $context")) jsonVersion = version > MC_1_12_2 } @@ -187,6 +187,8 @@ } else { write(files) } + + return Result.success(Unit) } fun addAll(file: PsiFile, entries: Iterable) { @@ -323,13 +325,13 @@ return gatherLangComments(prevLine, maxDepth, acc, depth + 1) } - fun buildSortingTemplateFromDefault(context: PsiElement, domain: String? = null): Template? { + fun buildSortingTemplateFromDefault(context: PsiElement, domain: String? = null): Result { val module = context.findModule() - ?: throw IllegalArgumentException("Cannot add translation for element outside of module") + ?: return Result.failure(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") + context.mcVersion ?: return Result.failure(IllegalArgumentException("Cannot determine MC version for element $context")) jsonVersion = version > MC_1_12_2 } @@ -342,8 +344,8 @@ .asSequence() .filter { domain == null || it.mcDomain == domain } .filter { (jsonVersion && it.fileType == JsonFileType.INSTANCE) || it.fileType == LangFileType } - .firstOrNull() ?: return null - val psi = PsiManager.getInstance(context.project).findFile(defaultTranslationFile) ?: return null + .firstOrNull() ?: return Result.success(null) + val psi = PsiManager.getInstance(context.project).findFile(defaultTranslationFile) ?: return Result.success(null) val elements = mutableListOf() if (psi is LangFile) { @@ -357,7 +359,7 @@ } } } else { - val rootObject = psi.firstChild as? JsonObject ?: return null + val rootObject = psi.firstChild as? JsonObject ?: return Result.success(null) var child: PsiElement? = rootObject.firstChild while (child != null) { when (child) { @@ -372,7 +374,7 @@ child = child.nextSibling } } - return Template(elements) + return Result.success(Template(elements)) } sealed class FileEntry { Index: src/main/kotlin/translations/inspections/NoTranslationInspection.kt =================================================================== --- src/main/kotlin/translations/inspections/NoTranslationInspection.kt (revision 7f1fc627d3b4ab54b567c008133d9c5aa4fae7a9) +++ src/main/kotlin/translations/inspections/NoTranslationInspection.kt (revision 1d88fee8c10f5f8387a006a90fa507492138cc72) @@ -22,6 +22,7 @@ import com.demonwav.mcdev.translations.TranslationFiles import com.demonwav.mcdev.translations.identification.TranslationIdentifier +import com.demonwav.mcdev.util.showBalloon import com.intellij.codeInspection.LocalQuickFix import com.intellij.codeInspection.ProblemDescriptor import com.intellij.codeInspection.ProblemsHolder @@ -70,7 +71,8 @@ override fun applyFix(project: Project, descriptor: ProblemDescriptor) { try { - val literal = descriptor.psiElement.toUElementOfType() ?: return + val element = descriptor.psiElement + val literal = element.toUElementOfType() ?: return val translation = TranslationIdentifier.identify(literal) val literalValue = literal.value as String val key = translation?.key?.copy(infix = literalValue)?.full ?: literalValue @@ -81,8 +83,10 @@ Messages.getQuestionIcon(), ) if (result != null) { - TranslationFiles.add(literal.sourcePsi!!, key, result) + TranslationFiles.add(literal.sourcePsi!!, key, result).onFailure { + return showBalloon(project, null, element, it.message) - } + } + } } catch (_: IncorrectOperationException) { } catch (e: Exception) { Notification( Index: src/main/kotlin/translations/intentions/ConvertToTranslationIntention.kt =================================================================== --- src/main/kotlin/translations/intentions/ConvertToTranslationIntention.kt (revision 7f1fc627d3b4ab54b567c008133d9c5aa4fae7a9) +++ src/main/kotlin/translations/intentions/ConvertToTranslationIntention.kt (revision 1d88fee8c10f5f8387a006a90fa507492138cc72) @@ -25,6 +25,7 @@ import com.demonwav.mcdev.translations.TranslationFiles import com.demonwav.mcdev.util.findModule import com.demonwav.mcdev.util.runWriteAction +import com.demonwav.mcdev.util.showBalloon import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction import com.intellij.notification.Notification import com.intellij.notification.NotificationType @@ -49,7 +50,9 @@ val literal = element.parent.toUElementOfType() ?: return val value = literal.evaluateString() ?: return - val existingKey = TranslationFiles.findTranslationKeyForText(element, value) + val existingKey = TranslationFiles.findTranslationKeyForText(element, value).getOrElse { + return showBalloon(project, editor, element, it.message) + } val result = Messages.showInputDialogWithCheckBox( "Enter translation key:", @@ -83,8 +86,10 @@ val replaceLiteral = result.second try { if (existingKey != key) { - TranslationFiles.add(element, key, value) + TranslationFiles.add(element, key, value).onFailure { + return showBalloon(project, editor, element, it.message) - } + } + } if (replaceLiteral) { val translationSettings = TranslationSettings.getInstance(project) val documentManager = PsiDocumentManager.getInstance(project) Index: src/main/kotlin/translations/sorting/TranslationSorter.kt =================================================================== --- src/main/kotlin/translations/sorting/TranslationSorter.kt (revision 7f1fc627d3b4ab54b567c008133d9c5aa4fae7a9) +++ src/main/kotlin/translations/sorting/TranslationSorter.kt (revision 1d88fee8c10f5f8387a006a90fa507492138cc72) @@ -28,6 +28,7 @@ import com.demonwav.mcdev.util.lexicographical import com.demonwav.mcdev.util.mcDomain import com.demonwav.mcdev.util.runWriteAction +import com.demonwav.mcdev.util.showBalloon import com.intellij.openapi.project.Project import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiFile @@ -84,16 +85,20 @@ it, keepComments, ) - else -> sortByTemplate( + else -> { + val template = TranslationFiles.buildSortingTemplateFromDefault(file, domain).getOrElse { + return showBalloon(project, null, null, it.message) + } ?: return showBalloon(project, null, null, "Could not generate template from default translation file") + sortByTemplate( - project, - locale, + project, + locale, - TranslationFiles.buildSortingTemplateFromDefault(file, domain) - ?: throw IllegalStateException("Could not generate template from default translation file"), + template, - it, - keepComments, - ) - } - } + it, + keepComments, + ) + } + } + } file.runWriteAction { TranslationFiles.replaceAll(file, sorted.asIterable()) Index: src/main/kotlin/util/actions.kt =================================================================== --- src/main/kotlin/util/actions.kt (revision 7f1fc627d3b4ab54b567c008133d9c5aa4fae7a9) +++ src/main/kotlin/util/actions.kt (revision 1d88fee8c10f5f8387a006a90fa507492138cc72) @@ -23,12 +23,24 @@ import com.demonwav.mcdev.facet.MinecraftFacet import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.VisualPosition import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.module.ModuleUtil +import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ProjectFileIndex +import com.intellij.openapi.ui.popup.Balloon +import com.intellij.openapi.ui.popup.JBPopupFactory +import com.intellij.openapi.wm.WindowManager +import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile +import com.intellij.psi.createSmartPointer +import com.intellij.ui.LightColors +import com.intellij.ui.awt.RelativePoint +import java.awt.Point import java.util.Arrays +import javax.swing.JComponent fun getDataFromActionEvent(e: AnActionEvent): ActionData? { fun findModuleForLibrary(file: PsiFile): Module? { @@ -64,3 +76,72 @@ return ActionData(project, editor, file, element, caret, instance) } + +fun showBalloon(e: AnActionEvent, message: String) { + val project = e.project ?: return + val data = getDataFromActionEvent(e) + showBalloon(project, data?.editor, data?.element, message) +} + +fun showBalloon(project: Project, editor: Editor?, element: PsiElement?, message: String?) { + if (message == null) { + return + } + + val balloon = JBPopupFactory.getInstance() + .createHtmlTextBalloonBuilder(message, null, LightColors.YELLOW, null) + .setHideOnAction(true) + .setHideOnClickOutside(true) + .setHideOnKeyOutside(true) + .createBalloon() + + val elementPointer = element?.createSmartPointer() + invokeLater { + val element = elementPointer?.element + if (element != null && editor != null) { + val pos = editor.offsetToVisualPosition(element.textRange.endOffset - element.textLength / 2) + val at = RelativePoint( + editor.contentComponent, + editor.visualPositionToXY(VisualPosition(pos.line + 1, pos.column)), + ) + balloon.show(at, Balloon.Position.below) + return@invokeLater + } + + val statusBar = WindowManager.getInstance().getStatusBar(project) + val statusBarComponent = statusBar.component + if (statusBarComponent != null) { + balloon.show(RelativePoint.getCenterOf(statusBarComponent), Balloon.Position.below) + return@invokeLater + } + + val focused = WindowManager.getInstance().getFocusedComponent(project) + if (focused is JComponent) { + balloon.show(RelativePoint.getCenterOf(focused), Balloon.Position.below) + return@invokeLater + } + + balloon.show(RelativePoint.fromScreen(Point()), Balloon.Position.below) + } +} + +fun showSuccessBalloon(editor: Editor, element: PsiElement, text: String) { + val balloon = JBPopupFactory.getInstance() + .createHtmlTextBalloonBuilder(text, null, LightColors.SLIGHTLY_GREEN, null) + .setHideOnAction(true) + .setHideOnClickOutside(true) + .setHideOnKeyOutside(true) + .createBalloon() + + val elementPointer = element.createSmartPointer() + invokeLater { + val element = elementPointer.element ?: return@invokeLater + val pos = editor.offsetToVisualPosition(element.textRange.endOffset - element.textLength / 2) + val at = RelativePoint( + editor.contentComponent, + editor.visualPositionToXY(VisualPosition(pos.line + 1, pos.column)), + ) + + balloon.show(at, Balloon.Position.below) + } +}