350b5839fc34c703b88d96b77dada750a0a25972
[idea/community.git] / platform / platform-impl / src / com / intellij / idea / ApplicationLoader.kt
1 // 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.
2 @file:JvmName("ApplicationLoader")
3 package com.intellij.idea
4
5 import com.intellij.diagnostic.*
6 import com.intellij.diagnostic.StartUpMeasurer.Phases
7 import com.intellij.featureStatistics.fusCollectors.LifecycleUsageTriggerCollector
8 import com.intellij.icons.AllIcons
9 import com.intellij.ide.*
10 import com.intellij.ide.customize.CustomizeIDEWizardDialog
11 import com.intellij.ide.customize.CustomizeIDEWizardStepsProvider
12 import com.intellij.ide.impl.OpenProjectTask
13 import com.intellij.ide.impl.ProjectUtil
14 import com.intellij.ide.plugins.*
15 import com.intellij.ide.ui.customization.CustomActionsSchema
16 import com.intellij.notification.Notification
17 import com.intellij.notification.NotificationType
18 import com.intellij.notification.Notifications
19 import com.intellij.openapi.actionSystem.IdeActions
20 import com.intellij.openapi.application.*
21 import com.intellij.openapi.application.impl.ApplicationImpl
22 import com.intellij.openapi.components.stateStore
23 import com.intellij.openapi.diagnostic.Logger
24 import com.intellij.openapi.keymap.KeymapManager
25 import com.intellij.openapi.keymap.impl.SystemShortcuts
26 import com.intellij.openapi.progress.ProcessCanceledException
27 import com.intellij.openapi.project.Project
28 import com.intellij.openapi.ui.DialogEarthquakeShaker
29 import com.intellij.openapi.util.IconLoader
30 import com.intellij.openapi.util.SystemInfo
31 import com.intellij.openapi.util.SystemPropertyBean
32 import com.intellij.openapi.util.io.FileUtilRt
33 import com.intellij.openapi.util.registry.RegistryKeyBean
34 import com.intellij.openapi.wm.IdeFrame
35 import com.intellij.openapi.wm.WeakFocusStackManager
36 import com.intellij.openapi.wm.WindowManager
37 import com.intellij.openapi.wm.ex.WindowManagerEx
38 import com.intellij.openapi.wm.impl.SystemDock
39 import com.intellij.openapi.wm.impl.WindowManagerImpl
40 import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeFrame
41 import com.intellij.platform.PlatformProjectOpenProcessor
42 import com.intellij.ui.AnimatedIcon
43 import com.intellij.ui.AppIcon
44 import com.intellij.ui.AppUIUtil
45 import com.intellij.ui.CustomProtocolHandler
46 import com.intellij.ui.mac.MacOSApplicationProvider
47 import com.intellij.ui.mac.foundation.Foundation
48 import com.intellij.ui.mac.touchbar.TouchBarsManager
49 import com.intellij.util.ArrayUtilRt
50 import com.intellij.util.concurrency.AppExecutorUtil
51 import com.intellij.util.io.exists
52 import com.intellij.util.ui.AsyncProcessIcon
53 import com.intellij.util.ui.accessibility.ScreenReader
54 import net.miginfocom.layout.PlatformDefaults
55 import org.jetbrains.annotations.ApiStatus
56 import java.awt.EventQueue
57 import java.awt.GraphicsEnvironment
58 import java.beans.PropertyChangeListener
59 import java.io.File
60 import java.io.IOException
61 import java.nio.file.Paths
62 import java.util.*
63 import java.util.concurrent.CompletableFuture
64 import java.util.concurrent.Future
65 import java.util.concurrent.atomic.AtomicReference
66 import javax.swing.JFrame
67 import javax.swing.JOptionPane
68 import kotlin.system.exitProcess
69
70 private val SAFE_JAVA_ENV_PARAMETERS = arrayOf(JetBrainsProtocolHandler.REQUIRED_PLUGINS_KEY)
71 private val LOG = Logger.getInstance("#com.intellij.idea.IdeaApplication")
72
73 private var filesToLoad: List<File> = emptyList()
74 private var wizardStepProvider: CustomizeIDEWizardStepsProvider? = null
75
76 private fun executeInitAppInEdt(rawArgs: Array<String>,
77                                 initAppActivity: Activity,
78                                 pluginDescriptorsFuture: CompletableFuture<List<IdeaPluginDescriptor>>) {
79   val args = processProgramArguments(rawArgs)
80   StartupUtil.patchSystem(LOG)
81   val headless = Main.isHeadless()
82   val app = initAppActivity.runChild("create app") {
83     ApplicationImpl(java.lang.Boolean.getBoolean(PluginManagerCore.IDEA_IS_INTERNAL_PROPERTY), false, headless, Main.isCommandLine())
84   }
85
86   val registerFuture = pluginDescriptorsFuture.thenApply {
87     initAppActivity.runChild("app component registration") {
88       app.registerComponents(it)
89     }
90     it
91   }
92
93   if (args.isEmpty()) {
94     startApp(app, IdeStarter(), initAppActivity, registerFuture, args)
95     return
96   }
97
98   // ApplicationStarter it is extension, so, to find starter, extensions must be registered prior to that
99   registerFuture.thenRunOrHandleError {
100     val starter = findStarter(args.first()) ?: IdeStarter()
101     if (Main.isHeadless() && !starter.isHeadless) {
102       Main.showMessage("Startup Error", "Application cannot start in headless mode", true)
103       exitProcess(Main.NO_GRAPHICS)
104     }
105
106     starter.premain(args)
107     startApp(app, starter, initAppActivity, registerFuture, args)
108   }
109 }
110
111 private fun startApp(app: ApplicationImpl, starter: ApplicationStarter, initAppActivity: Activity, registerFuture: CompletableFuture<List<IdeaPluginDescriptor>>, args: List<String>) {
112   // this code is here for one simple reason - here we have application,
113   // and after plugin loading we don't have - ApplicationManager.getApplication() can be used, but it doesn't matter
114   // but it is very important to call registerRegistryAndMessageBusAndComponent immediately after application creation
115   // and do not place any time-consuming code in between (e.g. showLicenseeInfoOnSplash)
116   val registerRegistryAndInitStoreFuture = registerRegistryAndInitStore(registerFuture, app)
117
118   val headless = app.isHeadlessEnvironment
119   if (!headless) {
120     initAppActivity.runChild("icon loader activation") {
121       // todo investigate why in test mode dummy icon manager is not suitable
122       IconLoader.activate()
123       IconLoader.setStrictGlobally(app.isInternal)
124     }
125   }
126
127   // preload services only after icon activation
128   val future = registerRegistryAndInitStoreFuture.thenCompose {
129     val preloadServiceActivity = StartUpMeasurer.start("preload services")
130     preloadServices(app, it)
131       .thenRun(Runnable {
132         preloadServiceActivity.end()
133       })
134   }
135
136   if (!headless) {
137     if (SystemInfo.isMac) {
138       initAppActivity.runChild("mac app init") {
139         MacOSApplicationProvider.initApplication()
140       }
141
142       // ensure that TouchBarsManager is loaded before WelcomeFrame/project
143       // do not wait completion - it is thread safe and not required for application start
144       AppExecutorUtil.getAppExecutorService().execute {
145         ParallelActivity.PREPARE_APP_INIT.run("mac touchbar") {
146           Foundation.init()
147           TouchBarsManager.isTouchBarAvailable()
148         }
149       }
150     }
151
152     // disabled due to https://youtrack.jetbrains.com/issue/JBR-1399
153     //initAppActivity.runChild("showLicenseeInfoOnSplash") {
154     //  SplashManager.showLicenseeInfoOnSplash(LOG)
155     //}
156
157     preloadIcons()
158
159     initAppActivity.runChild("migLayout") {
160       //IDEA-170295
161       PlatformDefaults.setLogicalPixelBase(PlatformDefaults.BASE_FONT_SIZE)
162     }
163     WeakFocusStackManager.getInstance()
164   }
165
166   future.thenRunOrHandleError {
167     // this invokeLater() call is needed not only because current thread maybe not EDT, but to place the app starting code on a freshly minted IdeEventQueue instance
168     val placeOnEventQueueActivity = initAppActivity.startChild(Phases.PLACE_ON_EVENT_QUEUE)
169     EventQueue.invokeLater {
170       placeOnEventQueueActivity.end()
171       StartupUtil.installExceptionHandler()
172       initAppActivity.end()
173
174       app.loadComponents(SplashManager.getProgressIndicator())
175       if (!headless) {
176         addActivateAndWindowsCliListeners(app)
177       }
178
179       (TransactionGuard.getInstance() as TransactionGuardImpl).performUserActivity {
180         starter.main(ArrayUtilRt.toStringArray(args))
181       }
182
183       if (PluginManagerCore.isRunningFromSources()) {
184         AppExecutorUtil.getAppExecutorService().execute {
185           AppUIUtil.updateWindowIcon(JOptionPane.getRootFrame())
186         }
187       }
188     }
189
190     // execute in parallel to loading components - this functionality should be used only by plugin functionality,
191     // that used after start-up
192     ParallelActivity.PREPARE_APP_INIT.run("init system properties") {
193       SystemPropertyBean.initSystemProperties()
194     }
195   }
196 }
197
198 private fun preloadIcons() {
199   AppExecutorUtil.getAppExecutorService().execute {
200     AsyncProcessIcon("")
201     AnimatedIcon.Blinking(AllIcons.Ide.FatalError)
202     AnimatedIcon.FS()
203   }
204 }
205
206 @ApiStatus.Internal
207 fun registerRegistryAndInitStore(registerFuture: CompletableFuture<List<IdeaPluginDescriptor>>, app: ApplicationImpl): CompletableFuture<List<IdeaPluginDescriptor>> {
208   return registerFuture
209     .thenCompose { plugins ->
210       val future = CompletableFuture.runAsync(Runnable {
211         ParallelActivity.PREPARE_APP_INIT.run("add registry keys") {
212           RegistryKeyBean.addKeysFromPlugins()
213         }
214       }, AppExecutorUtil.getAppExecutorService())
215
216       // yes, at this moment initSystemProperties or RegistryKeyBean.addKeysFromPlugins maybe not yet performed, but it doesn't affect because not used.
217       initConfigurationStore(app, null)
218
219       future
220         .thenApply { plugins }
221     }
222 }
223
224 private fun addActivateAndWindowsCliListeners(app: ApplicationImpl) {
225   StartupUtil.addExternalInstanceListener { args ->
226     val ref = AtomicReference<Future<CliResult>>()
227     app.invokeAndWait {
228       LOG.info("ApplicationImpl.externalInstanceListener invocation")
229       val realArgs = if (args.isEmpty()) args else args.subList(1, args.size)
230       val projectAndFuture = CommandLineProcessor.processExternalCommandLine(realArgs, args.firstOrNull())
231       ref.set(projectAndFuture.getSecond())
232       val frame = when (val project = projectAndFuture.getFirst()) {
233         null -> WindowManager.getInstance().findVisibleFrame()
234         else -> WindowManager.getInstance().getIdeFrame(project) as JFrame
235       }
236       if (frame != null) {
237         if (frame is IdeFrame) {
238           AppIcon.getInstance().requestFocus(frame as IdeFrame)
239         }
240         else {
241           frame.toFront()
242           DialogEarthquakeShaker.shake(frame)
243         }
244       }
245     }
246
247     ref.get()
248   }
249
250   MainRunner.LISTENER = WindowsCommandLineListener { currentDirectory, args ->
251     val argsList = args.toList()
252     LOG.info("Received external Windows command line: current directory $currentDirectory, command line $argsList")
253     if (argsList.isEmpty()) return@WindowsCommandLineListener 0
254     var state = app.defaultModalityState
255     for (starter in ApplicationStarter.EP_NAME.iterable) {
256       if (starter.canProcessExternalCommandLine() && argsList[0] == starter.commandName && starter.allowAnyModalityState()) {
257         state = app.anyModalityState
258       }
259     }
260
261     val ref = AtomicReference<Future<CliResult>>()
262     app.invokeAndWait({ ref.set(CommandLineProcessor.processExternalCommandLine(argsList, currentDirectory).getSecond()) }, state)
263     CliResult.getOrWrapFailure(ref.get(), 1).returnCode
264   }
265 }
266
267 fun initApplication(rawArgs: Array<String>, initUiTask: Future<*>?) {
268   val initAppActivity = MainRunner.startupStart.endAndStart(Phases.INIT_APP)
269   val pluginDescriptorsFuture = CompletableFuture<List<IdeaPluginDescriptor>>()
270   AppExecutorUtil.getAppExecutorService().execute {
271     initAppActivity.runChild("wait UI init") {
272       initUiTask?.get()
273     }
274     EventQueue.invokeLater {
275       executeInitAppInEdt(rawArgs, initAppActivity, pluginDescriptorsFuture)
276     }
277     initAppActivity.runChild("load system fonts") {
278       // editor and other UI components need the list of system fonts to implement font fallback
279       // this list is pre-loaded here, in parallel to other activities, to speed up project opening
280       // ideally, it shouldn't overlap with other font-related activities to avoid contention on JDK-internal font manager locks
281       loadSystemFonts()
282     }
283   }
284
285   val plugins = try {
286     initAppActivity.runChild("plugin descriptors loading") {
287       PluginManagerCore.getLoadedPlugins(MainRunner::class.java.classLoader)
288     }
289   }
290   catch (e: Throwable) {
291     pluginDescriptorsFuture.completeExceptionally(e)
292     return
293   }
294
295   pluginDescriptorsFuture.complete(plugins)
296 }
297
298 private fun loadSystemFonts() = GraphicsEnvironment.getLocalGraphicsEnvironment().availableFontFamilyNames
299
300 fun findStarter(key: String): ApplicationStarter? {
301   for (starter in ApplicationStarter.EP_NAME.iterable) {
302     if (starter == null) {
303       break
304     }
305
306     if (starter.commandName == key) {
307       return starter
308     }
309   }
310   return null
311 }
312
313 fun openFilesOnLoading(files: List<File>) {
314   filesToLoad = files
315 }
316
317 fun setWizardStepsProvider(provider: CustomizeIDEWizardStepsProvider) {
318   wizardStepProvider = provider
319 }
320
321 fun initConfigurationStore(app: ApplicationImpl, configPath: String?) {
322   val beforeApplicationLoadedActivity = StartUpMeasurer.start("beforeApplicationLoaded")
323   val effectiveConfigPath = FileUtilRt.toSystemIndependentName(configPath ?: PathManager.getConfigPath())
324   for (listener in ApplicationLoadListener.EP_NAME.iterable) {
325     try {
326       (listener ?: break).beforeApplicationLoaded(app, effectiveConfigPath)
327     }
328     catch (e: ProcessCanceledException) {
329       throw e
330     }
331     catch (e: Throwable) {
332       LOG.error(e)
333     }
334   }
335
336   val initStoreActivity = beforeApplicationLoadedActivity.endAndStart("init app store")
337
338   // we set it after beforeApplicationLoaded call, because app store can depends on stream provider state
339   app.stateStore.setPath(effectiveConfigPath)
340   LoadingPhase.setCurrentPhase(LoadingPhase.CONFIGURATION_STORE_INITIALIZED)
341   initStoreActivity.end()
342 }
343
344 open class IdeStarter : ApplicationStarter {
345   override fun isHeadless() = false
346
347   override fun getCommandName(): String? = null
348
349   override fun canProcessExternalCommandLine() = true
350
351   override fun processExternalCommandLineAsync(args: List<String>, currentDirectory: String?): Future<CliResult> {
352     LOG.info("Request to open in $currentDirectory with parameters: ${args.joinToString(separator = ",")}")
353     if (args.isEmpty()) {
354       return CliResult.ok()
355     }
356
357     val filename = args[0]
358     val file = if (currentDirectory == null) Paths.get(filename) else Paths.get(currentDirectory, filename)
359     if (file.exists()) {
360       var line = -1
361       if (args.size > 2 && CustomProtocolHandler.LINE_NUMBER_ARG_NAME == args[1]) {
362         try {
363           line = args[2].toInt()
364         }
365         catch (ignore: NumberFormatException) {
366           LOG.error("Wrong line number: ${args[2]}")
367         }
368
369       }
370       PlatformProjectOpenProcessor.doOpenProject(file, OpenProjectTask(), line)
371     }
372     return CliResult.error(1, "Can't find file: $file")
373   }
374
375   private fun loadProjectFromExternalCommandLine(commandLineArgs: List<String>): Project? {
376     var project: Project? = null
377     if (commandLineArgs.firstOrNull() != null) {
378       LOG.info("ApplicationLoader.loadProject")
379       val currentDirectory: String? = System.getenv("IDEA_INITIAL_DIRECTORY")
380       LOG.info("IDEA_INITIAL_DIRECTORY: $currentDirectory")
381       project = CommandLineProcessor.processExternalCommandLine(commandLineArgs, currentDirectory).getFirst()
382     }
383     return project
384   }
385
386   override fun main(args: Array<String>) {
387     val frameInitActivity = StartUpMeasurer.start(Phases.FRAME_INITIALIZATION)
388
389     val app = ApplicationManager.getApplication()
390     // Event queue should not be changed during initialization of application components.
391     // It also cannot be changed before initialization of application components because IdeEventQueue uses other
392     // application components. So it is proper to perform replacement only here.
393     frameInitActivity.runChild("set window manager") {
394       IdeEventQueue.getInstance().setWindowManager(WindowManager.getInstance() as WindowManagerImpl)
395     }
396
397     val commandLineArgs = args.toList()
398
399     val appFrameCreatedActivity = frameInitActivity.startChild("call appFrameCreated")
400     val lifecyclePublisher = app.messageBus.syncPublisher(AppLifecycleListener.TOPIC)
401     lifecyclePublisher.appFrameCreated(commandLineArgs)
402     appFrameCreatedActivity.end()
403
404     // must be after appFrameCreated because some listeners can mutate state of RecentProjectsManager
405     val willOpenProject = commandLineArgs.isNotEmpty() || filesToLoad.isNotEmpty() || RecentProjectsManager.getInstance().willReopenProjectOnStart()
406
407     // temporary check until the JRE implementation has been checked and bundled
408     if (java.lang.Boolean.getBoolean("ide.popup.enablePopupType")) {
409       @Suppress("SpellCheckingInspection")
410       System.setProperty("jbre.popupwindow.settype", "true")
411     }
412
413     val shouldShowWelcomeFrame = !willOpenProject || JetBrainsProtocolHandler.getCommand() != null
414     val doShowWelcomeFrame = if (shouldShowWelcomeFrame) WelcomeFrame.prepareToShow() else null
415     showWizardAndWelcomeFrame(when (doShowWelcomeFrame) {
416       null -> null
417       else -> Runnable {
418         doShowWelcomeFrame.run()
419         lifecyclePublisher.welcomeScreenDisplayed()
420       }
421     })
422
423     frameInitActivity.end()
424
425     AppExecutorUtil.getAppExecutorService().run {
426       LifecycleUsageTriggerCollector.onIdeStart()
427     }
428
429     TransactionGuard.submitTransaction(app, Runnable {
430       val project = when {
431         filesToLoad.isNotEmpty() -> ProjectUtil.tryOpenFileList(null, filesToLoad, "MacMenu")
432         commandLineArgs.isNotEmpty() -> loadProjectFromExternalCommandLine(commandLineArgs)
433         else -> null
434       }
435
436       app.messageBus.syncPublisher(AppLifecycleListener.TOPIC).appStarting(project)
437
438       if (project == null && !JetBrainsProtocolHandler.appStartedWithCommand()) {
439         RecentProjectsManager.getInstance().reopenLastProjectsOnStart()
440       }
441
442       EventQueue.invokeLater {
443         reportPluginError()
444       }
445     })
446
447     if (!app.isHeadlessEnvironment) {
448       postOpenUiTasks(app)
449     }
450   }
451
452   private fun showWizardAndWelcomeFrame(showWelcomeFrame: Runnable?) {
453     wizardStepProvider?.let { wizardStepsProvider ->
454       val wizardDialog = object : CustomizeIDEWizardDialog(wizardStepsProvider, null, false, true) {
455         override fun doOKAction() {
456           super.doOKAction()
457           showWelcomeFrame?.run()
458         }
459       }
460       if (wizardDialog.showIfNeeded()) {
461         return
462       }
463     }
464     showWelcomeFrame?.run()
465   }
466
467   private fun postOpenUiTasks(app: Application) {
468     if (SystemInfo.isMac) {
469       ApplicationManager.getApplication().executeOnPooledThread {
470         TouchBarsManager.onApplicationInitialized()
471         if (TouchBarsManager.isTouchBarAvailable()) {
472           CustomActionsSchema.addSettingsGroup(IdeActions.GROUP_TOUCHBAR, "Touch Bar")
473         }
474
475         val keymap = KeymapManager.getInstance().activeKeymap
476         SystemShortcuts().checkConflictsAndNotify(keymap)
477       }
478     }
479
480     app.invokeLater {
481       val updateSystemDockActivity = StartUpMeasurer.start("system dock menu")
482       SystemDock.updateMenu()
483       updateSystemDockActivity.end()
484     }
485     app.invokeLater {
486       val generalSettings = GeneralSettings.getInstance()
487       generalSettings.addPropertyChangeListener(GeneralSettings.PROP_SUPPORT_SCREEN_READERS, app,
488                                                 PropertyChangeListener { e -> ScreenReader.setActive(e.newValue as Boolean) })
489       ScreenReader.setActive(generalSettings.isSupportScreenReaders)
490     }
491   }
492 }
493
494 /**
495  * Method looks for `-Dkey=value` program arguments and stores some of them in system properties.
496  * We should use it for a limited number of safe keys.
497  * One of them is a list of ids of required plugins
498  *
499  * @see SAFE_JAVA_ENV_PARAMETERS
500  */
501 @Suppress("SpellCheckingInspection")
502 private fun processProgramArguments(args: Array<String>): List<String> {
503   if (args.isEmpty()) {
504     return emptyList()
505   }
506
507   val arguments = mutableListOf<String>()
508   val safeKeys = SAFE_JAVA_ENV_PARAMETERS.toList()
509   for (arg in args) {
510     if (arg.startsWith("-D")) {
511       val keyValue = arg.substring(2).split('=')
512       if (keyValue.size == 2 && safeKeys.contains(keyValue[0])) {
513         System.setProperty(keyValue[0], keyValue[1])
514         continue
515       }
516     }
517     if (SplashManager.NO_SPLASH == arg) {
518       continue
519     }
520
521     arguments.add(arg)
522   }
523   return arguments
524 }
525
526 @ApiStatus.Internal
527 fun preloadServices(app: ApplicationImpl, plugins: List<IdeaPluginDescriptor>): CompletableFuture<*> {
528   val futures = mutableListOf<CompletableFuture<Void>>()
529   val executor = AppExecutorUtil.createBoundedApplicationPoolExecutor("preload services", Runtime.getRuntime().availableProcessors(), false)
530   for (plugin in plugins) {
531     for (service in (plugin as IdeaPluginDescriptorImpl).app.services) {
532       if (service.preload) {
533         futures.add(CompletableFuture.runAsync(Runnable { app.precreateService(service.getInterface()) }, executor))
534       }
535     }
536   }
537
538   executor.shutdown()
539   return CompletableFuture.allOf(*futures.toTypedArray())
540 }
541
542 private fun CompletableFuture<*>.thenRunOrHandleError(handler: () -> Unit): CompletableFuture<Void>? {
543   return thenRun(handler)
544     .exceptionally {
545       MainRunner.processException(it)
546       null
547     }
548 }
549
550 private fun reportPluginError() {
551   if (PluginManagerCore.myPluginError == null) {
552     return
553   }
554
555   val title = IdeBundle.message("title.plugin.error")
556   Notifications.Bus.notify(Notification(title, title, PluginManagerCore.myPluginError, NotificationType.ERROR) { notification, event ->
557     notification.expire()
558
559     val description = event.description
560     if (PluginManagerCore.EDIT == description) {
561       val ideFrame = WindowManagerEx.getInstanceEx().findFrameFor(null)
562       PluginManagerConfigurableProxy.showPluginConfigurable(ideFrame?.component, null)
563       return@Notification
564     }
565
566     val disabledPlugins = LinkedHashSet(PluginManagerCore.disabledPlugins())
567     if (PluginManagerCore.myPlugins2Disable != null && PluginManagerCore.DISABLE == description) {
568       disabledPlugins.addAll(PluginManagerCore.myPlugins2Disable)
569     }
570     else if (PluginManagerCore.myPlugins2Enable != null && PluginManagerCore.ENABLE == description) {
571       disabledPlugins.removeAll(PluginManagerCore.myPlugins2Enable)
572       PluginManagerMain.notifyPluginsUpdated(null)
573     }
574
575     try {
576       PluginManagerCore.saveDisabledPlugins(disabledPlugins, false)
577     }
578     catch (ignore: IOException) {
579     }
580
581     PluginManagerCore.myPlugins2Enable = null
582     PluginManagerCore.myPlugins2Disable = null
583   })
584   PluginManagerCore.myPluginError = null
585 }