From a37dafcbad444c67854ca2266f4f7cf5e5874cf2 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 30 Jan 2020 18:15:17 +0100 Subject: [PATCH] Allow enabling/disabling plugins from Find Action without restart (IDEA-231814) GitOrigin-RevId: 80e6b6e09943944301064b48dfa8b4a72b542fff --- .../intellij/ide/plugins/DynamicPlugins.kt | 3 + .../intellij/ide/plugins/PluginEnabler.java | 88 +++++++++++++++++++ .../ide/plugins/newui/MyPluginModel.java | 49 ++--------- .../ide/ui/PluginBooleanOptionDescriptor.java | 8 +- .../ide/plugins/DynamicPluginsTest.kt | 33 +++++-- .../ide/plugins/DynamicPluginsTestUtil.kt | 6 +- 6 files changed, 131 insertions(+), 56 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/ide/plugins/PluginEnabler.java diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPlugins.kt b/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPlugins.kt index 2719b4d0bcc0..dea7daa81c55 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPlugins.kt +++ b/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPlugins.kt @@ -347,6 +347,9 @@ object DynamicPlugins { } finally { IdeEventQueue.getInstance().flushQueue() + if (ApplicationManager.getApplication().isUnitTestMode && !(loadedPluginDescriptor.pluginClassLoader is PluginClassLoader)) { + return true + } val classLoaderUnloaded = loadedPluginDescriptor.unloadClassLoader() if (!classLoaderUnloaded) { if (Registry.`is`("ide.plugins.snapshot.on.unload.fail") && MemoryDumpHelper.memoryDumpAvailable() && !ApplicationManager.getApplication().isUnitTestMode) { diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/PluginEnabler.java b/platform/platform-impl/src/com/intellij/ide/plugins/PluginEnabler.java new file mode 100644 index 000000000000..a48266d4061e --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/plugins/PluginEnabler.java @@ -0,0 +1,88 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.ide.plugins; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.PluginDescriptor; +import com.intellij.openapi.extensions.PluginId; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * @author yole + */ +public class PluginEnabler { + private static final Logger LOG = Logger.getInstance(PluginEnabler.class); + + public static boolean enablePlugins(Collection plugins, boolean enable) { + return updatePluginEnabledState(enable ? plugins : Collections.emptyList(), + enable ? Collections.emptyList() : plugins, + null); + } + + /** + * @return true if the requested enabled state was applied without restart, false if restart is required + */ + public static boolean updatePluginEnabledState(Collection pluginsToEnable, + Collection pluginsToDisable, + @Nullable JComponent parentComponent) { + List pluginDescriptorsToEnable = ContainerUtil.map(pluginsToEnable, PluginEnabler::loadFullDescriptor); + List pluginDescriptorsToDisable = ContainerUtil.map(pluginsToDisable, PluginEnabler::loadFullDescriptor); + + Set disabledIds = PluginManagerCore.getDisabledIds(); + for (PluginDescriptor descriptor : pluginsToEnable) { + descriptor.setEnabled(true); + disabledIds.remove(descriptor.getPluginId()); + } + for (PluginDescriptor descriptor : pluginsToDisable) { + descriptor.setEnabled(false); + disabledIds.add(descriptor.getPluginId()); + } + + try { + PluginManagerCore.saveDisabledPlugins(disabledIds, false); + } + catch (IOException e) { + PluginManagerMain.LOG.error(e); + } + + if (ContainerUtil.all(pluginDescriptorsToDisable, (plugin) -> DynamicPlugins.allowLoadUnloadWithoutRestart(plugin)) && + ContainerUtil.all(pluginDescriptorsToEnable, (plugin) -> DynamicPlugins.allowLoadUnloadWithoutRestart(plugin))) { + boolean needRestart = false; + for (IdeaPluginDescriptor descriptor : pluginDescriptorsToDisable) { + if (!DynamicPlugins.unloadPluginWithProgress(parentComponent, (IdeaPluginDescriptorImpl)descriptor, true)) { + needRestart = true; + break; + } + } + + if (!needRestart) { + for (IdeaPluginDescriptor descriptor : pluginDescriptorsToEnable) { + DynamicPlugins.loadPlugin((IdeaPluginDescriptorImpl)descriptor, true); + } + return true; + } + } + return false; + } + + @NotNull + public static IdeaPluginDescriptorImpl loadFullDescriptor(PluginDescriptor descriptor) { + // PluginDescriptor fields are cleaned after the plugin is loaded, so we need to reload the descriptor to check if it's dynamic + IdeaPluginDescriptorImpl fullDescriptor = + PluginManager.loadDescriptor(((IdeaPluginDescriptorImpl)descriptor).getPluginPath(), PluginManagerCore.PLUGIN_XML, Collections + .emptySet()); + if (fullDescriptor == null) { + LOG.error("Could not load full descriptor for plugin " + descriptor.getPath()); + fullDescriptor = (IdeaPluginDescriptorImpl)descriptor; + } + return fullDescriptor; + } +} diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/newui/MyPluginModel.java b/platform/platform-impl/src/com/intellij/ide/plugins/newui/MyPluginModel.java index ee8ab3738c25..b48ffac0ad38 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/newui/MyPluginModel.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/newui/MyPluginModel.java @@ -190,8 +190,8 @@ public class MyPluginModel extends InstalledPluginsTableModel implements PluginM } private boolean applyEnableDisablePlugins(JComponent parentComponent, Map enabledMap) { - List pluginDescriptorsToDisable = new ArrayList<>(); - List pluginDescriptorsToEnable = new ArrayList<>(); + List pluginDescriptorsToDisable = new ArrayList<>(); + List pluginDescriptorsToEnable = new ArrayList<>(); int rowCount = getRowCount(); for (int i = 0; i < rowCount; i++) { @@ -202,55 +202,16 @@ public class MyPluginModel extends InstalledPluginsTableModel implements PluginM boolean enabled = isEnabled(descriptor.getPluginId()); if (enabled != descriptor.isEnabled()) { - // PluginDescriptor fields are cleaned after the plugin is loaded, so we need to reload the descriptor to check if it's dynamic - IdeaPluginDescriptorImpl fullDescriptor = PluginManager.loadDescriptor(((IdeaPluginDescriptorImpl)descriptor).getPluginPath(), PluginManagerCore.PLUGIN_XML, Collections.emptySet()); - if (fullDescriptor == null) { - LOG.error("Could not load full descriptor for plugin " + descriptor.getPath()); - fullDescriptor = (IdeaPluginDescriptorImpl)descriptor; - } if (!enabled) { - pluginDescriptorsToDisable.add(fullDescriptor); + pluginDescriptorsToDisable.add(descriptor); } else { - pluginDescriptorsToEnable.add(fullDescriptor); + pluginDescriptorsToEnable.add(descriptor); } } - descriptor.setEnabled(enabled); } - List disableIds = new ArrayList<>(); - for (Map.Entry entry : enabledMap.entrySet()) { - Boolean enabled = entry.getValue(); - if (enabled != null && !enabled) { - disableIds.add(entry.getKey()); - } - } - - try { - PluginManagerCore.saveDisabledPlugins(disableIds, false); - } - catch (IOException e) { - PluginManagerMain.LOG.error(e); - } - - if (ContainerUtil.all(pluginDescriptorsToDisable, (plugin) -> DynamicPlugins.allowLoadUnloadWithoutRestart(plugin)) && - ContainerUtil.all(pluginDescriptorsToEnable, (plugin) -> DynamicPlugins.allowLoadUnloadWithoutRestart(plugin))) { - boolean needRestart = false; - for (IdeaPluginDescriptor descriptor : pluginDescriptorsToDisable) { - if (!DynamicPlugins.unloadPluginWithProgress(parentComponent, (IdeaPluginDescriptorImpl)descriptor, true)) { - needRestart = true; - break; - } - } - - if (!needRestart) { - for (IdeaPluginDescriptor descriptor : pluginDescriptorsToEnable) { - DynamicPlugins.loadPlugin((IdeaPluginDescriptorImpl)descriptor, true); - } - return true; - } - } - return false; + return PluginEnabler.updatePluginEnabledState(pluginDescriptorsToDisable, pluginDescriptorsToEnable, parentComponent); } public void pluginInstalledFromDisk(@NotNull PluginInstallCallbackData callbackData) { diff --git a/platform/platform-impl/src/com/intellij/ide/ui/PluginBooleanOptionDescriptor.java b/platform/platform-impl/src/com/intellij/ide/ui/PluginBooleanOptionDescriptor.java index d76e8cfe8ba3..21213a67df5a 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/PluginBooleanOptionDescriptor.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/PluginBooleanOptionDescriptor.java @@ -1,4 +1,4 @@ -// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.ui; import com.intellij.ide.IdeBundle; @@ -47,12 +47,14 @@ final class PluginBooleanOptionDescriptor extends BooleanOptionDescription { @Override public void setOptionState(boolean enabled) { Collection autoSwitchedIds = enabled ? getPluginsIdsToEnable(plugin) : getPluginsIdsToDisable(plugin); - PluginManager.getInstance().enablePlugins(autoSwitchedIds, enabled); + boolean enabledWithoutRestart = PluginEnabler.enablePlugins(autoSwitchedIds, enabled); if (autoSwitchedIds.size() > 1) { showAutoSwitchNotification(autoSwitchedIds, enabled); } - ourRestartNeededNotifier.showNotification(); + if (!enabledWithoutRestart) { + ourRestartNeededNotifier.showNotification(); + } } private void showAutoSwitchNotification(@NotNull Collection autoSwitchedPlugins, boolean enabled) { diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTest.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTest.kt index 2138ee0d192f..42e55e473969 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTest.kt @@ -32,12 +32,10 @@ import com.intellij.testFramework.assertions.Assertions.assertThat import com.intellij.testFramework.rules.InMemoryFsRule import com.intellij.ui.switcher.ShowQuickActionPopupAction import com.intellij.util.KeyedLazyInstanceEP -import com.intellij.util.SystemProperties import com.intellij.util.io.write import com.intellij.util.ui.UIUtil import com.intellij.util.xmlb.annotations.Attribute import org.junit.* -import org.junit.Assume.assumeFalse import java.io.File @RunsInEdt @@ -52,12 +50,6 @@ class DynamicPluginsTest { val projectRule = ProjectRule() val receivedNotifications = mutableListOf() - - @JvmStatic - @BeforeClass - fun check() { - assumeFalse(SystemProperties.getBooleanProperty("skip.DynamicPluginsTest", true)) - } } @Rule @@ -361,6 +353,31 @@ class DynamicPluginsTest { Disposer.dispose(disposable) } + @Test + fun disableWithoutRestart() { + val pluginId = "disableWithoutRestart" + System.currentTimeMillis() + val disposable = loadPluginWithText(""" + + $pluginId + + + + """.trimIndent(), DynamicPlugins::class.java.classLoader) + assertThat(ServiceManager.getService(MyPersistentComponent::class.java)).isNotNull() + + val pluginDescriptor = PluginManagerCore.getPlugin(PluginId.getId(pluginId))!! + val success = PluginEnabler.updatePluginEnabledState(emptyList(), listOf(pluginDescriptor), null) + assertThat(success).isTrue() + assertThat(pluginDescriptor.isEnabled).isFalse() + assertThat(ServiceManager.getService(MyPersistentComponent::class.java)).isNull() + + assertThat(PluginEnabler.updatePluginEnabledState(listOf(pluginDescriptor), emptyList(), null)).isTrue() + assertThat(pluginDescriptor.isEnabled).isTrue() + assertThat(ServiceManager.getService(MyPersistentComponent::class.java)).isNotNull() + + Disposer.dispose(disposable) + } + private fun loadPluginWithOptionalDependency(pluginXmlText: String, optionalDependencyDescriptorText: String): Disposable { val directory = FileUtil.createTempDirectory("test", "test", true) val plugin = File(directory, "/plugin/META-INF/plugin.xml") diff --git a/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTestUtil.kt b/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTestUtil.kt index 7403b74bc4ef..8928015de841 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTestUtil.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/plugins/DynamicPluginsTestUtil.kt @@ -29,7 +29,11 @@ fun loadDescriptorInTest(dir: Path, disabledPlugins: Set = emptySet()) } @JvmOverloads -fun loadExtensionWithText(extensionTag: String, loader: ClassLoader, ns: String = "com.intellij"): Disposable { +fun loadExtensionWithText( + extensionTag: String, + loader: ClassLoader = DynamicPlugins::class.java.classLoader, + ns: String = "com.intellij" +): Disposable { val name = "test" + abs(extensionTag.hashCode()) val text = """ $name