From 186ffa6182d1cf1c281fa6ffdd727acb9e8b3215 Mon Sep 17 00:00:00 2001 From: Sergei Tachenov Date: Wed, 16 Jul 2025 11:59:08 +0300 Subject: [PATCH] IJPL-197188 Fix fallback Switcher hint popup update PSI_ELEMENT.getData returns an outdated PSI element if called in-place in the same EDT event the selection changes. Fix by postponing the update using a state flow. GitOrigin-RevId: 29ce5db2492e14974887b68b2f044bd2f18de8f9 --- .../src/com/intellij/ide/actions/Switcher.kt | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/ide/actions/Switcher.kt b/platform/platform-impl/src/com/intellij/ide/actions/Switcher.kt index e8c1a6e1959c..25912fb25e00 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/Switcher.kt +++ b/platform/platform-impl/src/com/intellij/ide/actions/Switcher.kt @@ -21,9 +21,13 @@ import com.intellij.ide.util.gotoByName.QuickSearchComponent import com.intellij.openapi.Disposable import com.intellij.openapi.actionSystem.* import com.intellij.openapi.actionSystem.ex.ActionUtil +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.EDT import com.intellij.openapi.application.ModalityState import com.intellij.openapi.application.ReadAction +import com.intellij.openapi.application.UiDispatcherKind +import com.intellij.openapi.application.ui +import com.intellij.openapi.application.writeIntentReadAction import com.intellij.openapi.components.service import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory @@ -67,8 +71,12 @@ import com.intellij.util.ui.StartupUiUtil import com.intellij.util.ui.SwingTextTrimmer import com.intellij.util.ui.accessibility.ScreenReader import com.intellij.util.ui.components.BorderLayoutPanel +import com.intellij.util.ui.launchOnShow import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.Nls import org.jetbrains.annotations.NonNls @@ -279,6 +287,7 @@ object Switcher : BaseSwitcherAction(null) { pinned = recent, ) resetListModelAndUpdateNames(filesModel, filesToShow) + val selectedItemFlow = MutableStateFlow(null) val filesSelectionListener = object : ListSelectionListener { override fun valueChanged(e: ListSelectionEvent) { if (e.valueIsAdjusting) { @@ -286,9 +295,26 @@ object Switcher : BaseSwitcherAction(null) { } updatePathLabel() - val hint = hint - val popupUpdater = if (hint == null || !hint.isVisible) null else hint.getUserData(PopupUpdateProcessorBase::class.java) - popupUpdater?.updatePopup(CommonDataKeys.PSI_ELEMENT.getData(DataManager.getInstance().getDataContext(this@SwitcherPanel))) + selectedItemFlow.value = selectedList?.selectedValue + } + } + launchOnShow("Switcher hint popup updates") { + selectedItemFlow.collectLatest { selectedValue -> + // launchOnShow uses UI, but it won't do here: updatePopup needs a WIRA. + // Still, we'd like to avoid locking until the updatePopup call. + // At the same time, we'd like to keep this whole thing in a single EDT event, + // otherwise there's a chance that the hint popup may be hidden in between. + // So we can't just switch to Dispatchers.EDT, as that will dispatch on a separate event. + // Therefore, we use RELAX and take the lock only when necessary. + withContext(Dispatchers.ui(UiDispatcherKind.RELAX)) { + val hint = hint + val popupUpdater = if (hint == null || !hint.isVisible) null else hint.getUserData(PopupUpdateProcessorBase::class.java) + if (selectedValue != null && popupUpdater != null) { + writeIntentReadAction { + popupUpdater.updatePopup(CommonDataKeys.PSI_ELEMENT.getData(DataManager.getInstance().getDataContext(this@SwitcherPanel))) + } + } + } } }