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
This commit is contained in:
Sergei Tachenov
2025-07-16 17:35:11 +00:00
committed by intellij-monorepo-bot
parent 51e12aea9e
commit 186ffa6182
@@ -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<SwitcherListItem?>(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)))
}
}
}
}
}