From 7a927bdadf1de0da5c57a415263a679dd1107409 Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Thu, 11 Jul 2024 14:23:08 +0200 Subject: [PATCH] IJPL-158075 updateHeadersRunnable - use flow instead of alarm GitOrigin-RevId: 6984b38a83886070e434b522846716de4cfd2432 --- .../openapi/wm/impl/ToolWindowManagerImpl.kt | 166 +++++++++++------- 1 file changed, 100 insertions(+), 66 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowManagerImpl.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowManagerImpl.kt index 778f322f0d5b..66d1cf595fb5 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowManagerImpl.kt +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowManagerImpl.kt @@ -1,6 +1,7 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. @file:Suppress("ReplaceGetOrSet", "ReplacePutWithAssignment", "OverridingDeprecatedMember", "ReplaceNegatedIsEmptyWithIsNotEmpty", "PrivatePropertyName") +@file:OptIn(FlowPreview::class) package com.intellij.openapi.wm.impl @@ -27,6 +28,7 @@ import com.intellij.openapi.application.ModalityState import com.intellij.openapi.application.asContextElement import com.intellij.openapi.components.Service import com.intellij.openapi.components.service +import com.intellij.openapi.components.serviceAsync import com.intellij.openapi.components.serviceIfCreated import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.debug @@ -58,7 +60,6 @@ import com.intellij.openapi.wm.ex.ToolWindowEx import com.intellij.openapi.wm.ex.ToolWindowManagerEx import com.intellij.openapi.wm.ex.ToolWindowManagerListener import com.intellij.openapi.wm.ex.ToolWindowManagerListener.ToolWindowManagerEventType -import com.intellij.openapi.wm.ex.ToolWindowManagerListener.ToolWindowManagerEventType.* import com.intellij.platform.ide.progress.runWithModalProgressBlocking import com.intellij.serviceContainer.NonInjectable import com.intellij.toolWindow.* @@ -70,6 +71,10 @@ import com.intellij.util.concurrency.annotations.RequiresEdt import com.intellij.util.messages.SimpleMessageBusConnection import com.intellij.util.ui.* import kotlinx.coroutines.* +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.debounce import org.intellij.lang.annotations.MagicConstant import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.TestOnly @@ -203,7 +208,7 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( } @Service(Service.Level.APP) - internal class ToolWindowManagerAppLevelHelper { + internal class ToolWindowManagerAppLevelHelper(coroutineScope: CoroutineScope) { companion object { private fun handleFocusEvent(event: FocusEvent) { if (event.id == FocusEvent.FOCUS_LOST) { @@ -284,16 +289,23 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( val awtFocusListener = MyListener() Toolkit.getDefaultToolkit().addAWTEventListener(awtFocusListener, AWTEvent.FOCUS_EVENT_MASK or AWTEvent.WINDOW_FOCUS_EVENT_MASK) - val updateHeadersRunnable = { - for (project in getOpenedProjects()) { - (getInstance(project) as ToolWindowManagerImpl).updateToolWindowHeaders() - } + val updateHeadersRequests = MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) + coroutineScope.launch { + updateHeadersRequests + .debounce(50) + .collectLatest { + for (project in getOpenedProjects()) { + val toolWindowManager = project.serviceAsync() as ToolWindowManagerImpl + withContext(Dispatchers.EDT) { + toolWindowManager.updateToolWindowHeaders() + } + } + } } - val updateHeadersAlarm = SingleAlarm(updateHeadersRunnable, 50, ApplicationManager.getApplication()) - val focusListener = PropertyChangeListener { updateHeadersAlarm.cancelAndRequest() } + val focusListener = PropertyChangeListener { check(updateHeadersRequests.tryEmit(Unit)) } FocusUtil.addFocusOwnerListener(ApplicationManager.getApplication(), focusListener) - val connection = ApplicationManager.getApplication().messageBus.connect() + val connection = ApplicationManager.getApplication().messageBus.connect(coroutineScope) connection.subscribe(ProjectCloseListener.TOPIC, object : ProjectCloseListener { override fun projectClosingBeforeSave(project: Project) { val manager = (project.serviceIfCreated() as ToolWindowManagerImpl?) ?: return @@ -349,7 +361,7 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( } false - }, ApplicationManager.getApplication()) + }, coroutineScope) } } @@ -406,7 +418,7 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( } private fun updateToolWindowHeaders() { - focusManager.doWhenFocusSettlesDown(ExpirableRunnable.forProject(project) { + IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(ExpirableRunnable.forProject(project) { for (entry in idToEntry.values) { if (entry.readOnlyWindowInfo.isVisible) { val decorator = entry.toolWindow.decorator ?: continue @@ -487,9 +499,11 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( } } - suspend fun init(frameHelperDeferred: Deferred, - reopeningEditorJob: Job, - taskListDeferred: Deferred>) { + suspend fun init( + frameHelperDeferred: Deferred, + reopeningEditorJob: Job, + taskListDeferred: Deferred>, + ) { doInit(frameHelperDeferred = frameHelperDeferred, connection = project.messageBus.connect(coroutineScope), reopeningEditorJob = reopeningEditorJob, @@ -497,10 +511,12 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( } @VisibleForTesting - suspend fun doInit(frameHelperDeferred: Deferred, - connection: SimpleMessageBusConnection, - reopeningEditorJob: Job, - taskListDeferred: Deferred>?) { + suspend fun doInit( + frameHelperDeferred: Deferred, + connection: SimpleMessageBusConnection, + reopeningEditorJob: Job, + taskListDeferred: Deferred>?, + ) { withContext(ModalityState.any().asContextElement()) { val frameHelper = frameHelperDeferred.await() launch(Dispatchers.EDT) { @@ -636,10 +652,12 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( } } - internal fun activateToolWindow(entry: ToolWindowEntry, - info: WindowInfoImpl, - autoFocusContents: Boolean = true, - source: ToolWindowEventSource? = null) { + internal fun activateToolWindow( + entry: ToolWindowEntry, + info: WindowInfoImpl, + autoFocusContents: Boolean = true, + source: ToolWindowEventSource? = null, + ) { LOG.debug { "activateToolWindow($entry)" } if (isUnifiedToolWindowSizesEnabled()) { @@ -727,11 +745,13 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( return info } - private fun deactivateToolWindow(info: WindowInfoImpl, - entry: ToolWindowEntry, - dirtyMode: Boolean = false, - mutation: Mutation? = null, - source: ToolWindowEventSource? = null) { + private fun deactivateToolWindow( + info: WindowInfoImpl, + entry: ToolWindowEntry, + dirtyMode: Boolean = false, + mutation: Mutation? = null, + source: ToolWindowEventSource? = null, + ) { LOG.debug { "deactivateToolWindow(${info.id})" } setHiddenState(info, entry, source) @@ -857,11 +877,13 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( hideToolWindow(id = id, hideSide = hideSide, source = null) } - open fun hideToolWindow(id: String, - hideSide: Boolean = false, - moveFocus: Boolean = true, - removeFromStripe: Boolean = false, - source: ToolWindowEventSource? = null) { + open fun hideToolWindow( + id: String, + hideSide: Boolean = false, + moveFocus: Boolean = true, + removeFromStripe: Boolean = false, + source: ToolWindowEventSource? = null, + ) { EDT.assertIsEdt() val entry = idToEntry.get(id)!! @@ -892,12 +914,14 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( revalidateStripeButtons() } - private fun executeHide(entry: ToolWindowEntry, - info: WindowInfoImpl, - dirtyMode: Boolean, - hideSide: Boolean = false, - mutation: Mutation? = null, - source: ToolWindowEventSource? = null) { + private fun executeHide( + entry: ToolWindowEntry, + info: WindowInfoImpl, + dirtyMode: Boolean, + hideSide: Boolean = false, + mutation: Mutation? = null, + source: ToolWindowEventSource? = null, + ) { // hide and deactivate deactivateToolWindow(info, entry, dirtyMode = dirtyMode, mutation = mutation, source = source) @@ -950,10 +974,12 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( /** * @param dirtyMode if `true` then all UI operations are performed in dirty mode. */ - private fun showToolWindowImpl(entry: ToolWindowEntry, - toBeShownInfo: WindowInfoImpl, - dirtyMode: Boolean, - source: ToolWindowEventSource? = null): Boolean { + private fun showToolWindowImpl( + entry: ToolWindowEntry, + toBeShownInfo: WindowInfoImpl, + dirtyMode: Boolean, + source: ToolWindowEventSource? = null, + ): Boolean { if (!entry.toolWindow.isAvailable) { return false } @@ -1069,10 +1095,12 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( return result.entry } - internal fun registerToolWindow(preparedTask: PreparedRegisterToolWindowTask, - buttonManager: ToolWindowButtonManager, - layout: DesktopLayout, - ensureToolWindowActionRegistered: Boolean): RegisterToolWindowResult { + internal fun registerToolWindow( + preparedTask: PreparedRegisterToolWindowTask, + buttonManager: ToolWindowButtonManager, + layout: DesktopLayout, + ensureToolWindowActionRegistered: Boolean, + ): RegisterToolWindowResult { val task = preparedTask.task LOG.debug { "registerToolWindow($task)" } @@ -1525,7 +1553,7 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( } } - fireStateChanged(MoreButtonUpdated) + fireStateChanged(ToolWindowManagerEventType.MoreButtonUpdated) } override fun setShowNames(value: Boolean) { @@ -1538,7 +1566,7 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( } } - fireStateChanged(ShowNames) + fireStateChanged(ToolWindowManagerEventType.ShowNames) } override fun setSideCustomWidth(toolbar: ToolWindowToolbar, width: Int) { @@ -1551,7 +1579,7 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( } } - fireStateChanged(SideCustomWidth) + fireStateChanged(ToolWindowManagerEventType.SideCustomWidth) } override fun invokeLater(runnable: Runnable) { @@ -1563,7 +1591,7 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( } override val focusManager: IdeFocusManager - get() = IdeFocusManager.getInstance(project)!! + get() = IdeFocusManager.getGlobalInstance() override fun canShowNotification(toolWindowId: String): Boolean { val readOnlyWindowInfo = idToEntry.get(toolWindowId)?.readOnlyWindowInfo @@ -1862,13 +1890,15 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( } } - private fun doSetAnchor(entry: ToolWindowEntry, - info: WindowInfoImpl, - paneId: String, - anchor: ToolWindowAnchor, - order: Int, - currentInfo: WindowInfo? = null, - layoutState: DesktopLayout?) { + private fun doSetAnchor( + entry: ToolWindowEntry, + info: WindowInfoImpl, + paneId: String, + anchor: ToolWindowAnchor, + order: Int, + currentInfo: WindowInfo? = null, + layoutState: DesktopLayout?, + ) { if (isNewUi && currentInfo != null) { entry.removeStripeButton(currentInfo.anchor, currentInfo.isSplit) } @@ -1952,10 +1982,12 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( fireStateChanged(ToolWindowManagerEventType.SetSideToolAndAnchor, entry.toolWindow) } - private fun hideIfNeededAndShowAfterTask(entry: ToolWindowEntry, - info: WindowInfoImpl, - source: ToolWindowEventSource? = null, - task: () -> Unit) { + private fun hideIfNeededAndShowAfterTask( + entry: ToolWindowEntry, + info: WindowInfoImpl, + source: ToolWindowEventSource? = null, + task: () -> Unit, + ) { val wasVisible = entry.readOnlyWindowInfo.isVisible val wasFocused = entry.toolWindow.isActive if (wasVisible) { @@ -2359,7 +2391,7 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( layoutState.setUnifiedAnchorWeight(anchor, dockingAreaWeight) LOG.debug { "Moved/resized tool window ${info.id}, updated weight=${toolWindowWeight}, docking area weight=${dockingAreaWeight}" } } - fireStateChanged(MovedOrResized, toolWindow) + fireStateChanged(ToolWindowManagerEventType.MovedOrResized, toolWindow) } private fun dockingAreaComponentSizeCanBeTrusted(dockingAreaComponent: Component): Boolean { @@ -2384,7 +2416,7 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor( private fun getAdjustedWeight( toolWindowPane: ToolWindowPane, anchor: ToolWindowAnchor, - component: Component + component: Component, ): Float { val wholeSize = toolWindowPane.rootPane.size return getAdjustedRatio( @@ -2503,8 +2535,10 @@ private enum class KeyState { WAITING, PRESSED, RELEASED, HOLD } -private fun areAllModifiersPressed(@MagicConstant(flagsFromClass = InputEvent::class) modifiers: Int, - @MagicConstant(flagsFromClass = InputEvent::class) mask: Int): Boolean { +private fun areAllModifiersPressed( + @MagicConstant(flagsFromClass = InputEvent::class) modifiers: Int, + @MagicConstant(flagsFromClass = InputEvent::class) mask: Int, +): Boolean { return (modifiers xor mask) == 0 } @@ -2549,7 +2583,7 @@ private fun getActivateToolWindowVKsMask(): Int { @Suppress("DEPRECATION") baseModifiers = baseModifiers and (InputEvent.SHIFT_MASK or InputEvent.CTRL_MASK or InputEvent.META_MASK or InputEvent.ALT_MASK) - // If the keymap either doesn't define an ActivateProjectToolWindow shortcut, + // If the keymap either doesn't define an ActivateProjectToolWindow shortcut // or defines it with multiple modifiers, fall back to the default behavior. if (baseModifiers.countOneBits() == 1) { return baseModifiers