Skip to content

Commit

Permalink
Merge from 5.x: PR #16012
Browse files Browse the repository at this point in the history
Fixes #15594
  • Loading branch information
ccordoba12 committed Oct 6, 2021
2 parents 0151122 + 1d6c023 commit 4faf12a
Show file tree
Hide file tree
Showing 46 changed files with 1,641 additions and 269 deletions.
117 changes: 117 additions & 0 deletions spyder/api/plugin_registration/_confpage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
#
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
# (see spyder/__init__.py for details)

"""Plugin registry configuration page."""

# Third party imports
from qtpy.QtWidgets import (QGroupBox, QVBoxLayout, QCheckBox,
QGridLayout, QLabel)

# Local imports
from spyder.api.plugins import SpyderPlugin
from spyder.api.preferences import PluginConfigPage
from spyder.config.base import _
from spyder.config.manager import CONF


class PluginsConfigPage(PluginConfigPage):
def setup_page(self):
newcb = self.create_checkbox
self.plugins_checkboxes = {}

header_label = QLabel(
_("Here you can turn on/off any internal or external Spyder plugin "
"to disable functionality that is not desired or to have a lighter "
"experience. Unchecked plugins in this page will be unloaded "
"immediately and will not be loaded the next time Spyder starts."))
header_label.setWordWrap(True)

# ------------------ Internal plugin status group ---------------------
internal_layout = QGridLayout()
self.internal_plugins_group = QGroupBox(_("Internal plugins"))

i = 0
for plugin_name in self.plugin.all_internal_plugins:
(conf_section_name,
PluginClass) = self.plugin.all_internal_plugins[plugin_name]

if not getattr(PluginClass, 'CAN_BE_DISABLED', True):
# Do not list core plugins that can not be disabled
continue

plugin_loc_name = None
if hasattr(PluginClass, 'get_name'):
plugin_loc_name = PluginClass.get_name()
elif hasattr(PluginClass, 'get_plugin_title'):
plugin_loc_name = PluginClass.get_plugin_title()

plugin_state = CONF.get(conf_section_name, 'enable', True)
cb = newcb(plugin_loc_name, 'enable', default=True,
section=conf_section_name, restart=True)
internal_layout.addWidget(cb, i // 2, i % 2)
self.plugins_checkboxes[plugin_name] = (cb, plugin_state)
i += 1

self.internal_plugins_group.setLayout(internal_layout)

# ------------------ External plugin status group ---------------------
external_layout = QGridLayout()
self.external_plugins_group = QGroupBox(_("External plugins"))

i = 0
for i, plugin_name in enumerate(self.plugin.all_external_plugins):
(conf_section_name,
PluginClass) = self.plugin.all_external_plugins[plugin_name]

plugin_loc_name = None
if hasattr(PluginClass, 'get_name'):
plugin_loc_name = PluginClass.get_name()
elif hasattr(PluginClass, 'get_plugin_title'):
plugin_loc_name = PluginClass.get_plugin_title()

cb = newcb(plugin_loc_name, 'enable', default=True,
section=conf_section_name, restart=True)
external_layout.addWidget(cb, i // 2, i % 2)
self.plugins_checkboxes[plugin_name] = cb
i += 1

self.external_plugins_group.setLayout(external_layout)

layout = QVBoxLayout()
layout.addWidget(header_label)
layout.addWidget(self.internal_plugins_group)
if self.plugin.all_external_plugins:
layout.addWidget(self.external_plugins_group)
layout.addStretch(1)
self.setLayout(layout)

def apply_settings(self):
for plugin_name in self.plugins_checkboxes:
cb, previous_state = self.plugins_checkboxes[plugin_name]
if cb.isChecked() and not previous_state:
self.plugin.set_plugin_enabled(plugin_name)
PluginClass = None
external = False
if plugin_name in self.plugin.all_internal_plugins:
(__,
PluginClass) = self.plugin.all_internal_plugins[plugin_name]
elif plugin_name in self.plugin.all_external_plugins:
(__,
PluginClass) = self.plugin.all_external_plugins[plugin_name]
external = True

# TODO: Once we can test that all plugins can be restarted
# without problems during runtime, we can enable the
# autorestart feature provided by the plugin registry:
# self.plugin.register_plugin(self.main, PluginClass,
# external=external)
elif not cb.isChecked() and previous_state:
# TODO: Once we can test that all plugins can be restarted
# without problems during runtime, we can enable the
# autorestart feature provided by the plugin registry:
# self.plugin.delete_plugin(plugin_name)
pass
return set({})
35 changes: 35 additions & 0 deletions spyder/api/plugin_registration/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,38 @@ def on_plugin_available(func: Callable = None,

func._plugin_listen = plugin
return func


def on_plugin_teardown(func: Callable = None,
plugin: Optional[str] = None):
"""
Method decorator used to handle plugin teardown on Spyder.
This decorator will be called **before** the specified plugin is deleted
and also **before** the plugin that uses the decorator is destroyed.
The methods that use this decorator must have the following signature:
`def method(self)`.
Parameters
----------
func: Callable
Method to decorate. Given by default when applying the decorator.
plugin: str
Name of the requested plugin whose teardown triggers the method.
Returns
-------
func: Callable
The same method that was given as input.
"""
if func is None:
return functools.partial(on_plugin_teardown, plugin=plugin)

if plugin is None:
raise ValueError('on_plugin_teardown must have a well defined '
'plugin keyword argument value, '
'e.g., plugin=Plugins.Editor')

func._plugin_teardown = plugin
return func
24 changes: 24 additions & 0 deletions spyder/api/plugin_registration/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,20 @@ class SpyderPluginObserver:

def __init__(self):
self._plugin_listeners = {}
self._plugin_teardown_listeners = {}
for method_name in dir(self):
method = getattr(self, method_name, None)
if hasattr(method, '_plugin_listen'):
info = method._plugin_listen
logger.debug(f'Method {method_name} is watching plugin {info}')
self._plugin_listeners[info] = method_name

if hasattr(method, '_plugin_teardown'):
info = method._plugin_teardown
logger.debug(f'Method {method_name} will handle plugin '
f'teardown for {info}')
self._plugin_teardown_listeners[info] = method_name

def _on_plugin_available(self, plugin: str):
"""
Handle plugin availability and redirect it to plugin-specific
Expand All @@ -63,3 +70,20 @@ def _on_plugin_available(self, plugin: str):
method_name = self._plugin_listeners['__all']
method = getattr(self, method_name)
method(plugin)

def _on_plugin_teardown(self, plugin: str):
"""
Handle plugin teardown and redirect it to plugin-specific teardown
handlers.
Parameters
----------
plugin: str
Name of the plugin that is going through its teardown process.
"""
# Call plugin specific handler
if plugin in self._plugin_teardown_listeners:
method_name = self._plugin_teardown_listeners[plugin]
method = getattr(self, method_name)
logger.debug(f'Calling {method}')
method()
Loading

0 comments on commit 4faf12a

Please sign in to comment.