Allow enabling/disabling plugins from Find Action without restart (IDEA-231814)

GitOrigin-RevId: 80e6b6e09943944301064b48dfa8b4a72b542fff
This commit is contained in:
Dmitry Jemerov
2020-01-30 17:36:48 +00:00
committed by intellij-monorepo-bot
parent 002f266c66
commit a37dafcbad
6 changed files with 131 additions and 56 deletions
@@ -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) {
@@ -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<IdeaPluginDescriptor> 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<IdeaPluginDescriptor> pluginsToEnable,
Collection<IdeaPluginDescriptor> pluginsToDisable,
@Nullable JComponent parentComponent) {
List<IdeaPluginDescriptorImpl> pluginDescriptorsToEnable = ContainerUtil.map(pluginsToEnable, PluginEnabler::loadFullDescriptor);
List<IdeaPluginDescriptorImpl> pluginDescriptorsToDisable = ContainerUtil.map(pluginsToDisable, PluginEnabler::loadFullDescriptor);
Set<PluginId> 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;
}
}
@@ -190,8 +190,8 @@ public class MyPluginModel extends InstalledPluginsTableModel implements PluginM
}
private boolean applyEnableDisablePlugins(JComponent parentComponent, Map<PluginId, Boolean> enabledMap) {
List<IdeaPluginDescriptorImpl> pluginDescriptorsToDisable = new ArrayList<>();
List<IdeaPluginDescriptorImpl> pluginDescriptorsToEnable = new ArrayList<>();
List<IdeaPluginDescriptor> pluginDescriptorsToDisable = new ArrayList<>();
List<IdeaPluginDescriptor> 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<PluginId> disableIds = new ArrayList<>();
for (Map.Entry<PluginId, Boolean> 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) {
@@ -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<IdeaPluginDescriptor> 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<IdeaPluginDescriptor> autoSwitchedPlugins, boolean enabled) {
@@ -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<UISettings>()
@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("""
<idea-plugin>
<id>$pluginId</id>
<extensions defaultExtensionNs="com.intellij">
<applicationService serviceImplementation="${MyPersistentComponent::class.java.name}"/>
</extensions>
</idea-plugin>""".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")
@@ -29,7 +29,11 @@ fun loadDescriptorInTest(dir: Path, disabledPlugins: Set<PluginId> = 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 = """<idea-plugin>
<name>$name</name>