diff --git a/plugins/coverage-common/src/com/intellij/coverage/CoverageDataManagerImpl.java b/plugins/coverage-common/src/com/intellij/coverage/CoverageDataManagerImpl.java index 127f4b5eb3a0..66cc61b2d6f3 100644 --- a/plugins/coverage-common/src/com/intellij/coverage/CoverageDataManagerImpl.java +++ b/plugins/coverage-common/src/com/intellij/coverage/CoverageDataManagerImpl.java @@ -124,8 +124,11 @@ public class CoverageDataManagerImpl extends CoverageDataManager implements Disp @Override public CoverageSuitesBundle getCurrentSuitesBundle() { - CoverageSuitesBundle openedSuite = CoverageViewManager.getInstance(myProject).getOpenedSuite(); - if (openedSuite != null) return openedSuite; + CoverageViewManager manager = CoverageViewManager.getInstanceIfCreated(myProject); + if (manager != null) { + CoverageSuitesBundle openedSuite = manager.getOpenedSuite(); + if (openedSuite != null) return openedSuite; + } return myActiveBundles.values().stream().findFirst().orElse(null); } diff --git a/plugins/coverage-common/src/com/intellij/coverage/view/CoverageViewExtension.java b/plugins/coverage-common/src/com/intellij/coverage/view/CoverageViewExtension.java index ff89a156f107..c7b638f78d33 100644 --- a/plugins/coverage-common/src/com/intellij/coverage/view/CoverageViewExtension.java +++ b/plugins/coverage-common/src/com/intellij/coverage/view/CoverageViewExtension.java @@ -26,7 +26,6 @@ public abstract class CoverageViewExtension { protected final CoverageSuitesBundle mySuitesBundle; protected final CoverageViewManager.StateBean myStateBean; protected final CoverageDataManager myCoverageDataManager; - protected final CoverageViewManager myCoverageViewManager; public CoverageViewExtension(@NotNull Project project, CoverageSuitesBundle suitesBundle, CoverageViewManager.StateBean stateBean) { assert !project.isDefault() : "Should not run coverage for default project"; @@ -34,7 +33,6 @@ public abstract class CoverageViewExtension { mySuitesBundle = suitesBundle; myStateBean = stateBean; myCoverageDataManager = CoverageDataManager.getInstance(myProject); - myCoverageViewManager = CoverageViewManager.getInstance(myProject); } @Nullable diff --git a/plugins/coverage-common/src/com/intellij/coverage/view/CoverageViewManager.kt b/plugins/coverage-common/src/com/intellij/coverage/view/CoverageViewManager.kt index 5709d51a9024..2c2883dce59b 100644 --- a/plugins/coverage-common/src/com/intellij/coverage/view/CoverageViewManager.kt +++ b/plugins/coverage-common/src/com/intellij/coverage/view/CoverageViewManager.kt @@ -7,38 +7,38 @@ import com.intellij.coverage.CoverageSuitesBundle import com.intellij.coverage.view.CoverageViewManager.StateBean import com.intellij.icons.AllIcons import com.intellij.openapi.Disposable -import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.runInEdt import com.intellij.openapi.components.* -import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project import com.intellij.openapi.util.NlsSafe import com.intellij.openapi.wm.ToolWindowAnchor import com.intellij.openapi.wm.ToolWindowManager -import com.intellij.ui.AppUIUtil.invokeLaterIfProjectAlive -import com.intellij.ui.content.Content import com.intellij.ui.content.ContentManager +import com.intellij.util.concurrency.annotations.RequiresEdt import com.intellij.util.containers.DisposableWrapperList import org.jetbrains.annotations.NonNls @State(name = "CoverageViewManager", storages = [Storage(StoragePathMacros.PRODUCT_WORKSPACE_FILE)]) @Service(Service.Level.PROJECT) class CoverageViewManager(private val myProject: Project) : PersistentStateComponent, Disposable.Default { - private var myContentManager: ContentManager? = null var stateBean = StateBean() private set private val myViews: MutableMap = HashMap() - init { - invokeLaterIfProjectAlive(myProject) { - val toolWindow = ToolWindowManager.getInstance(myProject).registerToolWindow(TOOLWINDOW_ID) { - sideTool = true - icon = AllIcons.Toolwindows.ToolWindowCoverage - anchor = ToolWindowAnchor.RIGHT - stripeTitle = CoverageBundle.messagePointer("coverage.view.title") - } - toolWindow.helpId = CoverageView.HELP_ID - myContentManager = toolWindow.contentManager + @Volatile + private var myContentManager: ContentManager? = null + + @RequiresEdt + private fun initializeToolWindow(): ContentManager? { + if (myProject.isDisposed || !myProject.isOpen) return null + val toolWindow = ToolWindowManager.getInstance(myProject).registerToolWindow(TOOLWINDOW_ID) { + sideTool = true + icon = AllIcons.Toolwindows.ToolWindowCoverage + anchor = ToolWindowAnchor.RIGHT + stripeTitle = CoverageBundle.messagePointer("coverage.view.title") } + toolWindow.helpId = CoverageView.HELP_ID + return toolWindow.contentManager } override fun getState(): StateBean { @@ -60,41 +60,35 @@ class CoverageViewManager(private val myProject: Project) : PersistentStateCompo val openedSuite: CoverageSuitesBundle? get() { - val manager = myContentManager - if (manager == null) return null + val manager = myContentManager ?: return null val selectedContent = manager.selectedContent if (selectedContent == null) return null - for ((key, value) in myViews) { - val content = manager.getContent(value) - if (content === selectedContent) { - return key - } - } - return null + return myViews.firstNotNullOfOrNull { (suite, view) -> suite.takeIf { selectedContent === manager.getContent(view) } } } + @RequiresEdt fun activateToolwindow(view: CoverageView) { - myContentManager!!.setSelectedContent(myContentManager!!.getContent(view)) + val manager = myContentManager ?: return + manager.setSelectedContent(manager.getContent(view)) val toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(TOOLWINDOW_ID) - LOG.assertTrue(toolWindow != null) - toolWindow!!.activate(null, false) + ?: error("Coverage toolwindow is not registered") + toolWindow.activate(null, false) } - @Synchronized + @RequiresEdt fun createView(suitesBundle: CoverageSuitesBundle, activate: Boolean) { var coverageView = myViews[suitesBundle] - val content: Content - if (coverageView == null) { + val manager = getContentManager() ?: return + val content = if (coverageView == null) { coverageView = CoverageView(myProject, suitesBundle, stateBean) myViews[suitesBundle] = coverageView - content = myContentManager!!.factory.createContent(coverageView, getDisplayName(suitesBundle), false) - myContentManager!!.addContent(content) + manager.factory.createContent(coverageView, getDisplayName(suitesBundle), false) + .also { manager.addContent(it) } } else { - content = myContentManager!!.getContent(coverageView) + manager.getContent(coverageView) } - myContentManager!!.setSelectedContent(content) - + manager.setSelectedContent(content) if (CoverageOptionsProvider.getInstance(myProject).activateViewOnRun() && activate) { activateToolwindow(coverageView) } @@ -104,10 +98,11 @@ class CoverageViewManager(private val myProject: Project) : PersistentStateCompo val oldView = myViews.remove(suitesBundle) if (oldView != null) { oldView.saveSize() - ApplicationManager.getApplication().invokeLater { - val content = myContentManager!!.getContent(oldView) + runInEdt { + val manager = myContentManager ?: return@runInEdt + val content = manager.getContent(oldView) if (content != null) { - myContentManager!!.removeContent(content, false) + manager.removeContent(content, false) } } } @@ -119,20 +114,30 @@ class CoverageViewManager(private val myProject: Project) : PersistentStateCompo return getView(suitesBundle) } - @Deprecated("Use activateToolwindow(CoverageView) instead", ReplaceWith("if (activate) activateToolwindow(view)")) fun activateToolwindow(view: CoverageView, activate: Boolean) { if (activate) { - activateToolwindow(view) + runInEdt { + activateToolwindow(view) + } } } - @Deprecated("Use createView instead", ReplaceWith("createView(suitesBundle, activate)")) + @RequiresEdt fun createToolWindow(suitesBundle: CoverageSuitesBundle, activate: Boolean) { createView(suitesBundle, activate) } + /** + * Call to this method initializes the coverage tool window. + */ + @RequiresEdt + private fun getContentManager(): ContentManager? { + myContentManager?.also { return it } + return initializeToolWindow()?.also { myContentManager = it } + } + class StateBean { private var myFlattenPackages = false @@ -204,12 +209,14 @@ class CoverageViewManager(private val myProject: Project) : PersistentStateCompo } companion object { - private val LOG = Logger.getInstance(CoverageViewManager::class.java) const val TOOLWINDOW_ID: @NonNls String = "Coverage" @JvmStatic fun getInstance(project: Project): CoverageViewManager = project.service() + @JvmStatic + fun getInstanceIfCreated(project: Project): CoverageViewManager? = project.serviceIfCreated() + fun getDisplayName(suitesBundle: CoverageSuitesBundle): @NlsSafe String? { val configuration = suitesBundle.runConfiguration return configuration?.name ?: suitesBundle.presentableName diff --git a/plugins/coverage-common/src/com/intellij/coverage/view/CoverageViewSuiteListener.java b/plugins/coverage-common/src/com/intellij/coverage/view/CoverageViewSuiteListener.java index 6373941bed78..d560f54731ac 100644 --- a/plugins/coverage-common/src/com/intellij/coverage/view/CoverageViewSuiteListener.java +++ b/plugins/coverage-common/src/com/intellij/coverage/view/CoverageViewSuiteListener.java @@ -29,12 +29,6 @@ public class CoverageViewSuiteListener implements CoverageSuiteListener { myProject = project; } - @Override - public void beforeSuiteChosen() { - // Call here to ensure that toolwindow is created - CoverageViewManager.getInstance(myProject); - } - @Override public void coverageDataCalculated(@NotNull CoverageSuitesBundle suitesBundle) { CoverageViewManager viewManager = CoverageViewManager.getInstance(myProject); diff --git a/plugins/coverage-common/src/com/intellij/coverage/view/SelectInCoverageView.java b/plugins/coverage-common/src/com/intellij/coverage/view/SelectInCoverageView.java index 2f8240ac17c1..67432e125342 100644 --- a/plugins/coverage-common/src/com/intellij/coverage/view/SelectInCoverageView.java +++ b/plugins/coverage-common/src/com/intellij/coverage/view/SelectInCoverageView.java @@ -7,6 +7,7 @@ import com.intellij.coverage.CoverageSuitesBundle; import com.intellij.ide.SelectInContext; import com.intellij.ide.SelectInTarget; import com.intellij.ide.StandardTargetWeights; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; @@ -43,7 +44,7 @@ public final class SelectInCoverageView implements SelectInTarget { final CoverageView coverageView = coverageViewManager.getView(suitesBundle); coverageView.select(context.getVirtualFile()); if (requestFocus) { - coverageViewManager.activateToolwindow(coverageView); + ApplicationManager.getApplication().invokeLater(() -> coverageViewManager.activateToolwindow(coverageView)); } } } diff --git a/plugins/coverage/testSrc/com/intellij/coverage/view/CoverageViewTest.kt b/plugins/coverage/testSrc/com/intellij/coverage/view/CoverageViewTest.kt index 1f9109e5fe60..72d416d7a9e4 100644 --- a/plugins/coverage/testSrc/com/intellij/coverage/view/CoverageViewTest.kt +++ b/plugins/coverage/testSrc/com/intellij/coverage/view/CoverageViewTest.kt @@ -3,7 +3,7 @@ package com.intellij.coverage.view import com.intellij.coverage.CoverageIntegrationBaseTest import com.intellij.coverage.CoverageSuitesBundle -import com.intellij.openapi.wm.ToolWindowManager.Companion.getInstance +import com.intellij.openapi.wm.ToolWindowManager import kotlinx.coroutines.runBlocking import org.junit.Assert import org.junit.Test @@ -19,6 +19,7 @@ class CoverageViewTest : CoverageIntegrationBaseTest() { fun `test coverage toolwindow exists`() = runBlocking { val bundle = loadIJSuite() + assertToolWindowDoesNotExist() openSuiteAndWait(bundle) assertToolWindowExists() Assert.assertNotNull(findCoverageView(bundle)) @@ -33,6 +34,7 @@ class CoverageViewTest : CoverageIntegrationBaseTest() { val ijSuite = loadIJSuite() val xmlSuite = loadXMLSuite() + assertToolWindowDoesNotExist() openSuiteAndWait(ijSuite) assertToolWindowExists() Assert.assertNotNull(findCoverageView(ijSuite)) @@ -54,10 +56,17 @@ class CoverageViewTest : CoverageIntegrationBaseTest() { Assert.assertNull(findCoverageView(xmlSuite)) } - private fun findCoverageView(bundle: CoverageSuitesBundle): CoverageView? = - CoverageViewManager.getInstance(myProject).getToolwindow(bundle) - - private fun assertToolWindowExists() { - Assert.assertNotNull(getInstance(myProject).getToolWindow(CoverageViewManager.TOOLWINDOW_ID)) + @Test(timeout = TIMEOUT_MS) + fun `test call to service does not create tool window`() { + assertToolWindowDoesNotExist() + val manager = CoverageViewManager.getInstance(myProject) + assertToolWindowDoesNotExist() + manager.openedSuite + assertToolWindowDoesNotExist() } + + private fun findCoverageView(bundle: CoverageSuitesBundle): CoverageView? = CoverageViewManager.getInstance(myProject).getView(bundle) + private fun getCoverageToolWindow() = ToolWindowManager.getInstance(myProject).getToolWindow(CoverageViewManager.TOOLWINDOW_ID) + private fun assertToolWindowExists() = Assert.assertNotNull(getCoverageToolWindow()) + private fun assertToolWindowDoesNotExist() = Assert.assertNull(getCoverageToolWindow()) }