IJPL-204250 Fixed race conditions in plugin manager UI initialization

GitOrigin-RevId: dadfcff9a52671eeda0fa506bdc2d5e337cda58d
This commit is contained in:
Denis Zaichenko
2025-08-29 17:57:07 +00:00
committed by intellij-monorepo-bot
parent dc8fe0fa76
commit f3490e3dbd
6 changed files with 46 additions and 51 deletions
@@ -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<? super PluginsGroup> 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;
}
@@ -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<PluginId> = emptySet(),
var needRestart: Boolean = false,
@get:NlsSafe var error: String? = null,
var visiblePlugins: List<PluginDto> = emptyList(),
var installationStates: Map<PluginId, PluginInstallationState> = emptyMap(),
)
@@ -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)
}
}
@@ -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);
}
@@ -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<PluginId, Boolean>) {
@@ -136,29 +136,6 @@ internal object PluginModelAsyncOperationsExecutor {
}
}
fun updateButtons(
cs: CoroutineScope,
installedPluginComponents: List<ListPluginComponent>,
pluginComponentsMap: Map<PluginId, List<ListPluginComponent>>,
detailPanels: List<PluginDetailsPageComponent>,
) {
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<PluginId>, callback: (Map<PluginId, PluginUiModel>) -> Unit) {
val coroutineScope = service<CoreUiCoroutineScopeHolder>().coroutineScope
coroutineScope.launch(Dispatchers.IO) {