From f3490e3dbd886ef1685b6dfa8e04a6f48323cc2f Mon Sep 17 00:00:00 2001 From: Denis Zaichenko Date: Fri, 29 Aug 2025 16:18:24 +0200 Subject: [PATCH] IJPL-204250 Fixed race conditions in plugin manager UI initialization GitOrigin-RevId: dadfcff9a52671eeda0fa506bdc2d5e337cda58d --- .../plugins/PluginManagerConfigurable.java | 16 +++++++---- .../marketplace/ApplyPluginsStateResult.kt | 5 ++++ .../newui/DefaultUiPluginManagerController.kt | 4 ++- .../plugins/newui/ListPluginComponent.java | 21 ++++---------- .../ide/plugins/newui/MyPluginModel.kt | 28 +++++++++++++++---- .../PluginModelAsyncOperationsExecutor.kt | 23 --------------- 6 files changed, 46 insertions(+), 51 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurable.java b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurable.java index 4da21e1df127..fcf58ae95e69 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurable.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurable.java @@ -521,7 +521,8 @@ public final class PluginManagerConfigurable try { try { if (project != null) { - addSuggestedGroup(groups, model.getErrors(), model.getSuggestedPlugins(), model.getInstalledPlugins(), model.getInstallationStates()); + addSuggestedGroup(groups, model.getErrors(), model.getSuggestedPlugins(), model.getInstalledPlugins(), + model.getInstallationStates()); } PluginsViewCustomizer.PluginsGroupDescriptor internalPluginsGroupDescriptor = model.getInternalPluginsGroupDescriptor(); if (internalPluginsGroupDescriptor != null) { @@ -1093,9 +1094,14 @@ public final class PluginManagerConfigurable //noinspection ConstantConditions ((SearchUpDownPopupController)myInstalledSearchPanel.controller).setEventHandler(eventHandler); myInstalledPanel.startLoading(); + + PluginsGroup downloaded = + new PluginsGroup(IdeBundle.message("plugins.configurable.downloaded"), PluginsGroupType.INSTALLED); + + PluginsGroup installing = new PluginsGroup(IdeBundle.message("plugins.configurable.installing"), PluginsGroupType.INSTALLING); + myPluginModelFacade.getModel().setDownloadedGroup(myInstalledPanel, downloaded, installing); PluginManagerPanelFactory.INSTANCE.createInstalledPanel(myCoroutineScope, myPluginModelFacade.getModel(), model -> { try { - PluginsGroup installing = new PluginsGroup(IdeBundle.message("plugins.configurable.installing"), PluginsGroupType.INSTALLING); installing.getPreloadedModel().setErrors(model.getErrors()); installing.getPreloadedModel().setPluginInstallationStates(model.getInstallationStates()); installing.addModels(MyPluginModel.getInstallingPlugins()); @@ -1105,8 +1111,6 @@ public final class PluginManagerConfigurable myInstalledPanel.addGroup(installing); } - PluginsGroup downloaded = - new PluginsGroup(IdeBundle.message("plugins.configurable.downloaded"), PluginsGroupType.INSTALLED); downloaded.getPreloadedModel().setErrors(model.getErrors()); downloaded.getPreloadedModel().setPluginInstallationStates(model.getInstallationStates()); downloaded.addModels(model.getInstalledPlugins()); @@ -1481,7 +1485,7 @@ public final class PluginManagerConfigurable private void addSuggestedGroup(@NotNull List groups, @NotNull Map<@NotNull PluginId, - @NotNull List<@NotNull HtmlChunk>> errors, + @NotNull List<@NotNull HtmlChunk>> errors, @NotNull List<@NotNull PluginUiModel> plugins, @NotNull Map<@NotNull PluginId, @NotNull PluginUiModel> installedPlugins, @NotNull Map<@NotNull PluginId, @NotNull PluginInstallationState> installationStates) { @@ -2113,7 +2117,7 @@ public final class PluginManagerConfigurable ApplicationManager.getApplication().invokeLater(() -> { myPluginModelFacade.closeSession(); if (ApplicationManager.getApplication().isExitInProgress()) return; // already shutting down - if(myPluginManagerCustomizer != null) { + if (myPluginManagerCustomizer != null) { myPluginManagerCustomizer.requestRestart(myPluginModelFacade, myTabHeaderComponent); return; } diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/marketplace/ApplyPluginsStateResult.kt b/platform/platform-impl/src/com/intellij/ide/plugins/marketplace/ApplyPluginsStateResult.kt index 94291146c68b..fcc7d6db72b2 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/marketplace/ApplyPluginsStateResult.kt +++ b/platform/platform-impl/src/com/intellij/ide/plugins/marketplace/ApplyPluginsStateResult.kt @@ -1,6 +1,9 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.ide.plugins.marketplace +import com.intellij.ide.plugins.api.PluginDto +import com.intellij.ide.plugins.newui.PluginInstallationState +import com.intellij.ide.plugins.newui.PluginUiModel import com.intellij.openapi.extensions.PluginId import com.intellij.openapi.util.NlsSafe import kotlinx.serialization.Serializable @@ -12,4 +15,6 @@ data class ApplyPluginsStateResult( val pluginsToEnable: Set = emptySet(), var needRestart: Boolean = false, @get:NlsSafe var error: String? = null, + var visiblePlugins: List = emptyList(), + var installationStates: Map = emptyMap(), ) \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/newui/DefaultUiPluginManagerController.kt b/platform/platform-impl/src/com/intellij/ide/plugins/newui/DefaultUiPluginManagerController.kt index 42c5a9f079d6..83e1e7e10530 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/newui/DefaultUiPluginManagerController.kt +++ b/platform/platform-impl/src/com/intellij/ide/plugins/newui/DefaultUiPluginManagerController.kt @@ -381,7 +381,9 @@ object DefaultUiPluginManagerController : UiPluginManagerController { } session.isUiDisposedWithApply = true - return@withContext ApplyPluginsStateResult(pluginsToEnable, needRestart) + val pluginInstallationStates = getPluginInstallationStates() + val visiblePlugins = getVisiblePlugins(false) + return@withContext ApplyPluginsStateResult(pluginsToEnable, needRestart, null, visiblePlugins.map { PluginDto.fromModel(it) }, pluginInstallationStates) } } diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/newui/ListPluginComponent.java b/platform/platform-impl/src/com/intellij/ide/plugins/newui/ListPluginComponent.java index baa1bc501b98..e64fa6e7e982 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/newui/ListPluginComponent.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/newui/ListPluginComponent.java @@ -140,7 +140,7 @@ public final class ListPluginComponent extends JPanel { createTag(); if (myIsAvailable) { - doCreateButtons(pluginInstallationState, listModel.getInstalledModels().get(pluginId)); + createButtons(listModel.getInstalledModels().get(pluginId), pluginInstallationState); createMetricsPanel(); createLicensePanel(); } @@ -250,16 +250,7 @@ public final class ListPluginComponent extends JPanel { ColorButton.setWidth72(myInstallButton); } - private void createButtons() { - PluginModelAsyncOperationsExecutor.INSTANCE.createButtons(myCoroutineScope, this, myPlugin.getPluginId(), myMarketplace, - (state, model) -> { - doCreateButtons(state, model); - fullRepaint(); - return null; - }); - } - - private void doCreateButtons(PluginInstallationState installationState, PluginUiModel installedModel) { + private void createButtons(PluginUiModel installedModel, PluginInstallationState installationState) { installationState = installationState == null ? new PluginInstallationState(false, null) : installationState; if (myMarketplace) { if (installationState.getStatus() == PluginStatus.INSTALLED_AND_REQUIRED_RESTART) { @@ -783,7 +774,7 @@ public final class ListPluginComponent extends JPanel { } public void hideProgress(boolean success, boolean restartRequired, PluginUiModel installedPlugin) { - if(succesefullyFinishedOnce) return; + if (succesefullyFinishedOnce) return; myIndicator = null; myLayout.removeProgressComponent(); @@ -809,7 +800,7 @@ public final class ListPluginComponent extends JPanel { else { myPlugin = myInstalledDescriptorForMarketplace; myInstalledDescriptorForMarketplace = null; - updateButtons(); + updateButtons(installedPlugin, new PluginInstallationState(true, PluginStatus.INSTALLED_WITHOUT_RESTART)); } return; } @@ -865,7 +856,7 @@ public final class ListPluginComponent extends JPanel { PluginsViewCustomizerKt.getListPluginComponentCustomizer().processRemoveButtons(this); } - public void updateButtons() { + public void updateButtons(PluginUiModel installedPlugin, PluginInstallationState state) { if (myIsAvailable) { removeButtons(false); if (myRestartButton != null) { @@ -877,7 +868,7 @@ public final class ListPluginComponent extends JPanel { myAlignButton = null; } myAfterUpdate = false; - createButtons(); + createButtons(installedPlugin, state); if (myUpdateDescriptor != null) { setUpdateDescriptor(myUpdateDescriptor); } diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/newui/MyPluginModel.kt b/platform/platform-impl/src/com/intellij/ide/plugins/newui/MyPluginModel.kt index dcaf4c5454d4..6d71ff95f588 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/newui/MyPluginModel.kt +++ b/platform/platform-impl/src/com/intellij/ide/plugins/newui/MyPluginModel.kt @@ -6,6 +6,7 @@ import com.intellij.externalDependencies.ExternalDependenciesManager import com.intellij.ide.IdeBundle import com.intellij.ide.impl.ProjectUtil.getActiveFrameOrWelcomeScreen import com.intellij.ide.plugins.* +import com.intellij.ide.plugins.marketplace.ApplyPluginsStateResult import com.intellij.ide.plugins.marketplace.CheckErrorsResult import com.intellij.ide.plugins.marketplace.InstallPluginResult import com.intellij.ide.plugins.newui.PluginLogo.getDefault @@ -125,7 +126,7 @@ open class MyPluginModel(project: Project?) : InstalledPluginsTableModel(project } applyResult.pluginsToEnable.forEach { id -> super.setEnabled(id, PluginEnabledState.ENABLED) } myUninstalled.clear() - updateButtons() + updateButtons(applyResult) myPluginManagerCustomizer?.updateAfterModification { } return !applyResult.needRestart } @@ -797,11 +798,26 @@ open class MyPluginModel(project: Project?) : InstalledPluginsTableModel(project myCancelInstallCallback = callback } - private fun updateButtons() { - PluginModelAsyncOperationsExecutor.updateButtons(coroutineScope, - myInstalledPluginComponents, - myMarketplacePluginComponentMap, - myDetailPanels) + private suspend fun updateButtons(applyResult: ApplyPluginsStateResult) { + for (component in myInstalledPluginComponents) { + val pluginId = component.pluginModel.pluginId + val installedPlugin = applyResult.visiblePlugins.firstOrNull { it.pluginId == pluginId } ?: continue + val installationState = applyResult.installationStates[pluginId] ?: continue + component.updateButtons(installedPlugin, installationState) + } + for (plugins in myMarketplacePluginComponentMap.values) { + for (plugin in plugins) { + if (plugin.myInstalledDescriptorForMarketplace != null) { + val pluginId = plugin.pluginModel.pluginId + val installedPlugin = applyResult.visiblePlugins.firstOrNull { it.pluginId == pluginId } ?: continue + val installationState = applyResult.installationStates[pluginId] ?: continue + plugin.updateButtons(installedPlugin, installationState) + } + } + } + for (detailPanel in myDetailPanels) { + detailPanel.updateAll() + } } private fun applyChangedStates(changedStates: Map) { diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginModelAsyncOperationsExecutor.kt b/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginModelAsyncOperationsExecutor.kt index 7394c9f2040e..6d73aa7b4275 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginModelAsyncOperationsExecutor.kt +++ b/platform/platform-impl/src/com/intellij/ide/plugins/newui/PluginModelAsyncOperationsExecutor.kt @@ -136,29 +136,6 @@ internal object PluginModelAsyncOperationsExecutor { } } - fun updateButtons( - cs: CoroutineScope, - installedPluginComponents: List, - pluginComponentsMap: Map>, - detailPanels: List, - ) { - cs.launch(Dispatchers.EDT + ModalityState.any().asContextElement()) { - for (component in installedPluginComponents) { - component.updateButtons() - } - for (plugins in pluginComponentsMap.values) { - for (plugin in plugins) { - if (plugin.myInstalledDescriptorForMarketplace != null) { - plugin.updateButtons() - } - } - } - for (detailPanel in detailPanels) { - detailPanel.updateAll() - } - } - } - fun findPlugins(pluginIds: Set, callback: (Map) -> Unit) { val coroutineScope = service().coroutineScope coroutineScope.launch(Dispatchers.IO) {