diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPluginVfsListener.kt b/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPluginVfsListener.kt new file mode 100644 index 000000000000..5024d190f4ac --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPluginVfsListener.kt @@ -0,0 +1,85 @@ +// 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.ide.FrameStateListener +import com.intellij.notification.NotificationType +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.PathManager +import com.intellij.openapi.extensions.PluginDescriptor +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vfs.AsyncFileListener +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.vfs.VfsUtilCore +import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent +import com.intellij.openapi.vfs.newvfs.events.VFileEvent + +/** + * @author yole + */ +private const val AUTO_RELOAD_PLUGINS_SYSTEM_PROPERTY = "idea.auto.reload.plugins" + +class DynamicPluginVfsListener : AsyncFileListener { + init { + if (System.getProperty(AUTO_RELOAD_PLUGINS_SYSTEM_PROPERTY) != null) { + val pluginsPath = PathManager.getPluginsPath() + LocalFileSystem.getInstance().addRootToWatch(pluginsPath, true) + val pluginsRoot = LocalFileSystem.getInstance().findFileByPath(pluginsPath) + if (pluginsRoot != null) { + // ensure all plugins are in VFS + VfsUtilCore.processFilesRecursively(pluginsRoot) { true } + } + } + } + + override fun prepareChange(events: MutableList): AsyncFileListener.ChangeApplier? { + if (System.getProperty(AUTO_RELOAD_PLUGINS_SYSTEM_PROPERTY) == null) return null + + val pluginsToReload = hashSetOf() + for (event in events) { + if (!event.isFromRefresh) continue + if (event is VFileContentChangeEvent) { + findPluginByPath(event.path)?.let { pluginsToReload.add(it) } + } + } + val descriptorsToReload = pluginsToReload + .filter { it.isEnabled } + .map { PluginEnabler.loadFullDescriptor(it) } + .filter { DynamicPlugins.allowLoadUnloadWithoutRestart(it) } + + if (descriptorsToReload.isEmpty()) return null + + return object : AsyncFileListener.ChangeApplier { + override fun afterVfsChange() { + ApplicationManager.getApplication().invokeLater { + val reloaded = mutableListOf() + for (pluginDescriptor in descriptorsToReload) { + if (!DynamicPlugins.unloadPlugin(pluginDescriptor, isUpdate = true)) { + continue + } + reloaded.add(pluginDescriptor.name) + DynamicPlugins.loadPlugin(pluginDescriptor, false) + } + if (reloaded.isNotEmpty()) { + DynamicPlugins.notify("${reloaded.joinToString()} reloaded successfully", NotificationType.INFORMATION) + } + } + } + } + } + + private fun findPluginByPath(path: String): PluginDescriptor? { + if (!FileUtil.isAncestor(PathManager.getPluginsPath(), path, false)) return null + return PluginManager.getPlugins().firstOrNull { + FileUtil.isAncestor(it.path.absolutePath, path, false) + } + } +} + +class DynamicPluginsFrameStateListener : FrameStateListener { + override fun onFrameActivated() { + if (System.getProperty(AUTO_RELOAD_PLUGINS_SYSTEM_PROPERTY) == null) return + + val pluginsRoot = LocalFileSystem.getInstance().findFileByPath(PathManager.getPluginsPath()) + pluginsRoot?.refresh(true, true) + } +} 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 dea7daa81c55..b8801f37543a 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPlugins.kt +++ b/platform/platform-impl/src/com/intellij/ide/plugins/DynamicPlugins.kt @@ -357,8 +357,7 @@ object DynamicPlugins { val snapshotDate = SimpleDateFormat("dd.MM.yyyy_HH.mm.ss").format(Date()) val snapshotPath = "$snapshotFolder/unload-${pluginDescriptor.pluginId}-$snapshotDate.hprof" MemoryDumpHelper.captureMemoryDump(snapshotPath) - GROUP.createNotification("Captured memory snapshot on plugin unload fail: $snapshotPath", - NotificationType.WARNING).notify(null) + notify("Captured memory snapshot on plugin unload fail: $snapshotPath", NotificationType.WARNING) } LOG.info("Plugin ${pluginDescriptor.pluginId} is not unload-safe because class loader cannot be unloaded") } @@ -367,6 +366,10 @@ object DynamicPlugins { } } + internal fun notify(text: String, notificationType: NotificationType) { + GROUP.createNotification(text, notificationType).notify(null) + } + private fun unloadPluginDescriptor(pluginDescriptor: IdeaPluginDescriptorImpl, loadedPluginDescriptor: IdeaPluginDescriptorImpl) { val application = ApplicationManager.getApplication() as ApplicationImpl diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index 7c372edd6db4..f6f73f184384 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -901,5 +901,9 @@ + + + +