From 4bebb9fd5f0ec5bb2d594426a332882bbef2cc99 Mon Sep 17 00:00:00 2001 From: Ilya Muromtsev Date: Thu, 30 Apr 2026 15:12:49 +0300 Subject: [PATCH 1/3] fix: EDT while navigation https://github.com/explyt/spring-plugin/issues/225 https://github.com/explyt/spring-plugin/issues/226 --- .../explyt/spring/web/view/EndpointsToolWindow.kt | 6 ++++-- .../spring/web/view/nodes/EndpointNavigatable.kt | 14 ++++++++++++-- .../explyt/spring/web/view/nodes/HttpMethodNode.kt | 7 +++---- .../spring/web/view/nodes/SpringBootClassNode.kt | 7 +++---- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/EndpointsToolWindow.kt b/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/EndpointsToolWindow.kt index 1d2200329..c99f03eb8 100644 --- a/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/EndpointsToolWindow.kt +++ b/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/EndpointsToolWindow.kt @@ -35,7 +35,7 @@ import com.intellij.openapi.actionSystem.impl.ActionButton import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ModalityState import com.intellij.openapi.application.ReadAction -import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.application.runReadActionBlocking import com.intellij.openapi.project.Project import com.intellij.openapi.ui.SimpleToolWindowPanel import com.intellij.psi.PsiElement @@ -149,7 +149,9 @@ class EndpointsToolWindow(private val project: Project) : private fun navigation(closestPathForLocation: TreePath?) { val lastUserObject = TreeUtil.getLastUserObject(closestPathForLocation) - runReadAction { (lastUserObject as? EndpointNavigable)?.navigate() } + val navigable = (lastUserObject as? EndpointNavigable) ?: return + val navigatable = runReadActionBlocking { navigable.asNavigatable() } ?: return + navigatable.navigate(true) } private fun createRefreshButton(): JComponent { diff --git a/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/EndpointNavigatable.kt b/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/EndpointNavigatable.kt index 5f7140e7b..885ab3209 100644 --- a/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/EndpointNavigatable.kt +++ b/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/EndpointNavigatable.kt @@ -17,6 +17,16 @@ package com.explyt.spring.web.view.nodes +import com.intellij.pom.Navigatable + interface EndpointNavigable { - fun navigate() -} \ No newline at end of file + /** + * Resolves a [Navigatable] for this node. Must be called inside a read action + * because it touches PSI. The returned [Navigatable.navigate] call must be + * performed outside the read action because the platform now requires a + * write-intent read action when opening file editors + * + * Since 253 + */ + fun asNavigatable(): Navigatable? +} diff --git a/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/HttpMethodNode.kt b/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/HttpMethodNode.kt index e2867cb93..5bbdba93a 100644 --- a/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/HttpMethodNode.kt +++ b/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/HttpMethodNode.kt @@ -53,7 +53,6 @@ class HttpMethodNode( override fun buildChildren() = emptyArray() - override fun navigate() { - (httpElement.psiPointer.element?.navigationElement as? Navigatable)?.navigate(true) - } -} \ No newline at end of file + override fun asNavigatable(): Navigatable? = + httpElement.psiPointer.element?.navigationElement as? Navigatable +} diff --git a/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/SpringBootClassNode.kt b/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/SpringBootClassNode.kt index e9bacd638..b94f28d49 100644 --- a/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/SpringBootClassNode.kt +++ b/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/SpringBootClassNode.kt @@ -37,7 +37,6 @@ class SpringBootClassNode( override fun buildChildren() = emptyArray() - override fun navigate() { - (viewData?.psiPointer?.element?.navigationElement as? Navigatable)?.navigate(true) - } -} \ No newline at end of file + override fun asNavigatable(): Navigatable? = + viewData?.psiPointer?.element?.navigationElement as? Navigatable +} From 53ecd66c1d8aaf37e31b33a2c748c7e4ce7421c2 Mon Sep 17 00:00:00 2001 From: Ilya Muromtsev Date: Thu, 30 Apr 2026 16:16:08 +0300 Subject: [PATCH 2/3] fix: EDT while navigation https://github.com/explyt/spring-plugin/issues/213 --- .../explyt/spring/web/view/EndpointsToolWindow.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/EndpointsToolWindow.kt b/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/EndpointsToolWindow.kt index c99f03eb8..3925a81e2 100644 --- a/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/EndpointsToolWindow.kt +++ b/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/EndpointsToolWindow.kt @@ -35,7 +35,7 @@ import com.intellij.openapi.actionSystem.impl.ActionButton import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ModalityState import com.intellij.openapi.application.ReadAction -import com.intellij.openapi.application.runReadActionBlocking +import com.intellij.openapi.application.WriteIntentReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.ui.SimpleToolWindowPanel import com.intellij.psi.PsiElement @@ -147,11 +147,18 @@ class EndpointsToolWindow(private val project: Project) : }.installOn(endpointTree) } + @Suppress("UnstableApiUsage") private fun navigation(closestPathForLocation: TreePath?) { val lastUserObject = TreeUtil.getLastUserObject(closestPathForLocation) val navigable = (lastUserObject as? EndpointNavigable) ?: return - val navigatable = runReadActionBlocking { navigable.asNavigatable() } ?: return - navigatable.navigate(true) + // Both PSI access and opening a file editor need to happen under a + // write-intent read action on the EDT (IJPL platform threading model). + // A plain ReadAction is not sufficient ("Read access is allowed from + // inside read-action only" / "WriteIntentReadAction can not be called + // from ReadAction"). + WriteIntentReadAction.run { + navigable.asNavigatable()?.navigate(true) ?: return@run + } } private fun createRefreshButton(): JComponent { From a2a43474f3987062e8399d27da70455a31727888 Mon Sep 17 00:00:00 2001 From: Ilya Muromtsev Date: Thu, 30 Apr 2026 16:27:37 +0300 Subject: [PATCH 3/3] fix: Revert to invokeLater --- .../spring/web/view/EndpointsToolWindow.kt | 14 ++++---------- .../spring/web/view/nodes/EndpointNavigatable.kt | 16 ++++++++-------- .../spring/web/view/nodes/HttpMethodNode.kt | 5 +++-- .../spring/web/view/nodes/SpringBootClassNode.kt | 5 +++-- 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/EndpointsToolWindow.kt b/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/EndpointsToolWindow.kt index 3925a81e2..a2fa9a96b 100644 --- a/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/EndpointsToolWindow.kt +++ b/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/EndpointsToolWindow.kt @@ -35,7 +35,6 @@ import com.intellij.openapi.actionSystem.impl.ActionButton import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ModalityState import com.intellij.openapi.application.ReadAction -import com.intellij.openapi.application.WriteIntentReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.ui.SimpleToolWindowPanel import com.intellij.psi.PsiElement @@ -147,18 +146,13 @@ class EndpointsToolWindow(private val project: Project) : }.installOn(endpointTree) } - @Suppress("UnstableApiUsage") private fun navigation(closestPathForLocation: TreePath?) { val lastUserObject = TreeUtil.getLastUserObject(closestPathForLocation) val navigable = (lastUserObject as? EndpointNavigable) ?: return - // Both PSI access and opening a file editor need to happen under a - // write-intent read action on the EDT (IJPL platform threading model). - // A plain ReadAction is not sufficient ("Read access is allowed from - // inside read-action only" / "WriteIntentReadAction can not be called - // from ReadAction"). - WriteIntentReadAction.run { - navigable.asNavigatable()?.navigate(true) ?: return@run - } + // Schedule navigation on the EDT: invokeLater runs the action under a + // write-intent read action, which is required since 253 to open a file + // editor and at the same time covers PSI read access inside navigate(). + ApplicationManager.getApplication().invokeLater { navigable.navigate() } } private fun createRefreshButton(): JComponent { diff --git a/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/EndpointNavigatable.kt b/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/EndpointNavigatable.kt index 885ab3209..6df925bdb 100644 --- a/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/EndpointNavigatable.kt +++ b/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/EndpointNavigatable.kt @@ -17,16 +17,16 @@ package com.explyt.spring.web.view.nodes -import com.intellij.pom.Navigatable - interface EndpointNavigable { /** - * Resolves a [Navigatable] for this node. Must be called inside a read action - * because it touches PSI. The returned [Navigatable.navigate] call must be - * performed outside the read action because the platform now requires a - * write-intent read action when opening file editors + * Performs navigation to the underlying PSI element. * - * Since 253 + * Implementations access PSI and open a file editor, both of which require + * the EDT under a write-intent read action. Callers must invoke this from + * the EDT (e.g. via [com.intellij.openapi.application.Application.invokeLater]), + * not from a plain read action — opening an editor cannot be performed + * from inside a `ReadAction` ("WriteIntentReadAction can not be called + * from ReadAction", since 253). */ - fun asNavigatable(): Navigatable? + fun navigate() } diff --git a/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/HttpMethodNode.kt b/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/HttpMethodNode.kt index 5bbdba93a..c8b134712 100644 --- a/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/HttpMethodNode.kt +++ b/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/HttpMethodNode.kt @@ -53,6 +53,7 @@ class HttpMethodNode( override fun buildChildren() = emptyArray() - override fun asNavigatable(): Navigatable? = - httpElement.psiPointer.element?.navigationElement as? Navigatable + override fun navigate() { + (httpElement.psiPointer.element?.navigationElement as? Navigatable)?.navigate(true) + } } diff --git a/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/SpringBootClassNode.kt b/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/SpringBootClassNode.kt index b94f28d49..35883f5b7 100644 --- a/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/SpringBootClassNode.kt +++ b/modules/spring-web/src/main/kotlin/com/explyt/spring/web/view/nodes/SpringBootClassNode.kt @@ -37,6 +37,7 @@ class SpringBootClassNode( override fun buildChildren() = emptyArray() - override fun asNavigatable(): Navigatable? = - viewData?.psiPointer?.element?.navigationElement as? Navigatable + override fun navigate() { + (viewData?.psiPointer?.element?.navigationElement as? Navigatable)?.navigate(true) + } }