Skip to content

Commit

Permalink
Backport PR spyder-ide#23024: PR: Restore widget shortcuts to Prefere…
Browse files Browse the repository at this point in the history
…nces and allow to change them on the fly (Shortcuts)
  • Loading branch information
ccordoba12 authored and meeseeksmachine committed Dec 2, 2024
1 parent d76f1b1 commit c245c60
Show file tree
Hide file tree
Showing 32 changed files with 693 additions and 155 deletions.
3 changes: 3 additions & 0 deletions changelogs/Spyder-6.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

### API changes

* Add `plugin_name` kwarg to the `register_shortcut_for_widget` method of
`SpyderShortcutsMixin`.
* The `add_configuration_observer` method was added to `SpyderConfigurationObserver`.
* Add `items_elide_mode` kwarg to the constructors of `SpyderComboBox` and
`SpyderComboBoxWithIcons`.
* The `sig_item_in_popup_changed` and `sig_popup_is_hidden` signals were added
Expand Down
7 changes: 6 additions & 1 deletion conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,12 @@ def pytest_collection_modifyitems(config, items):


@pytest.fixture(autouse=True)
def reset_conf_before_test():
def reset_conf_before_test(request):
# To prevent running this fixture for a specific test, you need to use this
# marker.
if 'no_reset_conf' in request.keywords:
return

from spyder.config.manager import CONF
CONF.reset_to_defaults(notification=False)

Expand Down
71 changes: 61 additions & 10 deletions spyder/api/config/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

# Standard library imports
import logging
from typing import Any, Union, Optional
from typing import Any, Callable, Optional, Union
import warnings

# Local imports
Expand Down Expand Up @@ -239,8 +239,10 @@ def __init__(self):
section = self.CONF_SECTION if section is None else section
observed_options = self._configuration_listeners[section]
for option in observed_options:
logger.debug(f'{self} is observing {option} '
f'in section {section}')
logger.debug(
f'{self} is observing option "{option}" in section '
f'"{section}"'
)
CONF.observe_configuration(self, section, option)

def __del__(self):
Expand All @@ -257,12 +259,7 @@ def _gather_observers(self):
self._multi_option_listeners |= {method_name}

for section, option in info:
section_listeners = self._configuration_listeners.get(
section, {})
option_listeners = section_listeners.get(option, [])
option_listeners.append(method_name)
section_listeners[option] = option_listeners
self._configuration_listeners[section] = section_listeners
self._add_listener(method_name, option, section)

def _merge_none_observers(self):
"""Replace observers that declared section as None by CONF_SECTION."""
Expand All @@ -280,6 +277,27 @@ def _merge_none_observers(self):
self._configuration_listeners[self.CONF_SECTION] = section_selectors
self._configuration_listeners.pop(None, None)

def _add_listener(
self, func: Callable, option: ConfigurationKey, section: str
):
"""
Add a callable as listener of the option `option` on section `section`.
Parameters
----------
func: Callable
Function/method that will be called when `option` changes.
option: ConfigurationKey
Configuration option to observe.
section: str
Name of the section where `option` is contained.
"""
section_listeners = self._configuration_listeners.get(section, {})
option_listeners = section_listeners.get(option, [])
option_listeners.append(func)
section_listeners[option] = option_listeners
self._configuration_listeners[section] = section_listeners

def on_configuration_change(self, option: ConfigurationKey, section: str,
value: Any):
"""
Expand All @@ -298,8 +316,41 @@ def on_configuration_change(self, option: ConfigurationKey, section: str,
section_receivers = self._configuration_listeners.get(section, {})
option_receivers = section_receivers.get(option, [])
for receiver in option_receivers:
method = getattr(self, receiver)
method = (
receiver if callable(receiver) else getattr(self, receiver)
)
if receiver in self._multi_option_listeners:
method(option, value)
else:
method(value)

def add_configuration_observer(
self, func: Callable, option: str, section: Optional[str] = None
):
"""
Add a callable to observe the option `option` on section `section`.
Parameters
----------
func: Callable
Function that will be called when `option` changes.
option: ConfigurationKey
Configuration option to observe.
section: str
Name of the section where `option` is contained.
Notes
-----
- This is only necessary if you need to add a callable that is not a
class method to observe an option. Otherwise, you simply need to
decorate your method with
:function:`spyder.api.config.decorators.on_conf_change`.
"""
if section is None:
section = self.CONF_SECTION

logger.debug(
f'{self} is observing "{option}" option on section "{section}"'
)
self._add_listener(func, option, section)
CONF.observe_configuration(self, section, option)
44 changes: 22 additions & 22 deletions spyder/api/plugins/new_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,8 @@ class SpyderPluginV2(QObject, SpyderActionMixin, SpyderConfigurationObserver,
The window state.
"""

