From 6372bfea0aae83894488abd4e78aef87f4c18348 Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Sat, 13 Jul 2024 16:46:55 +0200 Subject: [PATCH] IJPL-158286 LookupCellRenderer - get rid of SingleAlarm in favour of flow GitOrigin-RevId: 8cffbdd2089d02d04bb012cb3dd579406acb2eaf --- platform/lang-impl/api-dump-unreviewed.txt | 1 - .../CompletionProgressIndicator.java | 2 +- .../codeInsight/lookup/impl/AsyncRendering.kt | 11 ++--- .../lookup/impl/LookupCellRenderer.kt | 44 ++++++++++++------- .../codeInsight/lookup/impl/LookupImpl.java | 27 +++++++++--- .../components/ScrollBarAnimationBehavior.kt | 2 +- .../openapi/progress/util/ProgressDialog.kt | 2 +- 7 files changed, 60 insertions(+), 29 deletions(-) diff --git a/platform/lang-impl/api-dump-unreviewed.txt b/platform/lang-impl/api-dump-unreviewed.txt index 26426418691a..5d1015f84459 100644 --- a/platform/lang-impl/api-dump-unreviewed.txt +++ b/platform/lang-impl/api-dump-unreviewed.txt @@ -7902,7 +7902,6 @@ f:com.intellij.codeInsight.lookup.impl.LookupCellRenderer - f:refreshUi():V - f:scheduleUpdateLookupWidthFromVisibleItems():V - f:updateItemPresentation(com.intellij.codeInsight.lookup.LookupElement):V -- f:updateLookupWidthFromVisibleItems():V f:com.intellij.codeInsight.lookup.impl.LookupCellRenderer$Companion - f:augmentIcon(com.intellij.openapi.editor.Editor,javax.swing.Icon,javax.swing.Icon):javax.swing.Icon - f:bodyInsets():java.awt.Insets diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java index 298a5f96996c..4106f0df9ce5 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java @@ -520,7 +520,7 @@ public final class CompletionProgressIndicator extends ProgressIndicatorBase imp private void addItemToLookup(CompletionResult item) { Ref stopRef = new Ref<>(Boolean.FALSE); DumbModeAccessType.RELIABLE_DATA_ONLY.ignoreDumbMode(() -> { - stopRef.set(!lookup.addItem(item.getLookupElement(), item.getPrefixMatcher())); + stopRef.set(lookup.isLookupDisposed() || !lookup.addItem(item.getLookupElement(), item.getPrefixMatcher())); }); if (stopRef.get()) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/AsyncRendering.kt b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/AsyncRendering.kt index 84e6a232fd4f..fee6652518f5 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/AsyncRendering.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/AsyncRendering.kt @@ -7,9 +7,7 @@ import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementPresentation import com.intellij.codeInsight.lookup.LookupElementRenderer import com.intellij.openapi.application.readAction -import com.intellij.openapi.components.ComponentManagerEx import com.intellij.openapi.util.Key -import com.intellij.util.cancelOnDispose import com.intellij.util.indexing.DumbModeAccessType import kotlinx.coroutines.* @@ -38,8 +36,11 @@ internal class AsyncRendering(private val lookup: LookupImpl) { synchronized(LAST_COMPUTATION) { cancelRendering(element) - @Suppress("UsagesOfObsoleteApi") - val job = (lookup.project as ComponentManagerEx).getCoroutineScope().launch(limitedDispatcher) { + if (lookup.isLookupDisposed) { + return + } + + val job = lookup.coroutineScope.launch(limitedDispatcher) { val job = coroutineContext.job readAction { if (element.isValid) { @@ -49,7 +50,7 @@ internal class AsyncRendering(private val lookup: LookupImpl) { synchronized(LAST_COMPUTATION) { element.replace(LAST_COMPUTATION, job, null) } - }.also { it.cancelOnDispose(lookup) } + } element.putUserData(LAST_COMPUTATION, job) } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupCellRenderer.kt b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupCellRenderer.kt index 85119d3422ae..f68ea4d54e25 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupCellRenderer.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupCellRenderer.kt @@ -9,7 +9,9 @@ import com.intellij.codeInsight.lookup.impl.LookupCellRenderer.Companion.bodyIns import com.intellij.codeInsight.lookup.impl.LookupCellRenderer.Companion.getGrayedForeground import com.intellij.codeInsight.lookup.impl.LookupCellRenderer.IconDecorator 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.editor.Editor import com.intellij.openapi.editor.colors.CodeInsightColors import com.intellij.openapi.editor.colors.EditorColorsManager @@ -23,6 +25,7 @@ import com.intellij.openapi.util.Key import com.intellij.openapi.util.NlsSafe import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.registry.Registry +import com.intellij.platform.util.coroutines.flow.throttle import com.intellij.psi.codeStyle.NameUtil import com.intellij.ui.* import com.intellij.ui.ExperimentalUI.Companion.isNewUI @@ -34,7 +37,6 @@ import com.intellij.ui.scale.JBUIScale.scale import com.intellij.ui.speedSearch.SpeedSearchUtil import com.intellij.util.IconUtil.cropIcon import com.intellij.util.ObjectUtils -import com.intellij.util.SingleAlarm import com.intellij.util.containers.ContainerUtil import com.intellij.util.containers.FList import com.intellij.util.ui.EmptyIcon @@ -43,6 +45,11 @@ import com.intellij.util.ui.JBUI import com.intellij.util.ui.UIUtil import com.intellij.util.ui.accessibility.AccessibleContextUtil import it.unimi.dsi.fastutil.ints.Int2BooleanOpenHashMap +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.Nls import org.jetbrains.annotations.VisibleForTesting @@ -75,7 +82,7 @@ class LookupCellRenderer(lookup: LookupImpl, editorComponent: JComponent) : List var lookupTextWidth: Int = 50 private set private val widthLock = ObjectUtils.sentinel("lookup width lock") - private val lookupWidthUpdater: Runnable + private val lookupWidthUpdater: () -> Unit private val shrinkLookup: Boolean private val asyncRendering: AsyncRendering @@ -111,23 +118,30 @@ class LookupCellRenderer(lookup: LookupImpl, editorComponent: JComponent) : List panel.add(tailComponent, BorderLayout.CENTER) panel.add(typeLabel, BorderLayout.EAST) - normalMetrics = this.lookup.topLevelEditor.component.getFontMetrics(normalFont) - boldMetrics = this.lookup.topLevelEditor.component.getFontMetrics(boldFont) - asyncRendering = AsyncRendering(this.lookup) + normalMetrics = lookup.topLevelEditor.component.getFontMetrics(normalFont) + boldMetrics = lookup.topLevelEditor.component.getFontMetrics(boldFont) + asyncRendering = AsyncRendering(lookup) if (ApplicationManager.getApplication().isUnitTestMode) { // avoid delay in unit tests - lookupWidthUpdater = Runnable { ApplicationManager.getApplication().invokeLater { this.updateLookupWidthFromVisibleItems() } } + lookupWidthUpdater = { + ApplicationManager.getApplication().invokeLater({ updateLookupWidthFromVisibleItems() }, lookup.project.disposed) + } } else { - val alarm = - SingleAlarm({ this.updateLookupWidthFromVisibleItems() }, 50, lookup, ModalityState.stateForComponent(editorComponent)) - lookupWidthUpdater = Runnable { - synchronized(alarm) { - if (!alarm.isDisposed) { - alarm.request() + val lookupWidthUpdateRequests = MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) + val coroutineContext = Dispatchers.EDT + ModalityState.stateForComponent(editorComponent).asContextElement() + lookup.coroutineScope.launch { + lookupWidthUpdateRequests + .throttle(50) + .collect { + withContext(coroutineContext) { + updateLookupWidthFromVisibleItems() + } } - } + } + lookupWidthUpdater = { + check(lookupWidthUpdateRequests.tryEmit(Unit)) } } @@ -478,7 +492,7 @@ class LookupCellRenderer(lookup: LookupImpl, editorComponent: JComponent) : List /** * Update lookup width due to visible in lookup items */ - fun updateLookupWidthFromVisibleItems() { + private fun updateLookupWidthFromVisibleItems() { val visibleItems = lookup.visibleItems var maxWidth = if (shrinkLookup) 0 else lookupTextWidth @@ -505,7 +519,7 @@ class LookupCellRenderer(lookup: LookupImpl, editorComponent: JComponent) : List } fun scheduleUpdateLookupWidthFromVisibleItems() { - lookupWidthUpdater.run() + lookupWidthUpdater() } fun itemAdded(element: LookupElement, fastPresentation: LookupElementPresentation) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java index 86be4c9b9109..2a2f07cf30e5 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java @@ -58,6 +58,9 @@ import com.intellij.util.ui.accessibility.AccessibleContextUtil; import com.intellij.util.ui.accessibility.ScreenReader; import com.intellij.util.ui.update.Activatable; import com.intellij.util.ui.update.UiNotifyConnector; +import kotlinx.coroutines.CoroutineScope; +import kotlinx.coroutines.CoroutineScopeKt; +import kotlinx.coroutines.Dispatchers; import one.util.streamex.StreamEx; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -77,6 +80,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import static com.intellij.codeInsight.lookup.LookupElement.LOOKUP_ELEMENT_SHOW_TIMESTAMP_MILLIS; +import static kotlinx.coroutines.SupervisorKt.SupervisorJob; public class LookupImpl extends LightweightHint implements LookupEx, Disposable, LookupElementListPresenter { private static final Logger LOG = Logger.getInstance(LookupImpl.class); @@ -116,6 +120,8 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable, private final EmptyLookupItem myDummyItem = new EmptyLookupItem(CommonBundle.message("tree.node.loading"), true); private boolean myFirstElementAdded = false; + final CoroutineScope coroutineScope = CoroutineScopeKt.CoroutineScope(SupervisorJob(null).plus(Dispatchers.getDefault())); + public LookupImpl(Project project, Editor editor, @NotNull LookupArranger arranger) { super(new JPanel(new BorderLayout())); setForceShowAsPopup(true); @@ -346,7 +352,9 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable, } public void fireBeforeAppendPrefix(char c) { - myPrefixChangeListeners.forEach((listener -> listener.beforeAppend(c))); + for (PrefixChangeListener listener : myPrefixChangeListeners) { + listener.beforeAppend(c); + } } public void appendPrefix(char c) { @@ -362,7 +370,9 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable, refreshUi(false, true); ensureSelectionVisible(true); } - myPrefixChangeListeners.forEach((listener -> listener.afterAppend(c))); + for (PrefixChangeListener listener : myPrefixChangeListeners) { + listener.afterAppend(c); + } } public void setStartCompletionWhenNothingMatches(boolean startCompletionWhenNothingMatches) { @@ -419,7 +429,9 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable, ensureSelectionVisible(true); } - myPrefixChangeListeners.forEach((listener -> listener.afterTruncate())); + for (PrefixChangeListener listener : myPrefixChangeListeners) { + listener.afterTruncate(); + } } void moveToCaretPosition() { @@ -449,7 +461,9 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable, if (!myFirstElementAdded && !items.isEmpty()) { myFirstElementAdded = true; - myListeners.forEach(LookupListener::firstElementShown); + for (LookupListener listener : myListeners) { + listener.firstElementShown(); + } } myOffsets.checkMinPrefixLengthChanges(items, this); @@ -461,7 +475,9 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable, model.removeAll(); if (!finalItems.isEmpty()) { Long currentTimeMillis = System.currentTimeMillis(); - finalItems.forEach(item -> item.putUserDataIfAbsent(LOOKUP_ELEMENT_SHOW_TIMESTAMP_MILLIS, currentTimeMillis)); + for (LookupElement item : finalItems) { + item.putUserDataIfAbsent(LOOKUP_ELEMENT_SHOW_TIMESTAMP_MILLIS, currentTimeMillis); + } model.add(finalItems); addDummyItems(myDummyItemCount.get()); } @@ -1195,6 +1211,7 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable, @Override public void dispose() { + CoroutineScopeKt.cancel(coroutineScope, null); ThreadingAssertions.assertEventDispatchThread(); assert myHidden; diff --git a/platform/platform-api/src/com/intellij/ui/components/ScrollBarAnimationBehavior.kt b/platform/platform-api/src/com/intellij/ui/components/ScrollBarAnimationBehavior.kt index 926776a0b042..a30f0d471917 100644 --- a/platform/platform-api/src/com/intellij/ui/components/ScrollBarAnimationBehavior.kt +++ b/platform/platform-api/src/com/intellij/ui/components/ScrollBarAnimationBehavior.kt @@ -60,7 +60,7 @@ internal class MacScrollBarAnimationBehavior( ) : DefaultScrollBarAnimationBehavior(trackAnimator, thumbAnimator) { private var isTrackHovered: Boolean = false @Suppress("SSBasedInspection") - private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default + CoroutineName("IdeRootPane")) + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) private val hideThumbRequests = MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) init { diff --git a/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressDialog.kt b/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressDialog.kt index e3dbca86ccd2..92aa71b5a953 100644 --- a/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressDialog.kt +++ b/platform/platform-impl/src/com/intellij/openapi/progress/util/ProgressDialog.kt @@ -60,7 +60,7 @@ class ProgressDialog( private var popup: DialogWrapper? = null @Suppress("SSBasedInspection") - private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default + CoroutineName("IdeRootPane")) + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) init { ui.progressBar.isIndeterminate = progressWindow.isIndeterminate