<NME> ElmExternalFormatAction.kt
<BEF> package org.elm.ide.actions

import com.intellij.ide.DataManager
import com.intellij.notification.NotificationType
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.elm.ide.notifications.executeAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.elm.ide.notifications.showBalloon
import org.elm.lang.core.ElmFileType
import org.elm.lang.core.psi.ElmFile
import org.elm.lang.core.psi.isElmFile
import org.elm.openapiext.isUnitTestMode
import org.elm.workspace.ElmFormatCLI
import org.elm.workspace.compiler.ELM_BUILD_ACTION_ID
import org.elm.workspace.elmToolchain
import org.elm.workspace.elmWorkspace


class ElmExternalFormatAction : AnAction() {

    override fun update(e: AnActionEvent) {
        super.update(e)
        e.presentation.isEnabled = getContext(e) != null
    }

    override fun actionPerformed(e: AnActionEvent) {

        val elmFormat = e.project?.elmToolchain?.elmFormat
        if (elmFormat == null) {
            e.project?.showBalloon("could not find elm-format", NotificationType.ERROR)
            return
        }

        val ctx = getContext(e)

        if (ctx == null) {
            if (isUnitTestMode) error("something went wrong initializing the context for elm-format")
            return
        }

        val elmFormat = project.elmToolchain.elmFormatCLI
        if (elmFormat == null) {
        } catch (ex: ExecutionException) {
            if (isUnitTestMode) throw ex
            val message = ex.message ?: "something went wrong running elm-format"
            e.project?.showBalloon(message, NotificationType.ERROR)

        }
    }

                project.showBalloon(result.msg, NotificationType.WARNING, "Show Errors" to {
                    val action = ActionManager.getInstance().getAction(ELM_BUILD_ACTION_ID)!!
                    executeAction(action, "elm-format-notif", DataManager.getInstance().getDataContext(editor.component))
                })
            }

            is ElmFormatResult.FailedToStart ->
                project.showBalloon(result.msg, NotificationType.ERROR, configureFixAction)

            is ElmFormatResult.UnknownFailure ->
                project.showBalloon(result.msg, NotificationType.ERROR)

            is ElmFormatResult.Success ->
                return
        }
    }

    private fun getContext(e: AnActionEvent): Context? {
        val project = e.project ?: return null
        val file = e.getData(CommonDataKeys.VIRTUAL_FILE) ?: return null
        if (!file.isInLocalFileSystem) return null
        if (!file.isElmFile) return null
        val editor = e.getData(PlatformDataKeys.EDITOR) ?: return null
        val elmVersion = ElmFormatCLI.getElmVersion(project, file) ?: return null
        return Context(project, file, editor, elmVersion)
    }

    data class Context(
            val project: Project,
            val file: VirtualFile,
            val editor: Editor,
            val elmVersion: Version
    )

    companion object {
        const val ID = "Elm.RunExternalElmFormat" // must stay in-sync with `plugin.xml`
    }
}

<MSG> Fix "cannot share data context between Swing events" bug (#291)

Using the `project` accessor on `AnActionEvent` uses `DataContext`
under-the-hood, which represents input focus. If the input focus changes,
and you try to use the old data context, IntelliJ will throw an exception.

A JetBrains engineer explains it here:
https://intellij-support.jetbrains.com/hc/en-us/community/posts/207379815/comments/207186065

And they recommend that you collect all of the data you need up-front
(at the beginning of `actionPerformed`). So that's what we do now.
<DFF> @@ -10,8 +10,6 @@ import com.intellij.openapi.fileEditor.FileDocumentManager
 import com.intellij.openapi.project.Project
 import com.intellij.openapi.vfs.VirtualFile
 import org.elm.ide.notifications.showBalloon
-import org.elm.lang.core.ElmFileType
-import org.elm.lang.core.psi.ElmFile
 import org.elm.lang.core.psi.isElmFile
 import org.elm.openapiext.isUnitTestMode
 import org.elm.workspace.ElmFormatCLI
@@ -28,17 +26,15 @@ class ElmExternalFormatAction : AnAction() {
     }
 
     override fun actionPerformed(e: AnActionEvent) {
-
-        val elmFormat = e.project?.elmToolchain?.elmFormat
-        if (elmFormat == null) {
-            e.project?.showBalloon("could not find elm-format", NotificationType.ERROR)
+        val ctx = getContext(e)
+        if (ctx == null) {
+            if (isUnitTestMode) error("should not happen: context is null!")
             return
         }
 
-        val ctx = getContext(e)
-
-        if (ctx == null) {
-            if (isUnitTestMode) error("something went wrong initializing the context for elm-format")
+        val elmFormat = ctx.project.elmToolchain?.elmFormat
+        if (elmFormat == null) {
+            ctx.project.showBalloon("could not find elm-format", NotificationType.ERROR)
             return
         }
 
@@ -47,8 +43,7 @@ class ElmExternalFormatAction : AnAction() {
         } catch (ex: ExecutionException) {
             if (isUnitTestMode) throw ex
             val message = ex.message ?: "something went wrong running elm-format"
-            e.project?.showBalloon(message, NotificationType.ERROR)
-
+            ctx.project.showBalloon(message, NotificationType.ERROR)
         }
     }
 

Fix "cannot share data context between Swing events" bug (#291)