From d2e12a600f718652080d5a88eae0bee25d8f34d4 Mon Sep 17 00:00:00 2001 From: Victor Rubezhny Date: Fri, 31 Jul 2026 23:24:46 +0200 Subject: [PATCH] fix: wizard UI stops updating when opened as modal dialog (CRW-12139) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The server selection and workspace selection wizard pages use EDT dispatch (invokeLater, runInEdt, withContext(Dispatchers.EDT)) to update UI components — cluster dropdown, DevWorkspace list, button states, and workspace status icons. These calls default to ModalityState.nonModal(), which defers execution until the modal dialog closes. This causes the UI to freeze when the wizard runs as a modal dialog (File -> Remote Development -> Connect to Dev Spaces), while working fine in non-modal mode (Run Plugin task). Use ModalityState.any() for all EDT dispatch in the wizard steps and the DevWorkspaceWatcher so that UI updates execute regardless of the current modal context. Also move the blocking createFilter(namespace) call in DevWorkspaceWatcher out of the EDT context to avoid freezing the UI on ADDED events, and dispatch refreshDevWorkspace model writes to EDT to prevent races with watch callbacks. Fixes: https://redhat.atlassian.net/browse/CRW-12139 Signed-off-by: Victor Rubezhny Assisted-By: Claude Opus 4.6 --- .../devworkspace/DevWorkspaceWatcher.kt | 12 ++- .../view/steps/DevSpacesServerStepView.kt | 29 +++--- .../view/steps/DevSpacesWorkspacesStepView.kt | 99 +++++++++++-------- 3 files changed, 79 insertions(+), 61 deletions(-) diff --git a/src/main/kotlin/com/redhat/devtools/gateway/devworkspace/DevWorkspaceWatcher.kt b/src/main/kotlin/com/redhat/devtools/gateway/devworkspace/DevWorkspaceWatcher.kt index fb6bb9ad..d78a08c9 100644 --- a/src/main/kotlin/com/redhat/devtools/gateway/devworkspace/DevWorkspaceWatcher.kt +++ b/src/main/kotlin/com/redhat/devtools/gateway/devworkspace/DevWorkspaceWatcher.kt @@ -12,6 +12,8 @@ package com.redhat.devtools.gateway.devworkspace import com.intellij.openapi.application.EDT +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.application.asContextElement import io.kubernetes.client.util.Watch import kotlinx.coroutines.* @@ -55,13 +57,13 @@ class DevWorkspaceWatcher( if (!scope.isActive || stopped) break val dw = DevWorkspace.from(event.`object`) - withContext(Dispatchers.EDT) { + if (event.type == "ADDED") { + matches = createFilter(namespace) + } + withContext(Dispatchers.EDT + ModalityState.any().asContextElement()) { if (stopped) return@withContext when (event.type) { - "ADDED" -> { - matches = createFilter(namespace) // Need this to make it read the new templates list - if(matches(dw)) listener.onAdded(dw) - } + "ADDED" -> if(matches(dw)) listener.onAdded(dw) "MODIFIED" -> if(matches(dw)) listener.onUpdated(dw) else listener.onDeleted(dw) "DELETED" -> listener.onDeleted(dw) } diff --git a/src/main/kotlin/com/redhat/devtools/gateway/view/steps/DevSpacesServerStepView.kt b/src/main/kotlin/com/redhat/devtools/gateway/view/steps/DevSpacesServerStepView.kt index 6a24e94f..7fd2f771 100644 --- a/src/main/kotlin/com/redhat/devtools/gateway/view/steps/DevSpacesServerStepView.kt +++ b/src/main/kotlin/com/redhat/devtools/gateway/view/steps/DevSpacesServerStepView.kt @@ -13,7 +13,9 @@ package com.redhat.devtools.gateway.view.steps import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.EDT import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.application.asContextElement import com.intellij.openapi.application.PathManager import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.thisLogger @@ -406,21 +408,18 @@ class DevSpacesServerStepView( val kubeConfigCurrentCluster = withContext(Dispatchers.IO) { KubeConfigUtils.getCurrentClusterName() } - ApplicationManager.getApplication().invokeLater( - { - if (disposed) return@invokeLater - currentContextClusterName = kubeConfigCurrentCluster - val previouslySelected = tfServer.selectedItem as? Cluster? - setClusters(updatedClusters) - setSelectedCluster( - (previouslySelected)?.name ?: kubeConfigCurrentCluster, - updatedClusters - ) - setSelectedAuthTab() - enableSaveConfigCheckbox() - }, - ModalityState.stateForComponent(component) - ) + withContext(Dispatchers.EDT + ModalityState.any().asContextElement()) { + if (disposed) return@withContext + currentContextClusterName = kubeConfigCurrentCluster + val previouslySelected = tfServer.selectedItem as? Cluster? + setClusters(updatedClusters) + setSelectedCluster( + (previouslySelected)?.name ?: kubeConfigCurrentCluster, + updatedClusters + ) + setSelectedAuthTab() + enableSaveConfigCheckbox() + } } } diff --git a/src/main/kotlin/com/redhat/devtools/gateway/view/steps/DevSpacesWorkspacesStepView.kt b/src/main/kotlin/com/redhat/devtools/gateway/view/steps/DevSpacesWorkspacesStepView.kt index a1966ee3..91de8c18 100644 --- a/src/main/kotlin/com/redhat/devtools/gateway/view/steps/DevSpacesWorkspacesStepView.kt +++ b/src/main/kotlin/com/redhat/devtools/gateway/view/steps/DevSpacesWorkspacesStepView.kt @@ -13,8 +13,8 @@ package com.redhat.devtools.gateway.view.steps import com.intellij.icons.AllIcons import com.intellij.openapi.Disposable -import com.intellij.openapi.application.invokeLater -import com.intellij.openapi.application.runInEdt +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.ModalityState import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.util.Disposer @@ -209,14 +209,17 @@ class DevSpacesWorkspacesStepView( dwListResult.items } - invokeLater { - val selectedIndex = listDevWorkspaces.selectedIndex - listDWDataModel.apply { - clear() - addAll(devWorkspaces) - } - listDevWorkspaces.selectedIndex = getValidSelectedIndex(selectedIndex) - } + ApplicationManager.getApplication().invokeLater( + { + val selectedIndex = listDevWorkspaces.selectedIndex + listDWDataModel.apply { + clear() + addAll(devWorkspaces) + } + listDevWorkspaces.selectedIndex = getValidSelectedIndex(selectedIndex) + }, + ModalityState.any() + ) return lastResourceVersions } @@ -233,11 +236,16 @@ class DevSpacesWorkspacesStepView( private fun refreshDevWorkspace(namespace: String, name: String) { val refreshedDevWorkspace = DevWorkspaces(devSpacesContext.client).get(namespace, name) - listDWDataModel - .indexOf(refreshedDevWorkspace) - .also { - if (it != -1) listDWDataModel[it] = refreshedDevWorkspace - } + ApplicationManager.getApplication().invokeLater( + { + listDWDataModel + .indexOf(refreshedDevWorkspace) + .also { + if (it != -1) listDWDataModel[it] = refreshedDevWorkspace + } + }, + ModalityState.any() + ) } private fun startDevWorkspace() { @@ -401,20 +409,23 @@ class DevSpacesWorkspacesStepView( } private fun enableButtons() { - runInEdt { - val workspace = getSelectedWorkspace() + ApplicationManager.getApplication().invokeLater( + { + val workspace = getSelectedWorkspace() - startDevWorkspaceButton.isEnabled = isStopped(workspace) - stopDevWorkspaceButton.isEnabled = isRunning(workspace) + startDevWorkspaceButton.isEnabled = isStopped(workspace) + stopDevWorkspaceButton.isEnabled = isRunning(workspace) - refreshNextButton() + refreshNextButton() - if (isAlreadyConnected(workspace)) { - stopDevWorkspaceButton.toolTipText = "This workspace is already connected." - } else { - stopDevWorkspaceButton.toolTipText = null - } - } + if (isAlreadyConnected(workspace)) { + stopDevWorkspaceButton.toolTipText = "This workspace is already connected." + } else { + stopDevWorkspaceButton.toolTipText = null + } + }, + ModalityState.any() + ) } private fun getSelectedWorkspace(): DevWorkspace? { @@ -528,24 +539,30 @@ class DevSpacesWorkspacesStepView( } override fun onUpdated(dw: DevWorkspace) { - runInEdt { - val idx = indexOfFirst { it.name == dw.name && it.namespace == dw.namespace } - if (idx == -1) { - val index = findInsertIndex(dw) - workspacesDataModel.add(index, dw) - } else { - workspacesDataModel.set(idx, dw) - } - } + ApplicationManager.getApplication().invokeLater( + { + val idx = indexOfFirst { it.name == dw.name && it.namespace == dw.namespace } + if (idx == -1) { + val index = findInsertIndex(dw) + workspacesDataModel.add(index, dw) + } else { + workspacesDataModel.set(idx, dw) + } + }, + ModalityState.any() + ) } override fun onDeleted(dw: DevWorkspace) { - runInEdt { - val idx = indexOfFirst { it.namespace == dw.namespace && it.name == dw.name } - if (idx >= 0) { - workspacesDataModel.remove(idx) - } - } + ApplicationManager.getApplication().invokeLater( + { + val idx = indexOfFirst { it.namespace == dw.namespace && it.name == dw.name } + if (idx >= 0) { + workspacesDataModel.remove(idx) + } + }, + ModalityState.any() + ) } private fun findInsertIndex(dw: DevWorkspace): Int {