[wm] make toolwindow pane an implementation detail of root pane

GitOrigin-RevId: 575a3203da66483ef867e16a920cd6c3337e1b83
This commit is contained in:
Ivan Semenov
2024-08-06 20:32:58 +02:00
committed by intellij-monorepo-bot
parent 254019a4fa
commit 6171dacd2b
13 changed files with 127 additions and 86 deletions

View File

@@ -4,20 +4,20 @@
package com.intellij.toolWindow
import com.intellij.ide.ui.LafManager
import com.intellij.openapi.application.EDT
import com.intellij.openapi.components.ComponentManagerEx
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.openapi.wm.WINDOW_INFO_DEFAULT_TOOL_WINDOW_PANE_ID
import com.intellij.openapi.wm.ex.ToolWindowManagerListener.ToolWindowManagerEventType
import com.intellij.openapi.wm.impl.IdeFrameImpl
import com.intellij.openapi.wm.impl.ProjectFrameHelper
import com.intellij.openapi.wm.impl.ToolWindowManagerImpl
import com.intellij.testFramework.LightPlatformTestCase
import com.intellij.testFramework.SkipInHeadlessEnvironment
import com.intellij.testFramework.replaceService
import com.intellij.testFramework.runInEdtAndWait
import kotlinx.coroutines.*
import kotlinx.coroutines.Job
import kotlinx.coroutines.runBlocking
@SkipInHeadlessEnvironment
abstract class ToolWindowManagerTestCase : LightPlatformTestCase() {
@@ -40,14 +40,13 @@ abstract class ToolWindowManagerTestCase : LightPlatformTestCase() {
}
project.replaceService(ToolWindowManager::class.java, manager!!, testRootDisposable)
val frame = withContext(Dispatchers.EDT) {
val frame = ProjectFrameHelper(IdeFrameImpl())
frame.init()
frame
}
val pane = ToolWindowPane.create(IdeFrameImpl(), WINDOW_INFO_DEFAULT_TOOL_WINDOW_PANE_ID)
val reopeningEditorJob = Job().also { it.complete() }
manager!!.doInit(CompletableDeferred(value = frame), project.messageBus.connect(testRootDisposable), reopeningEditorJob, taskListDeferred = null)
manager!!.doInit(pane,
project.messageBus.connect(testRootDisposable),
reopeningEditorJob,
taskListDeferred = null)
}
}

View File

@@ -18263,7 +18263,7 @@ c:com.intellij.openapi.wm.impl.PlatformFrameTitleBuilder
- getFileTitle(com.intellij.openapi.project.Project,com.intellij.openapi.vfs.VirtualFile):java.lang.String
- getFileTitleAsync(com.intellij.openapi.project.Project,com.intellij.openapi.vfs.VirtualFile,kotlin.coroutines.Continuation):java.lang.Object
- getProjectTitle(com.intellij.openapi.project.Project):java.lang.String
c:com.intellij.openapi.wm.impl.ProjectFrameHelper
a:com.intellij.openapi.wm.impl.ProjectFrameHelper
- com.intellij.openapi.actionSystem.UiDataProvider
- com.intellij.openapi.wm.IdeFrame
- com.intellij.util.ui.accessibility.AccessibleContextAccessor

View File