# --- Private attributes -------------------------------------------------
# ------------------------------------------------------------------------
# ---- Private attributes
# -------------------------------------------------------------------------
# Define configuration name map for plugin to split configuration
# among several files. See spyder/config/main.py
_CONF_NAME_MAP = None
Expand Down Expand Up @@ -361,8 +361,8 @@ def __init__(self, parent, configuration=None):
plugin_path = osp.join(self.get_path(), self.IMG_PATH)
IMAGE_PATH_MANAGER.add_image_path(plugin_path)

# --- Private methods ----------------------------------------------------
# ------------------------------------------------------------------------
# ---- Private methods
# -------------------------------------------------------------------------
def _register(self, omit_conf=False):
"""
Setup and register plugin in Spyder's main window and connect it to
Expand Down Expand Up @@ -397,8 +397,8 @@ def _unregister(self):
self.is_compatible = None
self.is_registered = False

# --- API: available methods ---------------------------------------------
# ------------------------------------------------------------------------
# ---- API: available methods
# -------------------------------------------------------------------------
def get_path(self):
"""
Return the plugin's system path.
Expand Down Expand Up @@ -765,8 +765,8 @@ def get_command_line_options(self):
sys_argv = [sys.argv[0]] # Avoid options passed to pytest
return get_options(sys_argv)[0]

# --- API: Mandatory methods to define -----------------------------------
# ------------------------------------------------------------------------
# ---- API: Mandatory methods to define
# -------------------------------------------------------------------------
@staticmethod
def get_name():
"""
Expand Down Expand Up @@ -832,8 +832,8 @@ def on_initialize(self):
f'The plugin {type(self)} is missing an implementation of '
'on_initialize')

# --- API: Optional methods to override ----------------------------------
# ------------------------------------------------------------------------
# ---- API: Optional methods to override
# -------------------------------------------------------------------------
@staticmethod
def check_compatibility():
"""
Expand Down Expand Up @@ -952,14 +952,14 @@ class SpyderDockablePlugin(SpyderPluginV2):
"""
A Spyder plugin to enhance functionality with a dockable widget.
"""
# --- API: Mandatory attributes ------------------------------------------
# ------------------------------------------------------------------------
# ---- API: Mandatory attributes
# -------------------------------------------------------------------------
# This is the main widget of the dockable plugin.
# It needs to be a subclass of PluginMainWidget.
WIDGET_CLASS = None

# --- API: Optional attributes -------------------------------------------
# ------------------------------------------------------------------------
# ---- API: Optional attributes
# -------------------------------------------------------------------------
# Define a list of plugins next to which we want to to tabify this plugin.
# Example: ['Plugins.Editor']
TABIFY = []
Expand All @@ -972,8 +972,8 @@ class SpyderDockablePlugin(SpyderPluginV2):
# the action to switch is called a second time.
RAISE_AND_FOCUS = False

# --- API: Available signals ---------------------------------------------
# ------------------------------------------------------------------------
# ---- API: Available signals
# -------------------------------------------------------------------------
sig_focus_changed = Signal()
"""
This signal is emitted to inform the focus of this plugin has changed.
Expand Down Expand Up @@ -1010,8 +1010,8 @@ class SpyderDockablePlugin(SpyderPluginV2):
needs its ancestor to be updated.
"""

# --- Private methods ----------------------------------------------------
# ------------------------------------------------------------------------
# ---- Private methods
# -------------------------------------------------------------------------
def __init__(self, parent, configuration):
if not issubclass(self.WIDGET_CLASS, PluginMainWidget):
raise SpyderAPIError(
Expand Down Expand Up @@ -1053,8 +1053,8 @@ def __init__(self, parent, configuration):
widget.sig_update_ancestor_requested.connect(
self.sig_update_ancestor_requested)

# --- API: available methods ---------------------------------------------
# ------------------------------------------------------------------------
# ---- API: available methods
# -------------------------------------------------------------------------
def before_long_process(self, message):
"""
Show a message in main window's status bar, change the mouse pointer
Expand Down Expand Up @@ -1116,8 +1116,8 @@ def set_ancestor(self, ancestor_widget):
"""
self.get_widget().set_ancestor(ancestor_widget)

# --- Convenience methods from the widget exposed on the plugin
# ------------------------------------------------------------------------
# ---- Convenience methods from the widget exposed on the plugin
# -------------------------------------------------------------------------
@property
def dockwidget(self):
return self.get_widget().dockwidget
Expand Down
Loading

0 comments on commit c245c60

Please sign in to comment.