@@ -79,7 +79,7 @@ internal class IdeProjectFrameAllocator(
private val options: OpenProjectTask,
private val projectStoreBaseDir: Path,
) : ProjectFrameAllocator {
private val deferredProjectFrameHelper = CompletableDeferred<ProjectFrameHelper>()
private val deferredProjectFrameHelper = CompletableDeferred<IdeProjectFrameHelper>()
override suspend fun preInitProject(project: Project) {
(project.serviceAsync<FileEditorManager>() as? FileEditorManagerImpl)?.initJob?.join()
@@ -125,7 +125,7 @@ internal class IdeProjectFrameAllocator(
val fileEditorManager = project.serviceAsync<FileEditorManager>() as FileEditorManagerImpl
fileEditorManager.initJob.join()
withContext(Dispatchers.EDT) {
frameHelper.rootPane.getToolWindowPane().setDocumentComponent(fileEditorManager.mainSplitters)
frameHelper.toolWindowPane.setDocumentComponent(fileEditorManager.mainSplitters)
}
}
@@ -157,8 +157,11 @@ internal class IdeProjectFrameAllocator(
val taskListDeferred = async(CoroutineName("toolwindow init command creation")) {
computeToolWindowBeans(project = project)
}
val toolWindowPane = withContext(Dispatchers.EDT) {
deferredProjectFrameHelper.await().toolWindowPane
}
toolWindowManager.await()?.init(
deferredProjectFrameHelper,
toolWindowPane,
reopeningEditorJob = reopeningEditorJob,
taskListDeferred = taskListDeferred,
)
@@ -204,9 +207,9 @@ internal class IdeProjectFrameAllocator(
if (frame != null) {
withContext(Dispatchers.EDT) {
val frameHelper = ProjectFrameHelper(frame = frame, loadingState = loadingState)
val frameHelper = IdeProjectFrameHelper(frame = frame, loadingState = loadingState)
completeFrameAndCloseOnCancel(frameHelper, deferredProjectFrameHelper) {
completeFrameAndCloseOnCancel(frameHelper) {
if (options.forceOpenInNewFrame) {
updateFullScreenState(frameHelper, getFrameInfo())
}
@@ -221,23 +224,23 @@ internal class IdeProjectFrameAllocator(
val preAllocated = getAndUnsetSplashProjectFrame() as IdeFrameImpl?
if (preAllocated != null) {
val frameHelper = withContext(Dispatchers.EDT) {
val frameHelper = ProjectFrameHelper(frame = preAllocated, loadingState = loadingState)
val frameHelper = IdeProjectFrameHelper(frame = preAllocated, loadingState = loadingState)
frameHelper.init()
frameHelper
}
completeFrameAndCloseOnCancel(frameHelper, deferredProjectFrameHelper) {}
completeFrameAndCloseOnCancel(frameHelper) {}
return
}
val frameInfo = getFrameInfo()
val frameProducer = createNewProjectFrameProducer(frameInfo = frameInfo)
withContext(Dispatchers.EDT) {
val frameHelper = ProjectFrameHelper(frameProducer.create(), loadingState = loadingState)
val frameHelper = IdeProjectFrameHelper(frameProducer.create(), loadingState = loadingState)
// must be after preInit (frame decorator is required to set a full-screen mode)
frameHelper.frame.isVisible = true
updateFullScreenState(frameHelper, frameInfo)
completeFrameAndCloseOnCancel(frameHelper, deferredProjectFrameHelper) {
completeFrameAndCloseOnCancel(frameHelper) {
span("ProjectFrameHelper.init") {
frameHelper.init()
}
@@ -247,8 +250,7 @@ internal class IdeProjectFrameAllocator(
}
private suspend inline fun completeFrameAndCloseOnCancel(
frameHelper: ProjectFrameHelper,
deferredProjectFrameHelper: CompletableDeferred<ProjectFrameHelper>,
frameHelper: IdeProjectFrameHelper,
task: () -> Unit,
) {
try {
@@ -368,7 +370,7 @@ private suspend fun restoreEditors(project: Project, fileEditorManager: FileEdit
}
private suspend fun postOpenEditors(
frameHelper: ProjectFrameHelper,
frameHelper: IdeProjectFrameHelper,
fileEditorManager: FileEditorManagerImpl,
project: Project,
toolWindowInitJob: Job,

View File

@@ -0,0 +1,17 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.wm.impl
import com.intellij.toolWindow.ToolWindowPane
import com.intellij.util.concurrency.annotations.RequiresEdt
/**
* Helper class which sets up a main IDE frame
*/
internal class IdeProjectFrameHelper(
frame: IdeFrameImpl,
loadingState: FrameLoadingState,
) : ProjectFrameHelper(frame, loadingState) {
@get:RequiresEdt
val toolWindowPane: ToolWindowPane
get() = rootPane.getToolWindowPane()
}

View File

@@ -17,10 +17,7 @@ import com.intellij.openapi.wm.StatusBar
import com.intellij.openapi.wm.WINDOW_INFO_DEFAULT_TOOL_WINDOW_PANE_ID
import com.intellij.openapi.wm.impl.customFrameDecorations.header.*
import com.intellij.platform.util.coroutines.childScope
import com.intellij.toolWindow.ToolWindowButtonManager
import com.intellij.toolWindow.ToolWindowPane
import com.intellij.toolWindow.ToolWindowPaneNewButtonManager
import com.intellij.toolWindow.ToolWindowPaneOldButtonManager
import com.intellij.ui.*
import com.intellij.ui.components.JBBox
import com.intellij.ui.components.JBLayeredPane
@@ -92,16 +89,9 @@ open class IdeRootPane internal constructor(
protected open fun createCenterComponent(frame: JFrame): Component {
val paneId = WINDOW_INFO_DEFAULT_TOOL_WINDOW_PANE_ID
val toolWindowButtonManager: ToolWindowButtonManager
if (ExperimentalUI.isNewUI()) {
toolWindowButtonManager = ToolWindowPaneNewButtonManager(paneId)
}
else {
toolWindowButtonManager = ToolWindowPaneOldButtonManager(paneId)
}
toolWindowButtonManager.setupContentPane(contentPane as JComponent)
toolWindowPane = ToolWindowPane(frame, coroutineScope, paneId, toolWindowButtonManager)
return toolWindowPane!!
val pane = ToolWindowPane.create(frame, coroutineScope, paneId)
toolWindowPane = pane
return pane.buttonManager.wrapWithControls(pane)
}
override fun getAccessibleContext(): AccessibleContext {

View File

@@ -70,12 +70,10 @@ private const val INIT_BOUNDS_KEY = "InitBounds"
private val LOG: Logger
get() = logger<ProjectFrameHelper>()
open class ProjectFrameHelper internal constructor(
abstract class ProjectFrameHelper internal constructor(
val frame: IdeFrameImpl,
loadingState: FrameLoadingState? = null,
) : IdeFrameEx, AccessibleContextAccessor, UiDataProvider {
@Internal
constructor(frame: IdeFrameImpl) : this(frame = frame, loadingState = null)
@Suppress("SSBasedInspection")
@Internal
@@ -94,9 +92,8 @@ open class ProjectFrameHelper internal constructor(
@Internal
protected open val mainMenuActionGroup: ActionGroup? = null
@JvmField
@Internal
val rootPane: IdeRootPane
protected val rootPane: IdeRootPane
private val frameHeaderHelper: ProjectFrameCustomHeaderHelper

View File

@@ -78,6 +78,7 @@ 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.ApiStatus.Internal
import org.jetbrains.annotations.TestOnly
import org.jetbrains.annotations.VisibleForTesting
import java.awt.*
@@ -119,10 +120,10 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor(
private val sideStack = SideStack()
private val toolWindowPanes = LinkedHashMap<String, ToolWindowPane>()
private var frameHelper: ProjectFrameHelper?
get() = state.frame
private var projectFrame: JFrame?
get() = state.projectFrame
set(value) {
state.frame = value
state.projectFrame = value
}
override var layoutToRestoreLater: DesktopLayout?
@@ -499,39 +500,39 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor(
}
}
suspend fun init(
frameHelperDeferred: Deferred<ProjectFrameHelper>,
internal suspend fun init(
pane: ToolWindowPane,
reopeningEditorJob: Job,
taskListDeferred: Deferred<List<RegisterToolWindowTask>>,
) {
doInit(frameHelperDeferred = frameHelperDeferred,
doInit(pane = pane,
connection = project.messageBus.connect(coroutineScope),
reopeningEditorJob = reopeningEditorJob,
taskListDeferred = taskListDeferred)
taskListDeferred = taskListDeferred
)
}
@Internal
@VisibleForTesting
suspend fun doInit(
frameHelperDeferred: Deferred<ProjectFrameHelper>,
pane: ToolWindowPane,
connection: SimpleMessageBusConnection,
reopeningEditorJob: Job,
taskListDeferred: Deferred<List<RegisterToolWindowTask>>?,
) {
withContext(ModalityState.any().asContextElement()) {
val frameHelper = frameHelperDeferred.await()
launch(Dispatchers.EDT) {
this@ToolWindowManagerImpl.frameHelper = frameHelper
this@ToolWindowManagerImpl.projectFrame = pane.frame
// Make sure we haven't already created the root tool window pane.
// We might have created panes for secondary frames, as they get
// registered differently, but we shouldn't have the main pane yet
LOG.assertTrue(!toolWindowPanes.containsKey(WINDOW_INFO_DEFAULT_TOOL_WINDOW_PANE_ID))
val toolWindowPane = frameHelper.rootPane.getToolWindowPane()
// This will be the tool window pane for the default frame, which is not automatically added by the ToolWindowPane constructor.
// If we're reopening other frames, their tool window panes will be added,
// but we still need to initialise the tool windows themselves.
toolWindowPanes.put(toolWindowPane.paneId, toolWindowPane)
toolWindowPanes.put(pane.paneId, pane)
}
connection.subscribe(ToolWindowManagerListener.TOPIC, dispatcher.multicaster)
toolWindowSetInitializer.initUi(reopeningEditorJob, taskListDeferred)
@@ -783,7 +784,7 @@ open class ToolWindowManagerImpl @NonInjectable @TestOnly internal constructor(
override val activeToolWindowId: String?
get() {
val frame = toolWindowPanes.values.firstOrNull { it.frame.isActive }?.frame ?: frameHelper?.frame ?: return null
val frame = toolWindowPanes.values.firstOrNull { it.frame.isActive }?.frame ?: projectFrame ?: return null
if (frame.isActive) {
return getToolWindowIdForComponent(frame.mostRecentFocusOwner)
}

View File

@@ -16,6 +16,7 @@ import com.intellij.util.concurrency.ThreadingAssertions
import org.jdom.Element
import org.jetbrains.annotations.ApiStatus
import java.util.*
import javax.swing.JFrame
@ApiStatus.Internal
interface ToolWindowManagerState : PersistentStateComponent<Element> {
@@ -26,7 +27,7 @@ interface ToolWindowManagerState : PersistentStateComponent<Element> {
val recentToolWindows: LinkedList<String>
val scheduledLayout: AtomicProperty<DesktopLayout?>
val isEditorComponentActive: Boolean
var frame: ProjectFrameHelper?
var projectFrame: JFrame?
var moreButton: ToolWindowAnchor
}
@@ -54,12 +55,12 @@ class ToolWindowManagerStateImpl : ToolWindowManagerState {
return ComponentUtil.getParentOfType(EditorsSplitters::class.java, IdeFocusManager.getGlobalInstance().focusOwner) != null
}
override var frame: ProjectFrameHelper? = null
override var projectFrame: JFrame? = null
override var moreButton: ToolWindowAnchor = ToolWindowAnchor.LEFT
override fun getState(): Element? {
if (frame == null) {
if (projectFrame == null) {
return null
}

View File

@@ -15,10 +15,9 @@ import javax.swing.JComponent
internal interface ToolWindowButtonManager {
val isNewUi: Boolean
fun setupContentPane(pane: JComponent)
fun setupToolWindowPane(pane: JComponent)
fun setupToolWindowPane(pane: JComponent) {
}
fun wrapWithControls(pane: ToolWindowPane): JComponent
fun initMoreButton(project: Project) {}

View File

@@ -27,6 +27,7 @@ import com.intellij.openapi.wm.impl.ToolWindowManagerImpl
import com.intellij.openapi.wm.impl.ToolWindowManagerImpl.Companion.getAdjustedRatio
import com.intellij.openapi.wm.impl.ToolWindowManagerImpl.Companion.getRegisteredMutableInfoOrLogError
import com.intellij.openapi.wm.impl.WindowInfoImpl
import com.intellij.ui.ExperimentalUI
import com.intellij.ui.JBColor
import com.intellij.ui.OnePixelSplitter
import com.intellij.ui.ScreenUtil
@@ -41,10 +42,9 @@ import com.intellij.util.ui.ImageUtil
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.UIUtil
import kotlinx.coroutines.CoroutineScope
import java.awt.Component
import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.Image
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.annotations.VisibleForTesting
import java.awt.*
import java.awt.geom.Point2D
import java.awt.image.BufferedImage
import java.lang.ref.SoftReference
@@ -61,9 +61,8 @@ private val LOG = logger<ToolWindowPane>()
/**
* This panel contains all tool stripes and JLayeredPane at the center area. All tool windows are located inside this layered pane.
*/
class ToolWindowPane internal constructor(
class ToolWindowPane private constructor(
frame: JFrame,
coroutineScope: CoroutineScope,
val paneId: String,
@field:JvmField internal val buttonManager: ToolWindowButtonManager,
) : JLayeredPane(), UISettingsListener {
@@ -82,6 +81,38 @@ class ToolWindowPane internal constructor(
}
internal fun log() = LOG
internal fun create(frame: JFrame, coroutineScope: CoroutineScope, paneId: String, buttonManager: ToolWindowButtonManager): ToolWindowPane {
return ToolWindowPane(frame, paneId, buttonManager).also { pane ->
val app = ApplicationManager.getApplication()
app.messageBus.connect(coroutineScope).subscribe(LafManagerListener.TOPIC, LafManagerListener { pane.isLookAndFeelUpdated = true })
}
}
internal fun create(frame: JFrame, coroutineScope: CoroutineScope, paneId: String): ToolWindowPane {
val buttonManager = createButtonManager(paneId)
return create(frame, coroutineScope, paneId, buttonManager)
}
// this is a method strictly for use in tests
// will not react to LaF changes, so if you want to test this - use one of the methods with coroutine scope
@Internal
@VisibleForTesting
fun create(frame: JFrame, paneId: String): ToolWindowPane {
val buttonManager = createButtonManager(paneId)
return ToolWindowPane(frame, paneId, buttonManager)
}
private fun createButtonManager(paneId: String): ToolWindowButtonManager {
val buttonManager: ToolWindowButtonManager
if (ExperimentalUI.isNewUI()) {
buttonManager = ToolWindowPaneNewButtonManager(paneId)
}
else {
buttonManager = ToolWindowPaneOldButtonManager(paneId)
}
return buttonManager
}
}
private var isLookAndFeelUpdated = false
@@ -151,8 +182,6 @@ class ToolWindowPane internal constructor(
if (Registry.`is`("ide.allow.split.and.reorder.in.tool.window")) {
ToolWindowInnerDragHelper(disposable, this).start()
}
val app = ApplicationManager.getApplication()
app.messageBus.connect(coroutineScope).subscribe(LafManagerListener.TOPIC, LafManagerListener { isLookAndFeelUpdated = true })
}
override fun removeNotify() {
@@ -268,8 +297,10 @@ class ToolWindowPane internal constructor(
internal fun setWeight(anchor: ToolWindowAnchor, weight: Float) {
setAnchorWeightFutures.remove(anchor)?.cancel(false)
val size = rootPane.size
if (size.height == 0 && size.width == 0) {
// can be null in tests
val rootPane = rootPane
val size = rootPane?.size ?: Dimension()
if (rootPane != null && size.height == 0 && size.width == 0) {
if (LOG.isDebugEnabled) {
LOG.debug("Postponing setting the weight of the anchor $anchor because the root pane size is $size")
}
@@ -839,8 +870,8 @@ private class FrameLayeredPane(splitter: JComponent, frame: JFrame) : JLayeredPa
}
val info = component.toolWindow.windowInfo
val rootWidth = rootPane.width
val rootHeight = rootPane.height
val rootWidth = rootPane?.width ?: 0
val rootHeight = rootPane?.height ?: 0
val weight = if (info.anchor.isHorizontal) getAdjustedRatio(component.getHeight(), rootHeight, 1)
else getAdjustedRatio(component.getWidth(), rootWidth, 1)
setBoundsInPaletteLayer(component, info.anchor, weight)
@@ -855,8 +886,8 @@ private class FrameLayeredPane(splitter: JComponent, frame: JFrame) : JLayeredPa
else if (weight > 1.0f) {
weight = 1.0f
}
val rootHeight = rootPane.height
val rootWidth = rootPane.width
val rootHeight = rootPane?.height ?: 0
val rootWidth = rootPane?.width ?: 0
when (anchor) {
ToolWindowAnchor.TOP -> {
component.setBounds(0, 0, width, (rootHeight * weight).toInt())

View File

@@ -13,6 +13,7 @@ import java.awt.BorderLayout
import java.awt.Dimension
import javax.swing.Icon
import javax.swing.JComponent
import javax.swing.JPanel
internal class ToolWindowPaneNewButtonManager(paneId: String, isPrimary: Boolean) : ToolWindowButtonManager {
@@ -24,11 +25,6 @@ internal class ToolWindowPaneNewButtonManager(paneId: String, isPrimary: Boolean
override val isNewUi: Boolean
get() = true
override fun setupContentPane(pane: JComponent) {
pane.add(left, BorderLayout.WEST)
pane.add(right, BorderLayout.EAST)
}
override fun setupToolWindowPane(pane: JComponent) {
left.topStripe.bottomAnchorDropAreaComponent = pane
left.bottomStripe.bottomAnchorDropAreaComponent = pane
@@ -36,6 +32,14 @@ internal class ToolWindowPaneNewButtonManager(paneId: String, isPrimary: Boolean
right.bottomStripe.bottomAnchorDropAreaComponent = pane
}
override fun wrapWithControls(pane: ToolWindowPane): JComponent {
return JPanel(BorderLayout()).apply {
add(pane, BorderLayout.CENTER)
add(left, BorderLayout.WEST)
add(right, BorderLayout.EAST)
}
}
override fun updateToolStripesVisibility(showButtons: Boolean, state: ToolWindowPaneState): Boolean {
val oldSquareVisible = left.isVisible && right.isVisible
val visible = showButtons || state.isStripesOverlaid

View File

@@ -29,9 +29,6 @@ internal class ToolWindowPaneOldButtonManager(paneId: String) : ToolWindowButton
override val isNewUi: Boolean
get() = false
override fun setupContentPane(pane: JComponent) {
}
override fun setupToolWindowPane(pane: JComponent) {
pane.add(topStripe, JLayeredPane.POPUP_LAYER, -1)
pane.add(leftStripe, JLayeredPane.POPUP_LAYER, -1)
@@ -39,6 +36,10 @@ internal class ToolWindowPaneOldButtonManager(paneId: String) : ToolWindowButton
pane.add(rightStripe, JLayeredPane.POPUP_LAYER, -1)
}
override fun wrapWithControls(pane: ToolWindowPane): JComponent {
return pane
}
override fun updateToolStripesVisibility(showButtons: Boolean, state: ToolWindowPaneState): Boolean {
val oldVisible = leftStripe.isVisible
val visible = showButtons || state.isStripesOverlaid

View File

@@ -108,23 +108,22 @@ internal class DockWindow(
val buttonManager: ToolWindowButtonManager
if (ExperimentalUI.isNewUI()) {
buttonManager = ToolWindowPaneNewButtonManager(paneId, false)
buttonManager.setupContentPane(dockContentUiContainer)
buttonManager.initMoreButton(dockManager.project)
buttonManager.updateResizeState(null)
}
else {
buttonManager = ToolWindowPaneOldButtonManager(paneId)
buttonManager.setupContentPane(dockContentUiContainer)
}
val containerComponent = container.containerComponent
toolWindowPane = ToolWindowPane(frame = frame, coroutineScope = coroutineScope!!.childScope(), paneId = paneId,
buttonManager = buttonManager)
toolWindowPane = ToolWindowPane.create(frame = frame, coroutineScope = coroutineScope!!.childScope(), paneId = paneId,
buttonManager = buttonManager)
val toolWindowManagerImpl = ToolWindowManager.getInstance(dockManager.project) as ToolWindowManagerImpl
toolWindowManagerImpl.addToolWindowPane(toolWindowPane!!, this)
toolWindowPane!!.setDocumentComponent(containerComponent)
dockContentUiContainer.remove(containerComponent)
dockContentUiContainer.add(toolWindowPane!!, BorderLayout.CENTER)
val toolWindowsComponent = buttonManager.wrapWithControls(toolWindowPane!!)
dockContentUiContainer.add(toolWindowsComponent, BorderLayout.CENTER)
// Close the container if it's empty, and we've just removed the last tool window
dockManager.project.messageBus.connect(coroutineScope).subscribe(ToolWindowManagerListener.TOPIC, object : ToolWindowManagerListener {