From 70434f6cfe1141c0daab515aa812003200d33d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:06 -0500 Subject: [PATCH 01/68] Add plugin teardown operations to the plugin registry --- spyder/api/plugin_registration/decorators.py | 35 +++++++ spyder/api/plugin_registration/mixins.py | 24 +++++ spyder/api/plugin_registration/registry.py | 99 ++++++++++++++++++++ spyder/api/plugins/new_api.py | 22 +++-- spyder/plugins/application/plugin.py | 8 +- spyder/plugins/preferences/plugin.py | 16 ++++ 6 files changed, 197 insertions(+), 7 deletions(-) diff --git a/spyder/api/plugin_registration/decorators.py b/spyder/api/plugin_registration/decorators.py index 501e5b213f5..c1df944bc19 100644 --- a/spyder/api/plugin_registration/decorators.py +++ b/spyder/api/plugin_registration/decorators.py @@ -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 diff --git a/spyder/api/plugin_registration/mixins.py b/spyder/api/plugin_registration/mixins.py index b28fc6b5d0b..bb1954ad3f1 100644 --- a/spyder/api/plugin_registration/mixins.py +++ b/spyder/api/plugin_registration/mixins.py @@ -34,6 +34,7 @@ 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'): @@ -41,6 +42,12 @@ def __init__(self): 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 @@ -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() diff --git a/spyder/api/plugin_registration/registry.py b/spyder/api/plugin_registration/registry.py index 9a3eecb2fd1..9da9fba4cb7 100644 --- a/spyder/api/plugin_registration/registry.py +++ b/spyder/api/plugin_registration/registry.py @@ -223,6 +223,33 @@ def _notify_plugin_dependencies(self, plugin_name: str): logger.debug(f'Plugin {plugin} has already loaded') plugin_instance._on_plugin_available(plugin) + def _notify_plugin_teardown(self, plugin_name: str): + """Notify dependents of a plugin that is going to be unavailable.""" + plugin_dependents = self.plugin_dependents.get(plugin_name, {}) + required_plugins = plugin_dependents.get('requires', []) + optional_plugins = plugin_dependents.get('optional', []) + + for plugin in required_plugins + optional_plugins: + if plugin in self.plugin_registry: + if self.plugin_availability.get(plugin, False): + logger.debug(f'Notifying plugin {plugin} that ' + f'{plugin_name} is going to be turned off') + plugin_instance = self.plugin_registry[plugin] + plugin_instance._on_plugin_teardown(plugin_name) + + def _teardown_plugin(self, plugin_name: str): + """Disconnect a plugin from its dependencies.""" + plugin_instance = self.plugin_registry[plugin_name] + plugin_dependencies = self.plugin_dependencies.get(plugin_name, {}) + required_plugins = plugin_dependencies.get('requires', []) + optional_plugins = plugin_dependencies.get('optional', []) + + for plugin in required_plugins + optional_plugins: + if plugin in self.plugin_registry: + if self.plugin_availability.get(plugin, False): + logger.debug(f'Disconnecting {plugin_name} from {plugin}') + plugin_instance._on_plugin_teardown(plugin) + # -------------------------- PUBLIC API ----------------------------------- def register_plugin( self, main_window: Any, @@ -317,6 +344,78 @@ def notify_plugin_availability(self, plugin_name: str, plugin_instance = self.plugin_registry[plugin] plugin_instance._on_plugin_available(plugin_name) + def delete_plugin(self, plugin_name: str) -> bool: + """ + Remove and delete a plugin from the registry by its name. + + Paremeters + ---------- + plugin_name: str + Name of the plugin to delete. + + Returns + ------- + plugin_deleted: bool + True if the registry was able to teardown and remove the plugin. + False otherwise. + """ + plugin_instance = self.plugin_registry[plugin_name] + + # Determine if plugin can be closed + can_delete = True + if isinstance(plugin_instance, SpyderPluginV2): + can_delete = plugin_instance.can_close() + elif isinstance(plugin_instance, SpyderPlugin): + can_delete = plugin_instance.closing_plugin(True) + + if not can_delete: + return False + + if isinstance(plugin_instance, SpyderPluginV2): + # Disconnect plugin from other plugins + self._teardown_plugin(plugin_name) + + # Disconnect depending plugins from the plugin to delete + self._notify_plugin_teardown(plugin_name) + + # Remove the plugin from the main window (if graphical) + if isinstance(plugin_instance, SpyderDockablePlugin): + plugin_instance.close_window() + + # Perform plugin closure tasks + plugin_instance.on_close(True) + elif isinstance(plugin_instance, SpyderPlugin): + # Disconnect depending plugins from the plugin to delete + self._notify_plugin_teardown(plugin_name) + if isinstance(plugin_instance, SpyderPluginWidget): + plugin_instance._close_window() + + # Delete plugin from the registry and auxiliary structures + self.plugin_dependents.pop(plugin_name) + self.plugin_dependencies.pop(plugin_name) + + for plugin_ in self.plugin_dependents: + all_plugin_dependents = self.plugin_dependents[plugin_] + for key in {'requires', 'optional'}: + plugin_dependents = all_plugin_dependents[key] + plugin_dependents.remove(plugin_name) + + for plugin_ in self.plugin_dependencies: + all_plugin_dependencies = self.plugin_dependencies[plugin_] + for key in {'requires', 'optional'}: + plugin_dependencies = all_plugin_dependencies[key] + plugin_dependencies.remove(plugin_name) + + self.plugin_availability.pop(plugin_name) + self.old_plugins -= {plugin_name} + self.enabled_plugins -= {plugin_name} + self.internal_plugins -= {plugin_name} + self.external_plugins -= {plugin_name} + + # Remove the plugin from the registry + self.plugin_registry.pop(plugin_name) + return True + def get_plugin(self, plugin_name: str) -> SpyderPluginClass: """ Get a reference to a plugin instance by its name. diff --git a/spyder/api/plugins/new_api.py b/spyder/api/plugins/new_api.py index c8b8b9d563c..aa5d429b4c9 100644 --- a/spyder/api/plugins/new_api.py +++ b/spyder/api/plugins/new_api.py @@ -153,6 +153,11 @@ class SpyderPluginV2(QObject, SpyderActionMixin, SpyderConfigurationObserver, # Define context to store actions, toolbars, toolbuttons and menus. CONTEXT_NAME = None + # Define if a plugin can be disabled in preferences. + # If False, the plugin is considered "core" and therefore it cannot be + # disabled. Default: True + CAN_BE_DISABLED = True + # --- API: Signals ------------------------------------------------------- # ------------------------------------------------------------------------ # Signals here are automatically connected by the Spyder main window and @@ -758,14 +763,19 @@ def on_close(self, cancelable=False): """ Perform actions before the main window is closed. + This method **must** only operate on local attributes and not other + plugins. + """ + pass + + def can_close(self) -> bool: + """ + Determine if a plugin can be closed. + Returns ------- - bool - Whether the plugin may be closed immediately or not. - - Notes - ----- - The returned value is ignored if *cancelable* is False. + close: bool + True if the plugin can be closed, False otherwise. """ return True diff --git a/spyder/plugins/application/plugin.py b/spyder/plugins/application/plugin.py index cd7482667ca..61bd31a5281 100644 --- a/spyder/plugins/application/plugin.py +++ b/spyder/plugins/application/plugin.py @@ -21,7 +21,8 @@ # Local imports from spyder.api.plugins import Plugins, SpyderPluginV2 from spyder.api.translations import get_translation -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.widgets.menus import MENU_SEPARATOR from spyder.config.base import (DEV, get_module_path, get_debug_level, running_under_pytest) @@ -45,6 +46,7 @@ class Application(SpyderPluginV2): CONF_SECTION = 'main' CONF_FILE = False CONF_WIDGET_CLASS = ApplicationConfigPage + CAN_BE_DISABLED = False def get_name(self): return _('Application') @@ -62,6 +64,7 @@ def on_initialize(self): self.sig_restart_requested.connect(self.restart) + # --------------------- PLUGIN INITIALIZATION ----------------------------- @on_plugin_available(plugin=Plugins.Shortcuts) def on_shortcuts_available(self): if self.is_plugin_available(Plugins.MainMenu): @@ -97,6 +100,9 @@ def on_editor_available(self): editor = self.get_plugin(Plugins.Editor) self.get_container().sig_load_log_file.connect(editor.load) + # -------------------------- PLUGIN TEARDOWN ------------------------------ + # @on_plugin_teardown(plugin=Plugins.) + def on_close(self): self.get_container().on_close() diff --git a/spyder/plugins/preferences/plugin.py b/spyder/plugins/preferences/plugin.py index bad3d7b344b..e14b382aab0 100644 --- a/spyder/plugins/preferences/plugin.py +++ b/spyder/plugins/preferences/plugin.py @@ -103,6 +103,22 @@ def register_plugin_preferences( self.config_pages[plugin.CONF_SECTION] = ( self.OLD_API, Widget, plugin) + def deregister_plugin_preferences( + self, plugin: Union[SpyderPluginV2, SpyderPlugin]): + """Remove a plugin preference page and additional configuration tabs.""" + name = (getattr(plugin, 'NAME', None) or + getattr(plugin, 'CONF_SECTION', None)) + + # Remove configuration page for the plugin + self.config_pages.pop(name) + + # Remove additional configuration tabs that the plugin did introduce + if isinstance(plugin, SpyderPluginV2): + for plugin_name in plugin.ADDITIONAL_CONF_TABS: + tabs = plugin.ADDITIONAL_CONF_TABS[plugin_name] + for tab in tabs: + self.config_tabs[plugin_name].remove(tab) + def check_version_and_merge(self, conf_section: str, conf_key: str, new_value: BasicType, current_version: Version, plugin): From 9081b6c0fd7cffb4f255aa038b80d712345e06a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:06 -0500 Subject: [PATCH 02/68] Use the teardown mechanism when closing Spyder --- spyder/api/plugin_registration/registry.py | 83 ++++++++++++++++++++-- spyder/app/mainwindow.py | 38 ++-------- spyder/plugins/appearance/plugin.py | 9 ++- spyder/plugins/application/plugin.py | 2 +- spyder/plugins/editor/plugin.py | 6 +- spyder/plugins/ipythonconsole/plugin.py | 9 ++- spyder/plugins/preferences/plugin.py | 2 +- spyder/plugins/toolbar/plugin.py | 5 -- 8 files changed, 104 insertions(+), 50 deletions(-) diff --git a/spyder/api/plugin_registration/registry.py b/spyder/api/plugin_registration/registry.py index 9da9fba4cb7..0d0c28576eb 100644 --- a/spyder/api/plugin_registration/registry.py +++ b/spyder/api/plugin_registration/registry.py @@ -8,7 +8,7 @@ # Standard library imports import logging -from typing import Dict, List, Union, Type, Any +from typing import Dict, List, Union, Type, Any, Set, Optional # Third-party library imports from qtpy.QtCore import QObject, Signal @@ -391,20 +391,22 @@ def delete_plugin(self, plugin_name: str) -> bool: plugin_instance._close_window() # Delete plugin from the registry and auxiliary structures - self.plugin_dependents.pop(plugin_name) - self.plugin_dependencies.pop(plugin_name) + self.plugin_dependents.pop(plugin_name, None) + self.plugin_dependencies.pop(plugin_name, None) for plugin_ in self.plugin_dependents: all_plugin_dependents = self.plugin_dependents[plugin_] for key in {'requires', 'optional'}: - plugin_dependents = all_plugin_dependents[key] - plugin_dependents.remove(plugin_name) + plugin_dependents = all_plugin_dependents.get(key, []) + if plugin_name in plugin_dependents: + plugin_dependents.remove(plugin_name) for plugin_ in self.plugin_dependencies: all_plugin_dependencies = self.plugin_dependencies[plugin_] for key in {'requires', 'optional'}: - plugin_dependencies = all_plugin_dependencies[key] - plugin_dependencies.remove(plugin_name) + plugin_dependencies = all_plugin_dependencies.get(key, []) + if plugin_name in plugin_dependencies: + plugin_dependencies.remove(plugin_name) self.plugin_availability.pop(plugin_name) self.old_plugins -= {plugin_name} @@ -416,6 +418,73 @@ def delete_plugin(self, plugin_name: str) -> bool: self.plugin_registry.pop(plugin_name) return True + def delete_all_plugins(self, excluding: Optional[Set[str]] = None) -> bool: + """ + Remove all plugins from the registry. + + The teardown mechanism will remove external plugins first and then + internal ones, where the Spyder 4 plugins will be removed first and + then the Spyder 5 ones. + + Parameters + ---------- + excluding: Optional[Set[str]] + A set that lists plugins (by name) that will not be deleted. + + Returns + ------- + all_deleted: bool + True if all the plugins were closed and deleted. False otherwise. + """ + excluding = excluding or set({}) + can_close = True + + # Delete Spyder 4 external plugins + for plugin_name in set(self.external_plugins): + if plugin_name not in excluding: + plugin_instance = self.plugin_registry[plugin_name] + if isinstance(plugin_instance, SpyderPlugin): + can_close &= self.delete_plugin(plugin_name) + if not can_close: + break + + if not can_close: + return False + + # Delete Spyder 5+ external plugins + for plugin_name in set(self.external_plugins): + if plugin_name not in excluding: + plugin_instance = self.plugin_registry[plugin_name] + if isinstance(plugin_instance, SpyderPluginV2): + can_close &= self.delete_plugin(plugin_name) + if not can_close: + break + + if not can_close: + return False + + # Delete Spyder 4 internal plugins + for plugin_name in set(self.internal_plugins): + if plugin_name not in excluding: + plugin_instance = self.plugin_registry[plugin_name] + if isinstance(plugin_instance, SpyderPlugin): + can_close &= self.delete_plugin(plugin_name) + if not can_close: + break + + if not can_close: + return False + + for plugin_name in set(self.internal_plugins): + if plugin_name not in excluding: + plugin_instance = self.plugin_registry[plugin_name] + if isinstance(plugin_instance, SpyderPluginV2): + can_close &= self.delete_plugin(plugin_name) + if not can_close: + break + + return can_close + def get_plugin(self, plugin_name: str) -> SpyderPluginClass: """ Get a reference to a plugin instance by its name. diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py index e9bef830be5..bb342869c15 100644 --- a/spyder/app/mainwindow.py +++ b/spyder/app/mainwindow.py @@ -1084,9 +1084,9 @@ def __getattr__(self, attr): """ # Mapping of new plugin identifiers vs old attributtes # names given for plugins - if attr in self._INTERNAL_PLUGINS_MAPPING.keys(): - return self.get_plugin(self._INTERNAL_PLUGINS_MAPPING[attr]) try: + if attr in self._INTERNAL_PLUGINS_MAPPING.keys(): + return self.get_plugin(self._INTERNAL_PLUGINS_MAPPING[attr]) return self.get_plugin(attr) except SpyderAPIError: pass @@ -1510,42 +1510,18 @@ def closing(self, cancelable=False): if CONF.get('main', 'single_instance') and self.open_files_server: self.open_files_server.close() - # Internal plugins - for plugin in (self.widgetlist + self.thirdparty_plugins): - # New API - try: - if isinstance(plugin, SpyderDockablePlugin): - plugin.close_window() - if not plugin.on_close(cancelable): - return False - except AttributeError: - pass + can_close = PLUGIN_REGISTRY.delete_all_plugins( + excluding={Plugins.Layout}) - # Old API - try: - plugin._close_window() - if not plugin.closing_plugin(cancelable): - return False - except AttributeError: - pass - - # New API: External plugins - for plugin_name in PLUGIN_REGISTRY.external_plugins: - plugin_instance = PLUGIN_REGISTRY.get_plugin(plugin_name) - try: - if isinstance(plugin_instance, SpyderDockablePlugin): - plugin.close_window() - - if not plugin.on_close(cancelable): - return False - except AttributeError as e: - logger.error(str(e)) + if not can_close: + return False # Save window settings *after* closing all plugin windows, in order # to show them in their previous locations in the next session. # Fixes spyder-ide/spyder#12139 prefix = 'window' + '/' self.layouts.save_current_window_settings(prefix) + PLUGIN_REGISTRY.delete_plugin(Plugins.Layout) self.already_closed = True return True diff --git a/spyder/plugins/appearance/plugin.py b/spyder/plugins/appearance/plugin.py index ac69c277edc..6232032dbb1 100644 --- a/spyder/plugins/appearance/plugin.py +++ b/spyder/plugins/appearance/plugin.py @@ -12,7 +12,8 @@ # Local imports from spyder.api.plugins import Plugins, SpyderPluginV2 -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.plugins.appearance.confpage import AppearanceConfigPage @@ -34,6 +35,7 @@ class Appearance(SpyderPluginV2): CONF_SECTION = NAME CONF_WIDGET_CLASS = AppearanceConfigPage CONF_FILE = False + CAN_BE_DISABLED = False # --- SpyderPluginV2 API # ------------------------------------------------------------------------ @@ -53,3 +55,8 @@ def on_initialize(self): def register_preferences(self): preferences = self.get_plugin(Plugins.Preferences) preferences.register_plugin_preferences(self) + + @on_plugin_teardown(plugin=Plugins.Preferences) + def deregister_preferences(self): + preferences = self.get_plugin(Plugins.Preferences) + preferences.deregister_plugin_preferences(self) diff --git a/spyder/plugins/application/plugin.py b/spyder/plugins/application/plugin.py index 61bd31a5281..55900ef9dfe 100644 --- a/spyder/plugins/application/plugin.py +++ b/spyder/plugins/application/plugin.py @@ -103,7 +103,7 @@ def on_editor_available(self): # -------------------------- PLUGIN TEARDOWN ------------------------------ # @on_plugin_teardown(plugin=Plugins.) - def on_close(self): + def on_close(self, _unused=True): self.get_container().on_close() def on_mainwindow_visible(self): diff --git a/spyder/plugins/editor/plugin.py b/spyder/plugins/editor/plugin.py index 2c6235f8435..bfd0d7c41bf 100644 --- a/spyder/plugins/editor/plugin.py +++ b/spyder/plugins/editor/plugin.py @@ -404,7 +404,11 @@ def stop_completion_services(self, language): def send_completion_request(self, language, request, params): logger.debug("Perform request {0} for: {1}".format( request, params['file'])) - self.main.completions.send_request(language, request, params) + try: + self.main.completions.send_request(language, request, params) + except AttributeError: + # Completions was closed + pass @Slot(str, tuple, dict) def _rpc_call(self, method, args, kwargs): diff --git a/spyder/plugins/ipythonconsole/plugin.py b/spyder/plugins/ipythonconsole/plugin.py index c6b9f59a338..6a7e3503c7b 100644 --- a/spyder/plugins/ipythonconsole/plugin.py +++ b/spyder/plugins/ipythonconsole/plugin.py @@ -585,7 +585,9 @@ def refresh_plugin(self): if client: sw = client.shellwidget - self.main.variableexplorer.set_shellwidget(sw) + variableexplorer = getattr(self.main, 'variableexplorer', None) + if variableexplorer: + variableexplorer.set_shellwidget(sw) self.sig_pdb_state_changed.emit( sw.is_waiting_pdb_input(), sw.get_pdb_last_step()) self.sig_shellwidget_changed.emit(sw) @@ -1811,8 +1813,9 @@ def shellwidget_started(self, client): self.sig_shellwidget_created.emit(client.shellwidget) def shellwidget_deleted(self, client): - if self.main.variableexplorer is not None: - self.main.variableexplorer.remove_shellwidget(client.shellwidget) + variableexplorer = getattr(self.main, 'variableexplorer', None) + if variableexplorer: + variableexplorer.remove_shellwidget(client.shellwidget) self.sig_shellwidget_deleted.emit(client.shellwidget) diff --git a/spyder/plugins/preferences/plugin.py b/spyder/plugins/preferences/plugin.py index e14b382aab0..c8d6881abab 100644 --- a/spyder/plugins/preferences/plugin.py +++ b/spyder/plugins/preferences/plugin.py @@ -114,7 +114,7 @@ def deregister_plugin_preferences( # Remove additional configuration tabs that the plugin did introduce if isinstance(plugin, SpyderPluginV2): - for plugin_name in plugin.ADDITIONAL_CONF_TABS: + for plugin_name in (plugin.ADDITIONAL_CONF_TABS or []): tabs = plugin.ADDITIONAL_CONF_TABS[plugin_name] for tab in tabs: self.config_tabs[plugin_name].remove(tab) diff --git a/spyder/plugins/toolbar/plugin.py b/spyder/plugins/toolbar/plugin.py index 0b85980428c..365cf8ab240 100644 --- a/spyder/plugins/toolbar/plugin.py +++ b/spyder/plugins/toolbar/plugin.py @@ -101,11 +101,6 @@ def on_mainwindow_visible(self): container.create_toolbars_menu() container.load_last_visible_toolbars() - def on_close(self): - container = self.get_container() - if container._toolbars_visible: - self.save_visible_toolbars() - # --- Public API # ------------------------------------------------------------------------ def create_application_toolbar(self, toolbar_id, title): From 3ddada27538ca24f0aed74ed034fbcdd3c685797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:06 -0500 Subject: [PATCH 03/68] Remove direct ipythonconsole reference from the editor --- spyder/plugins/editor/plugin.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spyder/plugins/editor/plugin.py b/spyder/plugins/editor/plugin.py index bfd0d7c41bf..24d31f52258 100644 --- a/spyder/plugins/editor/plugin.py +++ b/spyder/plugins/editor/plugin.py @@ -2556,9 +2556,10 @@ def current_file_changed(self, filename, position, line, column): current_stack.hide_tooltip() # Update debugging state - if self.main.ipyconsole is not None: - pdb_state = self.main.ipyconsole.get_pdb_state() - pdb_last_step = self.main.ipyconsole.get_pdb_last_step() + ipyconsole = getattr(self.main, 'ipyconsole', None) + if ipyconsole is not None: + pdb_state = ipyconsole.get_pdb_state() + pdb_last_step = ipyconsole.get_pdb_last_step() self.update_pdb_state(pdb_state, pdb_last_step) def current_editor_cursor_changed(self, line, column): From 55b8a43b1e8f7fe7c9add91aa8fa37fb15077ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:06 -0500 Subject: [PATCH 04/68] Prevent hard reference to the editor on the mainwindow --- spyder/app/mainwindow.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py index bb342869c15..efec423499f 100644 --- a/spyder/app/mainwindow.py +++ b/spyder/app/mainwindow.py @@ -1368,10 +1368,11 @@ def update_edit_menu(self): # instance console, not_readonly, readwrite_editor = textedit_properties - # Editor has focus and there is no file opened in it - if (not console and not_readonly and self.editor - and not self.editor.is_file_opened()): - return + if hasattr(self, 'editor'): + # Editor has focus and there is no file opened in it + if (not console and not_readonly and self.editor + and not self.editor.is_file_opened()): + return # Disabling all actions to begin with for child in self.edit_menu.actions(): From 5d595c7ab1b411046d545c15792b057768692497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:06 -0500 Subject: [PATCH 05/68] Check another reference to the editor in the mainwindow --- spyder/app/mainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py index efec423499f..252cce06826 100644 --- a/spyder/app/mainwindow.py +++ b/spyder/app/mainwindow.py @@ -1395,7 +1395,7 @@ def update_edit_menu(self): # Comment, uncomment, indent, unindent... if not console and not_readonly: # This is the editor and current file is writable - if self.editor: + if hasattr(self, 'editor'): for action in self.editor.edit_menu_actions: action.setEnabled(True) From f47e146c1d8991f191a3c31de36d9604f1f70f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:06 -0500 Subject: [PATCH 06/68] Restore toolbar plugin on_close --- spyder/plugins/toolbar/plugin.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spyder/plugins/toolbar/plugin.py b/spyder/plugins/toolbar/plugin.py index 365cf8ab240..90d0e72e44e 100644 --- a/spyder/plugins/toolbar/plugin.py +++ b/spyder/plugins/toolbar/plugin.py @@ -101,6 +101,11 @@ def on_mainwindow_visible(self): container.create_toolbars_menu() container.load_last_visible_toolbars() + def on_close(self, _unused): + container = self.get_container() + if container._visible_toolbars: + container._save_visible_toolbars() + # --- Public API # ------------------------------------------------------------------------ def create_application_toolbar(self, toolbar_id, title): From cee776ece82199ab641871830796ea874eff5d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:07 -0500 Subject: [PATCH 07/68] Prevent AttributeError during tests related to layouts --- spyder/app/mainwindow.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py index 252cce06826..a065dde10cd 100644 --- a/spyder/app/mainwindow.py +++ b/spyder/app/mainwindow.py @@ -34,6 +34,8 @@ import threading import traceback +from attr import has + #============================================================================== # Check requirements before proceeding #============================================================================== @@ -1460,12 +1462,13 @@ def resizeEvent(self, event): def moveEvent(self, event): """Reimplement Qt method""" - if not self.isMaximized() and not self.layouts.get_fullscreen_flag(): - self.window_position = self.pos() - QMainWindow.moveEvent(self, event) + if hasattr(self, 'layouts'): + if not self.isMaximized() and not self.layouts.get_fullscreen_flag(): + self.window_position = self.pos() + QMainWindow.moveEvent(self, event) - # To be used by the tour to be able to move - self.sig_moved.emit(event) + # To be used by the tour to be able to move + self.sig_moved.emit(event) def hideEvent(self, event): """Reimplement Qt method""" From 700ae026d1fb9ffcdb194b63e07556380e3207a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:07 -0500 Subject: [PATCH 08/68] Apply review comments --- spyder/api/plugin_registration/registry.py | 8 ++++---- spyder/app/mainwindow.py | 3 --- spyder/plugins/application/plugin.py | 2 -- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/spyder/api/plugin_registration/registry.py b/spyder/api/plugin_registration/registry.py index 0d0c28576eb..0f7102c5426 100644 --- a/spyder/api/plugin_registration/registry.py +++ b/spyder/api/plugin_registration/registry.py @@ -394,15 +394,15 @@ def delete_plugin(self, plugin_name: str) -> bool: self.plugin_dependents.pop(plugin_name, None) self.plugin_dependencies.pop(plugin_name, None) - for plugin_ in self.plugin_dependents: - all_plugin_dependents = self.plugin_dependents[plugin_] + for plugin in self.plugin_dependents: + all_plugin_dependents = self.plugin_dependents[plugin] for key in {'requires', 'optional'}: plugin_dependents = all_plugin_dependents.get(key, []) if plugin_name in plugin_dependents: plugin_dependents.remove(plugin_name) - for plugin_ in self.plugin_dependencies: - all_plugin_dependencies = self.plugin_dependencies[plugin_] + for plugin in self.plugin_dependencies: + all_plugin_dependencies = self.plugin_dependencies[plugin] for key in {'requires', 'optional'}: plugin_dependencies = all_plugin_dependencies.get(key, []) if plugin_name in plugin_dependencies: diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py index a065dde10cd..d1ef70ac8be 100644 --- a/spyder/app/mainwindow.py +++ b/spyder/app/mainwindow.py @@ -19,7 +19,6 @@ # ============================================================================= # Stdlib imports # ============================================================================= -from __future__ import print_function from collections import OrderedDict from enum import Enum import errno @@ -34,8 +33,6 @@ import threading import traceback -from attr import has - #============================================================================== # Check requirements before proceeding #============================================================================== diff --git a/spyder/plugins/application/plugin.py b/spyder/plugins/application/plugin.py index 55900ef9dfe..d27c09f5bb5 100644 --- a/spyder/plugins/application/plugin.py +++ b/spyder/plugins/application/plugin.py @@ -101,8 +101,6 @@ def on_editor_available(self): self.get_container().sig_load_log_file.connect(editor.load) # -------------------------- PLUGIN TEARDOWN ------------------------------ - # @on_plugin_teardown(plugin=Plugins.) - def on_close(self, _unused=True): self.get_container().on_close() From 1559bc2c83fb208801a75cffc6419fa30cedae39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:07 -0500 Subject: [PATCH 09/68] Add deprecation warning for unregister --- spyder/api/plugins/new_api.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/spyder/api/plugins/new_api.py b/spyder/api/plugins/new_api.py index aa5d429b4c9..564e59f9274 100644 --- a/spyder/api/plugins/new_api.py +++ b/spyder/api/plugins/new_api.py @@ -17,6 +17,7 @@ import os import os.path as osp from typing import List, Union +import warnings # Third party imports from qtpy.QtCore import QObject, Qt, Signal, Slot @@ -709,13 +710,6 @@ def on_initialize(self): # --- API: Optional methods to override ---------------------------------- # ------------------------------------------------------------------------ - def unregister(self): - """ - Disconnect signals and clean up the plugin to be able to stop it while - Spyder is running. - """ - pass - @staticmethod def check_compatibility(): """ @@ -761,12 +755,16 @@ def on_mainwindow_visible(self): def on_close(self, cancelable=False): """ - Perform actions before the main window is closed. + Perform actions before the plugin is closed. This method **must** only operate on local attributes and not other plugins. """ - pass + if hasattr(self, 'unregister'): + warnings.warn('The unregister method was deprecated and it ' + 'was replaced by `on_close`. Please see the ' + 'Spyder 5.2.0 migration guide to get more ' + 'information.') def can_close(self) -> bool: """ From 1d4fb823e350f25f205161e33b4cb162c3548f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:07 -0500 Subject: [PATCH 10/68] Remove call to unregister --- spyder/app/mainwindow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py index d1ef70ac8be..ca738e1cb82 100644 --- a/spyder/app/mainwindow.py +++ b/spyder/app/mainwindow.py @@ -340,7 +340,6 @@ def unregister_plugin(self, plugin): logger.info("Removing {} dockwidget...".format(plugin.NAME)) self.remove_dockwidget(plugin) - plugin.unregister() plugin._unregister() def create_plugin_conf_widget(self, plugin): From 679350bbf5b3b6445245677d0a58ad788d683e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:07 -0500 Subject: [PATCH 11/68] Remove unregister from preferences --- spyder/plugins/preferences/plugin.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/spyder/plugins/preferences/plugin.py b/spyder/plugins/preferences/plugin.py index c8d6881abab..b755a4f0e94 100644 --- a/spyder/plugins/preferences/plugin.py +++ b/spyder/plugins/preferences/plugin.py @@ -314,10 +314,6 @@ def reset(self): application = self.get_plugin(Plugins.Application) application.sig_restart_requested.emit() - - def unregister(self): - pass - def on_close(self, cancelable=False) -> bool: container = self.get_container() return not container.is_dialog_open() From 9941b36f190696c327b4224879585611dbb76b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:08 -0500 Subject: [PATCH 12/68] Start application migration to use the teardown mechanism --- spyder/plugins/application/plugin.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spyder/plugins/application/plugin.py b/spyder/plugins/application/plugin.py index d27c09f5bb5..93bfbec05d4 100644 --- a/spyder/plugins/application/plugin.py +++ b/spyder/plugins/application/plugin.py @@ -101,6 +101,11 @@ def on_editor_available(self): self.get_container().sig_load_log_file.connect(editor.load) # -------------------------- PLUGIN TEARDOWN ------------------------------ + @on_plugin_teardown(plugin=Plugins.Preferences) + def on_preferences_teardown(self): + preferences = self.get_plugin(Plugins.Preferences) + preferences.deregister_plugin_preferences(self) + def on_close(self, _unused=True): self.get_container().on_close() From 8bd3f9b566f1f547a5cffff54656c201c2333147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:08 -0500 Subject: [PATCH 13/68] Enable tests in forks --- .github/workflows/test-linux.yml | 8 ++++---- .github/workflows/test-mac.yml | 8 ++++---- .github/workflows/test-win.yml | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml index 067c2b5e260..6a39bcd2ad8 100644 --- a/.github/workflows/test-linux.yml +++ b/.github/workflows/test-linux.yml @@ -16,10 +16,10 @@ on: - '**.sh' pull_request: - branches: - - master - - 5.* - - 4.x + # branches: + # - master + # - 5.* + # - 4.x paths: - '.github/scripts/*.sh' - '.github/workflows/*.yml' diff --git a/.github/workflows/test-mac.yml b/.github/workflows/test-mac.yml index 37c2dbcde4f..95a74ab2c35 100644 --- a/.github/workflows/test-mac.yml +++ b/.github/workflows/test-mac.yml @@ -16,10 +16,10 @@ on: - '**.sh' pull_request: - branches: - - master - - 5.* - - 4.x + # branches: + # - master + # - 5.* + # - 4.x paths: - '.github/scripts/*.sh' - '.github/workflows/*.yml' diff --git a/.github/workflows/test-win.yml b/.github/workflows/test-win.yml index c5f39301066..70c2f8bc776 100644 --- a/.github/workflows/test-win.yml +++ b/.github/workflows/test-win.yml @@ -16,10 +16,10 @@ on: - '**.sh' pull_request: - branches: - - master - - 5.* - - 4.x + # branches: + # - master + # - 5.* + # - 4.x paths: - '.github/scripts/*.sh' - '.github/workflows/*.yml' From ef2741fc3bfe7bb651d02538fdd12abd32c84801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:08 -0500 Subject: [PATCH 14/68] Disconnect console and shortcuts --- spyder/api/plugins/new_api.py | 2 +- spyder/plugins/application/plugin.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/spyder/api/plugins/new_api.py b/spyder/api/plugins/new_api.py index 564e59f9274..a4929f2b96e 100644 --- a/spyder/api/plugins/new_api.py +++ b/spyder/api/plugins/new_api.py @@ -603,7 +603,7 @@ def initialize(self): Notes ----- This method should be called to initialize the plugin, but it should - not be overriden, since it internally calls `on_initialize` and emits + not be overridden, since it internally calls `on_initialize` and emits the `sig_plugin_ready` signal. """ self.on_initialize() diff --git a/spyder/plugins/application/plugin.py b/spyder/plugins/application/plugin.py index 93bfbec05d4..c73130f68a6 100644 --- a/spyder/plugins/application/plugin.py +++ b/spyder/plugins/application/plugin.py @@ -106,6 +106,21 @@ def on_preferences_teardown(self): preferences = self.get_plugin(Plugins.Preferences) preferences.deregister_plugin_preferences(self) + @on_plugin_teardown(plugin=Plugins.Editor) + def on_editor_teardown(self): + editor = self.get_plugin(Plugins.Editor) + self.get_container().sig_load_log_file.disconnect(editor.load) + + @on_plugin_teardown(plugin=Plugins.Console) + def on_console_teardown(self): + if self.is_plugin_available(Plugins.MainMenu): + self.report_action.setVisible(False) + + @on_plugin_teardown(plugin=Plugins.Shortcuts) + def on_shortcuts_teardown(self): + if self.is_plugin_available(Plugins.MainMenu): + self._depopulate_help_menu() + def on_close(self, _unused=True): self.get_container().on_close() @@ -211,6 +226,9 @@ def _populate_help_menu_about_section(self): def _window(self): return self.main.window() + def _depopulate_help_menu(self): + pass + # ---- Public API # ------------------------------------------------------------------------ def get_application_context_menu(self, parent=None): From 5df20c5c3f9baf2a6e03fdb9cbfff16f0f615c03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:08 -0500 Subject: [PATCH 15/68] Add remove_item_from_application_menu method in main menu --- spyder/api/widgets/menus.py | 4 +++ spyder/plugins/application/plugin.py | 31 ++++++++++++++++++- spyder/plugins/mainmenu/plugin.py | 46 ++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/spyder/api/widgets/menus.py b/spyder/api/widgets/menus.py index a83b736614b..10e6a863553 100644 --- a/spyder/api/widgets/menus.py +++ b/spyder/api/widgets/menus.py @@ -168,6 +168,10 @@ def add_action(self: T, self._dirty = True self._actions_map[item_id] = action + def remove_action(self, item_id: str): + action = self._actions_map.pop(item_id) + self._actions.remove(action) + def get_title(self): """ Return the title for menu. diff --git a/spyder/plugins/application/plugin.py b/spyder/plugins/application/plugin.py index c73130f68a6..11952911807 100644 --- a/spyder/plugins/application/plugin.py +++ b/spyder/plugins/application/plugin.py @@ -227,7 +227,36 @@ def _window(self): return self.main.window() def _depopulate_help_menu(self): - pass + self._depopulate_help_menu_documentation_section() + self._depopulate_help_menu_support_section() + self._depopulate_help_menu_about_section() + + def _depopulate_help_menu_documentation_section(self): + mainmenu = self.get_plugin(Plugins.MainMenu) + for documentation_action in [ + ApplicationActions.SpyderDocumentationAction, + ApplicationActions.SpyderDocumentationVideoAction]: + mainmenu.remove_item_from_application_menu( + documentation_action, + menu_id=ApplicationMenus.Help) + + def _depopulate_help_menu_support_section(self): + """Remove Spyder base support actions from the Help main menu.""" + mainmenu = self.get_plugin(Plugins.MainMenu) + for support_action in [ + ApplicationActions.SpyderTroubleshootingAction, + ApplicationActions.SpyderDependenciesAction, + ApplicationActions.SpyderCheckUpdatesAction, + ApplicationActions.SpyderSupportAction]: + mainmenu.remove_item_from_application_menu( + support_action, + menu_id=ApplicationMenus.Help) + + def _depopulate_help_menu_about_section(self): + mainmenu = self.get_plugin(Plugins.MainMenu) + mainmenu.remove_item_from_application_menu( + ApplicationActions.SpyderAbout, + menu_id=ApplicationMenus.Help) # ---- Public API # ------------------------------------------------------------------------ diff --git a/spyder/plugins/mainmenu/plugin.py b/spyder/plugins/mainmenu/plugin.py index 672fff513b1..ad247beaf66 100644 --- a/spyder/plugins/mainmenu/plugin.py +++ b/spyder/plugins/mainmenu/plugin.py @@ -264,6 +264,52 @@ def add_item_to_application_menu(self, item: ItemType, menu.add_action(item, section=section, before=before, before_section=before_section, omit_id=omit_id) + def remove_item_from_application_menu(self, item_id: str, + menu_id: Optional[str] = None): + """ + Remove action or widget from given application menu by id. + + Parameters + ---------- + item_id: str + The item identifier to remove from the given menu. + menu_id: str or None + The application menu unique string identifier. + """ + if menu_id not in self._APPLICATION_MENUS: + raise SpyderAPIError('{} is not a valid menu_id'.format(menu_id)) + + # TODO: For now just add the item to the bottom for non-migrated menus. + # Temporal solution while migration is complete + app_menu_actions = { + ApplicationMenus.Edit: self._main.edit_menu_actions, + ApplicationMenus.Search: self._main.search_menu_actions, + ApplicationMenus.Source: self._main.source_menu_actions, + ApplicationMenus.Run: self._main.run_menu_actions, + ApplicationMenus.Debug: self._main.debug_menu_actions, + } + + menu = self.get_application_menu(menu_id) + + if menu_id in app_menu_actions: + actions = app_menu_actions[menu_id] # type: list + position = None + for i, action in enumerate(actions): + this_item_id = None + if (isinstance(action, SpyderAction) or + hasattr(action, 'action_id')): + this_item_id = action.action_id + elif (isinstance(action, SpyderMenu) or + hasattr(action, 'menu_id')): + this_item_id = action.menu_id + if this_item_id is not None and this_item_id == item_id: + position = i + break + if position is not None: + actions.pop(position) + else: + menu.remove_action(item_id) + def get_application_menu(self, menu_id: str) -> SpyderMenu: """ Return an application menu by menu unique id. From 2984c6dcb2376946e00420497c4aa01a48400b34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:08 -0500 Subject: [PATCH 16/68] Disconnect from main menu --- spyder/api/widgets/menus.py | 5 ++-- spyder/plugins/application/plugin.py | 34 +++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/spyder/api/widgets/menus.py b/spyder/api/widgets/menus.py index 10e6a863553..7ed34a31e5c 100644 --- a/spyder/api/widgets/menus.py +++ b/spyder/api/widgets/menus.py @@ -169,8 +169,9 @@ def add_action(self: T, self._actions_map[item_id] = action def remove_action(self, item_id: str): - action = self._actions_map.pop(item_id) - self._actions.remove(action) + if item_id in self._actions_map: + action = self._actions_map.pop(item_id) + self._actions.remove(action) def get_title(self): """ diff --git a/spyder/plugins/application/plugin.py b/spyder/plugins/application/plugin.py index 11952911807..1f507f81dd3 100644 --- a/spyder/plugins/application/plugin.py +++ b/spyder/plugins/application/plugin.py @@ -28,7 +28,9 @@ running_under_pytest) from spyder.plugins.application.confpage import ApplicationConfigPage from spyder.plugins.application.container import ( - ApplicationContainer, ApplicationPluginMenus, WinUserEnvDialog) + ApplicationActions, ApplicationContainer, ApplicationPluginMenus, + WinUserEnvDialog) +from spyder.plugins.console.api import ConsoleActions from spyder.plugins.mainmenu.api import ( ApplicationMenus, FileMenuSections, HelpMenuSections, ToolsMenuSections) from spyder.utils.qthelpers import add_actions @@ -121,6 +123,13 @@ def on_shortcuts_teardown(self): if self.is_plugin_available(Plugins.MainMenu): self._depopulate_help_menu() + @on_plugin_teardown(plugin=Plugins.MainMenu) + def on_main_menu_teardown(self): + self._depopulate_file_menu() + self._depopulate_tools_menu() + self._depopulate_help_menu() + self.report_action.setVisible(False) + def on_close(self, _unused=True): self.get_container().on_close() @@ -245,6 +254,7 @@ def _depopulate_help_menu_support_section(self): mainmenu = self.get_plugin(Plugins.MainMenu) for support_action in [ ApplicationActions.SpyderTroubleshootingAction, + ConsoleActions.SpyderReportAction, ApplicationActions.SpyderDependenciesAction, ApplicationActions.SpyderCheckUpdatesAction, ApplicationActions.SpyderSupportAction]: @@ -258,6 +268,28 @@ def _depopulate_help_menu_about_section(self): ApplicationActions.SpyderAbout, menu_id=ApplicationMenus.Help) + def _depopulate_file_menu(self): + mainmenu = self.get_plugin(Plugins.MainMenu) + mainmenu.remove_item_from_application_menu( + ApplicationActions.SpyderRestart, + menu_id=ApplicationMenus.File) + mainmenu.remove_item_from_application_menu( + ApplicationActions.SpyderRestartDebug, + menu_id=ApplicationMenus.File) + + def _depopulate_tools_menu(self): + """Add base actions and menus to the Tools menu.""" + mainmenu = self.get_plugin(Plugins.MainMenu) + if WinUserEnvDialog is not None: + mainmenu.remove_item_from_application_menu( + ApplicationActions.SpyderWindowsEnvVariables, + menu_id=ApplicationMenus.Tools) + + if get_debug_level() >= 2: + mainmenu.remove_item_from_application_menu( + ApplicationPluginMenus.DebugLogsMenu, + menu_id=ApplicationMenus.Tools) + # ---- Public API # ------------------------------------------------------------------------ def get_application_context_menu(self, parent=None): From 8175a4349678b365e6d2354f29a59c5455a7725c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:08 -0500 Subject: [PATCH 17/68] Prevent non-existing action removal --- spyder/api/widgets/menus.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/spyder/api/widgets/menus.py b/spyder/api/widgets/menus.py index 7ed34a31e5c..c5a6c597e2d 100644 --- a/spyder/api/widgets/menus.py +++ b/spyder/api/widgets/menus.py @@ -171,7 +171,15 @@ def add_action(self: T, def remove_action(self, item_id: str): if item_id in self._actions_map: action = self._actions_map.pop(item_id) - self._actions.remove(action) + position = None + + for i, (_, act) in enumerate(self._actions): + if act == action: + position = i + break + + if position is not None: + self._actions.pop(position) def get_title(self): """ From 06e8ab688ea1a93aa625381adc4a84e199dc1f1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:08 -0500 Subject: [PATCH 18/68] Apply review comments --- spyder/api/widgets/menus.py | 1 + spyder/plugins/application/plugin.py | 11 +++++------ spyder/plugins/mainmenu/plugin.py | 25 ++++++++++++++++++++----- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/spyder/api/widgets/menus.py b/spyder/api/widgets/menus.py index c5a6c597e2d..11590318e0c 100644 --- a/spyder/api/widgets/menus.py +++ b/spyder/api/widgets/menus.py @@ -180,6 +180,7 @@ def remove_action(self, item_id: str): if position is not None: self._actions.pop(position) + self._dirty = True def get_title(self): """ diff --git a/spyder/plugins/application/plugin.py b/spyder/plugins/application/plugin.py index 1f507f81dd3..ff1be3ea0d7 100644 --- a/spyder/plugins/application/plugin.py +++ b/spyder/plugins/application/plugin.py @@ -270,12 +270,11 @@ def _depopulate_help_menu_about_section(self): def _depopulate_file_menu(self): mainmenu = self.get_plugin(Plugins.MainMenu) - mainmenu.remove_item_from_application_menu( - ApplicationActions.SpyderRestart, - menu_id=ApplicationMenus.File) - mainmenu.remove_item_from_application_menu( - ApplicationActions.SpyderRestartDebug, - menu_id=ApplicationMenus.File) + for action_id in [ApplicationActions.SpyderRestart, + ApplicationActions.SpyderRestartDebug]: + mainmenu.remove_item_from_application_menu( + action_id, + menu_id=ApplicationMenus.File) def _depopulate_tools_menu(self): """Add base actions and menus to the Tools menu.""" diff --git a/spyder/plugins/mainmenu/plugin.py b/spyder/plugins/mainmenu/plugin.py index ad247beaf66..849f7279c64 100644 --- a/spyder/plugins/mainmenu/plugin.py +++ b/spyder/plugins/mainmenu/plugin.py @@ -282,17 +282,31 @@ def remove_item_from_application_menu(self, item_id: str, # TODO: For now just add the item to the bottom for non-migrated menus. # Temporal solution while migration is complete app_menu_actions = { - ApplicationMenus.Edit: self._main.edit_menu_actions, - ApplicationMenus.Search: self._main.search_menu_actions, - ApplicationMenus.Source: self._main.source_menu_actions, - ApplicationMenus.Run: self._main.run_menu_actions, - ApplicationMenus.Debug: self._main.debug_menu_actions, + ApplicationMenus.Edit: ( + self._main.edit_menu_actions, self._main.edit_menu), + ApplicationMenus.Search: ( + self._main.search_menu_actions, self._main.search_menu), + ApplicationMenus.Source: ( + self._main.source_menu_actions, self._main.source_menu), + ApplicationMenus.Run: ( + self._main.run_menu_actions, self._main.run_menu), + ApplicationMenus.Debug: ( + self._main.debug_menu_actions, self._main.debug_menu), + } + + app_menus = { + ApplicationMenus.Edit: self._main.edit_menu, + ApplicationMenus.Search: self._main.search_menu, + ApplicationMenus.Source: self._main.source_menu, + ApplicationMenus.Run: self._main.run_menu, + ApplicationMenus.Debug: self._main.debug_menu } menu = self.get_application_menu(menu_id) if menu_id in app_menu_actions: actions = app_menu_actions[menu_id] # type: list + menu = app_menus[menu_id] position = None for i, action in enumerate(actions): this_item_id = None @@ -307,6 +321,7 @@ def remove_item_from_application_menu(self, item_id: str, break if position is not None: actions.pop(position) + menu.remove_action(item_id) else: menu.remove_action(item_id) From 1603da36cb09ec88c3fce4745391a4202e38d992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:09 -0500 Subject: [PATCH 19/68] Remove shortcuts teardown method --- spyder/plugins/application/plugin.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spyder/plugins/application/plugin.py b/spyder/plugins/application/plugin.py index ff1be3ea0d7..d2267d84e32 100644 --- a/spyder/plugins/application/plugin.py +++ b/spyder/plugins/application/plugin.py @@ -118,11 +118,6 @@ def on_console_teardown(self): if self.is_plugin_available(Plugins.MainMenu): self.report_action.setVisible(False) - @on_plugin_teardown(plugin=Plugins.Shortcuts) - def on_shortcuts_teardown(self): - if self.is_plugin_available(Plugins.MainMenu): - self._depopulate_help_menu() - @on_plugin_teardown(plugin=Plugins.MainMenu) def on_main_menu_teardown(self): self._depopulate_file_menu() From 34ae1da6d8ebd72a22853b0e664441115f242aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:09 -0500 Subject: [PATCH 20/68] Migrate breakpoints to use the new teardown mechanism --- spyder/plugins/breakpoints/plugin.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/spyder/plugins/breakpoints/plugin.py b/spyder/plugins/breakpoints/plugin.py index 2c7a20a1146..188cfc4948b 100644 --- a/spyder/plugins/breakpoints/plugin.py +++ b/spyder/plugins/breakpoints/plugin.py @@ -18,7 +18,8 @@ # Local imports from spyder.api.plugins import Plugins, SpyderDockablePlugin -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.plugins.breakpoints.widgets.main_widget import BreakpointWidget from spyder.plugins.mainmenu.api import ApplicationMenus @@ -140,6 +141,30 @@ def on_main_menu_available(self): mainmenu.add_item_to_application_menu( list_action, menu_id=ApplicationMenus.Debug) + @on_plugin_teardown(plugin=Plugins.Editor) + def on_editor_teardown(self): + widget = self.get_widget() + editor = self.get_plugin(Plugins.Editor) + list_action = self.get_action(BreakpointsActions.ListBreakpoints) + + editor.breakpoints_saved.disconnect(self.set_data) + widget.sig_clear_all_breakpoints_requested.disconnect( + editor.clear_all_breakpoints) + widget.sig_clear_breakpoint_requested.disconnect( + editor.clear_breakpoint) + widget.sig_edit_goto_requested.disconnect(editor.load) + widget.sig_conditional_breakpoint_requested.disconnect( + editor.set_or_edit_conditional_breakpoint) + + editor.pythonfile_dependent_actions.remove(list_action) + + @on_plugin_teardown(plugin=Plugins.MainMenu) + def on_main_menu_teardown(self): + mainmenu = self.get_plugin(Plugins.MainMenu) + list_action = self.get_action(BreakpointsActions.ListBreakpoints) + debug_menu = mainmenu.get_application_menu(ApplicationMenus.Debug) + mainmenu.remove_item_from_application_menu(list_action, debug_menu) + # --- Private API # ------------------------------------------------------------------------ def _load_data(self): From 521400bde815a68f144cd00e3e88e5b3be9262e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:09 -0500 Subject: [PATCH 21/68] Update remove_item_from_application_menu call --- spyder/plugins/breakpoints/plugin.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spyder/plugins/breakpoints/plugin.py b/spyder/plugins/breakpoints/plugin.py index 188cfc4948b..302e2e24cff 100644 --- a/spyder/plugins/breakpoints/plugin.py +++ b/spyder/plugins/breakpoints/plugin.py @@ -161,9 +161,8 @@ def on_editor_teardown(self): @on_plugin_teardown(plugin=Plugins.MainMenu) def on_main_menu_teardown(self): mainmenu = self.get_plugin(Plugins.MainMenu) - list_action = self.get_action(BreakpointsActions.ListBreakpoints) - debug_menu = mainmenu.get_application_menu(ApplicationMenus.Debug) - mainmenu.remove_item_from_application_menu(list_action, debug_menu) + mainmenu.remove_item_from_application_menu( + BreakpointsActions.ListBreakpoints, menu_id=ApplicationMenus.Debug) # --- Private API # ------------------------------------------------------------------------ From be435d9ef44fb49ddf215a936f63a4288ac89487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:09 -0500 Subject: [PATCH 22/68] Migrate completions plugin to use the teardown mechanism --- spyder/plugins/completion/plugin.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/spyder/plugins/completion/plugin.py b/spyder/plugins/completion/plugin.py index 0587dc43d74..02574544bd4 100644 --- a/spyder/plugins/completion/plugin.py +++ b/spyder/plugins/completion/plugin.py @@ -27,7 +27,8 @@ # Local imports from spyder.config.manager import CONF from spyder.api.plugins import SpyderPluginV2, Plugins -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.config.base import _, running_under_pytest from spyder.config.user import NoDefault from spyder.plugins.completion.api import (CompletionRequestTypes, @@ -310,6 +311,18 @@ def on_mainmenu_available(self): for args, kwargs in self.items_to_add_to_application_menus: main_menu.add_item_to_application_menu(*args, **kwargs) + @on_plugin_teardown(plugin=Plugins.Preferences) + def on_preferences_teardown(self): + preferences = self.get_plugin(Plugins.Preferences) + preferences.deregister_plugin_preferences(self) + + @on_plugin_teardown(plugin=Plugins.StatusBar) + def on_statusbar_teardown(self): + container = self.get_container() + self.statusbar = self.get_plugin(Plugins.StatusBar) + for sb in container.all_statusbar_widgets(): + self.statusbar.remove_status_widget(sb.ID) + def unregister(self): """Stop all running completion providers.""" for provider_name in self.providers: From cdae372ff388b9b8d28bc04d946dbb04cdee9262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:09 -0500 Subject: [PATCH 23/68] Remove kite-defined actions --- spyder/plugins/completion/api.py | 49 +++++++++++++++++++ spyder/plugins/completion/plugin.py | 9 ++++ .../completion/providers/kite/provider.py | 9 ++++ 3 files changed, 67 insertions(+) diff --git a/spyder/plugins/completion/api.py b/spyder/plugins/completion/api.py index 4e2b01b8bba..b9a1b9329b4 100644 --- a/spyder/plugins/completion/api.py +++ b/spyder/plugins/completion/api.py @@ -1275,6 +1275,35 @@ def create_action(self, name, text, icon=None, icon_text='', tip=None, shortcut_context=shortcut_context, context=context, initial=initial, register_shortcut=register_shortcut) + def get_action(self, name, context=None, plugin=None): + """ + Return an action by name, context and plugin. + + Parameters + ---------- + name: str + Name of the action to retrieve. + context: Optional[str] + Widget or context identifier under which the action was stored. + If None, then `CONTEXT_NAME` is used instead + plugin: Optional[str] + Name of the plugin where the action was defined. If None, then + `PLUGIN_NAME` is used. + + Returns + ------- + action: SpyderAction + The corresponding action stored under the given `name`, `context` + and `plugin`. + + Raises + ------ + KeyError + If either of `name`, `context` or `plugin` keys do not exist in the + toolbar registry. + """ + return self.main.get_action(name, context=context, plugin=plugin) + def create_application_menu(self, menu_id, title, dynamic=True): """ Create a Spyder application menu. @@ -1361,3 +1390,23 @@ def add_item_to_application_menu(self, item, menu_id=None, self.main.add_item_to_application_menu( item, menu_id=menu_id, section=section, before=before, before_section=before_section) + + def remove_item_from_application_menu(self, item, menu=None, menu_id=None): + """ + Remove action or widget `item` from given application menu. + + Parameters + ---------- + item: SpyderAction or SpyderMenu + The item to add to the `menu`. + menu: ApplicationMenu or None + Instance of a Spyder application menu. + menu_id: str or None + The application menu unique string identifier. + + Notes + ----- + Must provide a `menu` or a `menu_id`. + """ + self.main.remove_item_from_application_menu( + item, menu=menu, menu_id=menu_id) diff --git a/spyder/plugins/completion/plugin.py b/spyder/plugins/completion/plugin.py index 02574544bd4..4d69955d148 100644 --- a/spyder/plugins/completion/plugin.py +++ b/spyder/plugins/completion/plugin.py @@ -899,6 +899,10 @@ def create_action(self, *args, **kwargs): kwargs['parent'] = container return container.create_action(*args, **kwargs) + def get_action(self, *args, **kwargs): + container = self.get_container() + return container.get_action(*args, **kwargs) + def get_application_menu(self, *args, **kwargs): # TODO: Check if this method makes sense with the new plugin # registration mechanism. @@ -920,6 +924,11 @@ def create_menu(self, *args, **kwargs): def add_item_to_application_menu(self, *args, **kwargs): self.items_to_add_to_application_menus.append((args, kwargs)) + def remove_item_from_application_menu(self, *args, **kwargs): + main_menu = self.get_plugin(Plugins.MainMenu) + if main_menu: + main_menu.remove_item_from_application_menu(*args, **kwargs) + def add_item_to_menu(self, *args, **kwargs): container = self.get_container() container.add_item_to_menu(*args, **kwargs) diff --git a/spyder/plugins/completion/providers/kite/provider.py b/spyder/plugins/completion/providers/kite/provider.py index 3c2f96bfa26..4ab20b629f2 100644 --- a/spyder/plugins/completion/providers/kite/provider.py +++ b/spyder/plugins/completion/providers/kite/provider.py @@ -127,6 +127,15 @@ def start(self): self.client.start() def shutdown(self): + try: + install_action = self.get_action(KiteProviderActions.Installation) + self.add_remove_from_application_menu( + install_action, + menu_id=ApplicationMenus.Tools) + except KeyError: + # Action does not exist + pass + self.client.stop() if self.kite_process is not None: self.kite_process.kill() From 88f0a278d222947ebc311fdc00cf45cd82c73f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:09 -0500 Subject: [PATCH 24/68] Fix minor issue in Kite --- spyder/plugins/completion/providers/kite/provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder/plugins/completion/providers/kite/provider.py b/spyder/plugins/completion/providers/kite/provider.py index 4ab20b629f2..47706351c59 100644 --- a/spyder/plugins/completion/providers/kite/provider.py +++ b/spyder/plugins/completion/providers/kite/provider.py @@ -129,7 +129,7 @@ def start(self): def shutdown(self): try: install_action = self.get_action(KiteProviderActions.Installation) - self.add_remove_from_application_menu( + self.remove_item_from_application_menu( install_action, menu_id=ApplicationMenus.Tools) except KeyError: From 35900d4661d5c48b7b0f5e7ef6d3de995c7f9158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:09 -0500 Subject: [PATCH 25/68] Reimplement can_close --- spyder/plugins/completion/plugin.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/spyder/plugins/completion/plugin.py b/spyder/plugins/completion/plugin.py index 4d69955d148..64a6cdf7caf 100644 --- a/spyder/plugins/completion/plugin.py +++ b/spyder/plugins/completion/plugin.py @@ -331,6 +331,17 @@ def unregister(self): # TODO: Remove status bar widgets provider_info['instance'].shutdown() + def can_close(self) -> bool: + """Check if any provider has any pending task.""" + can_close = False + for provider_name in self.providers: + provider_info = self.providers[provider_name] + if provider_info['status'] == self.RUNNING: + provider = provider_info['instance'] + provider_can_close = provider.can_close() + can_close |= provider_can_close + return can_close + def on_close(self, cancelable=False) -> bool: """Check if any provider has any pending task before closing.""" can_close = False From d716e8a4bc75d8e8867512f677089998ca1e5d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:09 -0500 Subject: [PATCH 26/68] Update remove_item_from_application_menu calls --- spyder/plugins/completion/api.py | 18 ++++++------------ .../completion/providers/kite/provider.py | 3 +-- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/spyder/plugins/completion/api.py b/spyder/plugins/completion/api.py index b9a1b9329b4..2d52fcda6cf 100644 --- a/spyder/plugins/completion/api.py +++ b/spyder/plugins/completion/api.py @@ -1391,22 +1391,16 @@ def add_item_to_application_menu(self, item, menu_id=None, item, menu_id=menu_id, section=section, before=before, before_section=before_section) - def remove_item_from_application_menu(self, item, menu=None, menu_id=None): + def remove_item_from_application_menu(self, item_id: str, + menu_id: Optional[str] = None): """ - Remove action or widget `item` from given application menu. + Remove action or widget from given application menu by id. Parameters ---------- - item: SpyderAction or SpyderMenu - The item to add to the `menu`. - menu: ApplicationMenu or None - Instance of a Spyder application menu. + item_id: str + The item identifier to remove from the given menu. menu_id: str or None The application menu unique string identifier. - - Notes - ----- - Must provide a `menu` or a `menu_id`. """ - self.main.remove_item_from_application_menu( - item, menu=menu, menu_id=menu_id) + self.main.remove_item_from_application_menu(item_id, menu_id=menu_id) diff --git a/spyder/plugins/completion/providers/kite/provider.py b/spyder/plugins/completion/providers/kite/provider.py index 47706351c59..2118f57952e 100644 --- a/spyder/plugins/completion/providers/kite/provider.py +++ b/spyder/plugins/completion/providers/kite/provider.py @@ -128,9 +128,8 @@ def start(self): def shutdown(self): try: - install_action = self.get_action(KiteProviderActions.Installation) self.remove_item_from_application_menu( - install_action, + KiteProviderActions.Installation, menu_id=ApplicationMenus.Tools) except KeyError: # Action does not exist From 6896462e1a3c250eaf3bb5e64e9477f58be26fba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:09 -0500 Subject: [PATCH 27/68] Add remove_application_menu to main menu --- spyder/plugins/completion/plugin.py | 24 ++++++++++++++++++++++++ spyder/plugins/mainmenu/plugin.py | 13 +++++++++++++ 2 files changed, 37 insertions(+) diff --git a/spyder/plugins/completion/plugin.py b/spyder/plugins/completion/plugin.py index 64a6cdf7caf..37545716ecd 100644 --- a/spyder/plugins/completion/plugin.py +++ b/spyder/plugins/completion/plugin.py @@ -323,6 +323,30 @@ def on_statusbar_teardown(self): for sb in container.all_statusbar_widgets(): self.statusbar.remove_status_widget(sb.ID) + @on_plugin_teardown(plugin=Plugins.MainMenu) + def on_mainmenu_teardown(self): + main_menu = self.get_plugin(Plugins.MainMenu) + signature = inspect.signature(main_menu.add_item_to_application_menu) + + for args, kwargs in self.application_menus_to_create: + menu_id = args[0] + main_menu.remove_application_menu(menu_id) + + for args, kwargs in self.items_to_add_to_application_menus: + binding = signature.bind(*args, **kwargs) + binding.apply_defaults() + + item = binding.arguments['item'] + menu_id = binding.arguments['menu_id'] + item_id = None + if hasattr(item, 'action_id'): + item_id = item.action_id + elif hasattr(item, 'menu_id'): + item_id = item.menu_id + if item_id is not None: + main_menu.remove_item_from_application_menu( + item_id, menu_id=menu_id) + def unregister(self): """Stop all running completion providers.""" for provider_name in self.providers: diff --git a/spyder/plugins/mainmenu/plugin.py b/spyder/plugins/mainmenu/plugin.py index 849f7279c64..452009cca15 100644 --- a/spyder/plugins/mainmenu/plugin.py +++ b/spyder/plugins/mainmenu/plugin.py @@ -264,6 +264,19 @@ def add_item_to_application_menu(self, item: ItemType, menu.add_action(item, section=section, before=before, before_section=before_section, omit_id=omit_id) + def remove_application_menu(self, menu_id: str): + """ + Remove a Spyder application menu. + + Parameters + ---------- + menu_id: str + The menu unique identifier string. + """ + if menu_id in self._APPLICATION_MENUS: + menu = self._APPLICATION_MENUS.pop(menu_id) + self.main.menuBar().removeAction(menu.menuAction()) + def remove_item_from_application_menu(self, item_id: str, menu_id: Optional[str] = None): """ From e0fbc17c9fc37094a5145bf8f627b132fc3d1c3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:09 -0500 Subject: [PATCH 28/68] Remove unregister method --- spyder/plugins/completion/plugin.py | 2 +- spyder/plugins/completion/tests/conftest.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spyder/plugins/completion/plugin.py b/spyder/plugins/completion/plugin.py index 37545716ecd..bd985aab521 100644 --- a/spyder/plugins/completion/plugin.py +++ b/spyder/plugins/completion/plugin.py @@ -347,7 +347,7 @@ def on_mainmenu_teardown(self): main_menu.remove_item_from_application_menu( item_id, menu_id=menu_id) - def unregister(self): + def stop_all_providers(self): """Stop all running completion providers.""" for provider_name in self.providers: provider_info = self.providers[provider_name] diff --git a/spyder/plugins/completion/tests/conftest.py b/spyder/plugins/completion/tests/conftest.py index 2e22ee99725..354c0bdb076 100644 --- a/spyder/plugins/completion/tests/conftest.py +++ b/spyder/plugins/completion/tests/conftest.py @@ -188,7 +188,7 @@ def wait_until_all_started(): def teardown(): os.environ['SPY_TEST_USE_INTROSPECTION'] = 'False' - completion_plugin.unregister() + completion_plugin.stop_all_providers() request.addfinalizer(teardown) return completion_plugin, capabilities From def4e71e0fb1ba05a333dc1968223566b5cdc5b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:09 -0500 Subject: [PATCH 29/68] Migrate Console, Explorer and Find in files to use the new teardown mechanism --- spyder/plugins/console/plugin.py | 11 ++++++++++- spyder/plugins/explorer/plugin.py | 27 ++++++++++++++++++++++++++- spyder/plugins/findinfiles/plugin.py | 28 +++++++++++++++++++++++++++- 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/spyder/plugins/console/plugin.py b/spyder/plugins/console/plugin.py index f1f105f94a1..edc0206284e 100644 --- a/spyder/plugins/console/plugin.py +++ b/spyder/plugins/console/plugin.py @@ -17,7 +17,8 @@ # Local imports from spyder.api.plugins import Plugins, SpyderDockablePlugin -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.plugins.console.widgets.main_widget import ConsoleWidget from spyder.plugins.mainmenu.api import ApplicationMenus, FileMenuSections @@ -122,6 +123,14 @@ def on_main_menu_available(self): menu_id=ApplicationMenus.File, section=FileMenuSections.Restart) + @on_plugin_teardown(plugin=Plugins.MainMenu) + def on_main_menu_teardown(self): + widget = self.get_widget() + mainmenu = self.get_plugin(Plugins.MainMenu) + mainmenu.remove_item_from_application_menu( + widget.quit_action, + menu_id=ApplicationMenus.File) + def update_font(self): font = self.get_font() self.get_widget().set_font(font) diff --git a/spyder/plugins/explorer/plugin.py b/spyder/plugins/explorer/plugin.py index 3ec04a6fe36..fd82c7229bf 100644 --- a/spyder/plugins/explorer/plugin.py +++ b/spyder/plugins/explorer/plugin.py @@ -20,7 +20,8 @@ # Local imports from spyder.api.translations import get_translation from spyder.api.plugins import SpyderDockablePlugin, Plugins -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.plugins.explorer.widgets.main_widget import ExplorerWidget from spyder.plugins.explorer.confpage import ExplorerConfigPage @@ -210,6 +211,30 @@ def on_ipython_console_available(self): ipyconsole.run_script(fname, osp.dirname(fname), '', False, False, False, True, False)) + @on_plugin_teardown(plugin=Plugins.Editor) + def on_editor_teardown(self): + editor = self.get_plugin(Plugins.Editor) + + editor.sig_dir_opened.disconnect(self.chdir) + self.sig_file_created.disconnect() + self.sig_file_removed.disconnect(editor.removed) + self.sig_file_renamed.disconnect(editor.renamed) + self.sig_folder_removed.disconnect(editor.removed_tree) + self.sig_folder_renamed.disconnect(editor.renamed_tree) + self.sig_module_created.disconnect(editor.new) + self.sig_open_file_requested.disconnect(editor.load) + + @on_plugin_teardown(plugin=Plugins.Preferences) + def on_preferences_teardown(self): + preferences = self.get_plugin(Plugins.Preferences) + preferences.deregister_plugin_preferences(self) + + @on_plugin_teardown(plugin=Plugins.IPythonConsole) + def on_ipython_console_teardown(self): + ipyconsole = self.get_plugin(Plugins.IPythonConsole) + self.sig_interpreter_opened.disconnect( + ipyconsole.create_client_from_path) + self.sig_run_requested.disconnect() # ---- Public API # ------------------------------------------------------------------------ diff --git a/spyder/plugins/findinfiles/plugin.py b/spyder/plugins/findinfiles/plugin.py index 2b798a8b2b2..54681359338 100644 --- a/spyder/plugins/findinfiles/plugin.py +++ b/spyder/plugins/findinfiles/plugin.py @@ -13,7 +13,8 @@ # Local imports from spyder.api.plugins import Plugins, SpyderDockablePlugin -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.plugins.findinfiles.widgets import FindInFilesWidget from spyder.plugins.mainmenu.api import ApplicationMenus @@ -91,6 +92,31 @@ def on_main_menu_available(self): menu_id=ApplicationMenus.Search, ) + @on_plugin_teardown(plugin=Plugins.Editor) + def on_editor_teardown(self): + widget = self.get_widget() + editor = self.get_plugin(Plugins.Editor) + widget.sig_edit_goto_requested.disconnect() + editor.sig_file_opened_closed_or_updated.disconnect( + self.set_current_opened_file) + + @on_plugin_teardown(plugin=Plugins.Projects) + def on_projects_teardon_plugin_teardown(self): + projects = self.get_plugin(Plugins.Projects) + projects.sig_project_loaded.disconnect(self.set_project_path) + projects.sig_project_closed.disconnect(self.unset_project_path) + + @on_plugin_teardown(plugin=Plugins.MainMenu) + def on_main_menu_teardown(self): + mainmenu = self.get_plugin(Plugins.MainMenu) + findinfiles_action = self.get_action(FindInFilesActions.FindInFiles) + + menu = mainmenu.get_application_menu(ApplicationMenus.Search) + mainmenu.remove_item_to_application_menu( + findinfiles_action, + menu=menu, + ) + def on_close(self, cancelable=False): self.get_widget()._update_options() self.get_widget()._stop_and_reset_thread(ignore_results=True) From f55385d93cebe255951a9700cf93a6884e5812ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:10 -0500 Subject: [PATCH 30/68] Minor typo correction --- spyder/plugins/findinfiles/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder/plugins/findinfiles/plugin.py b/spyder/plugins/findinfiles/plugin.py index 54681359338..e060c28c0a9 100644 --- a/spyder/plugins/findinfiles/plugin.py +++ b/spyder/plugins/findinfiles/plugin.py @@ -112,7 +112,7 @@ def on_main_menu_teardown(self): findinfiles_action = self.get_action(FindInFilesActions.FindInFiles) menu = mainmenu.get_application_menu(ApplicationMenus.Search) - mainmenu.remove_item_to_application_menu( + mainmenu.remove_item_from_application_menu( findinfiles_action, menu=menu, ) From 5927dcb63beb441cda0d043ba9474214d04ff179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:10 -0500 Subject: [PATCH 31/68] Prevent non-existinng search_menu_actions list --- spyder/plugins/editor/plugin.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spyder/plugins/editor/plugin.py b/spyder/plugins/editor/plugin.py index 24d31f52258..e4bf9cad6ad 100644 --- a/spyder/plugins/editor/plugin.py +++ b/spyder/plugins/editor/plugin.py @@ -1069,6 +1069,7 @@ def get_plugin_actions(self): find_previous_action, replace_action, gotoline_action] + self.main.search_toolbar_actions = [find_action, find_next_action, replace_action] @@ -1081,6 +1082,10 @@ def get_plugin_actions(self): self.text_lowercase_action] # ---- Search menu/toolbar construction ---- + if not hasattr(self.main, 'search_menu_actions'): + # This list will not exist in the fast tests. + self.main.search_menu_actions = [] + self.main.search_menu_actions = ( search_menu_actions + self.main.search_menu_actions) From 0f1bead6278b94e12a3f029a31cbb7453784cc35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:10 -0500 Subject: [PATCH 32/68] Update calls to remove_item_from_application_menu --- spyder/plugins/console/plugin.py | 5 +++-- spyder/plugins/findinfiles/plugin.py | 6 ++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/spyder/plugins/console/plugin.py b/spyder/plugins/console/plugin.py index edc0206284e..7efde26f2c8 100644 --- a/spyder/plugins/console/plugin.py +++ b/spyder/plugins/console/plugin.py @@ -20,7 +20,8 @@ from spyder.api.plugin_registration.decorators import ( on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation -from spyder.plugins.console.widgets.main_widget import ConsoleWidget +from spyder.plugins.console.widgets.main_widget import ( + ConsoleWidget, ConsoleWidgetActions) from spyder.plugins.mainmenu.api import ApplicationMenus, FileMenuSections # Localization @@ -128,7 +129,7 @@ def on_main_menu_teardown(self): widget = self.get_widget() mainmenu = self.get_plugin(Plugins.MainMenu) mainmenu.remove_item_from_application_menu( - widget.quit_action, + ConsoleWidgetActions.Quit, menu_id=ApplicationMenus.File) def update_font(self): diff --git a/spyder/plugins/findinfiles/plugin.py b/spyder/plugins/findinfiles/plugin.py index e060c28c0a9..d3ceb8aab8d 100644 --- a/spyder/plugins/findinfiles/plugin.py +++ b/spyder/plugins/findinfiles/plugin.py @@ -109,12 +109,10 @@ def on_projects_teardon_plugin_teardown(self): @on_plugin_teardown(plugin=Plugins.MainMenu) def on_main_menu_teardown(self): mainmenu = self.get_plugin(Plugins.MainMenu) - findinfiles_action = self.get_action(FindInFilesActions.FindInFiles) - menu = mainmenu.get_application_menu(ApplicationMenus.Search) mainmenu.remove_item_from_application_menu( - findinfiles_action, - menu=menu, + FindInFilesActions.FindInFiles, + menu_id=ApplicationMenus.Search, ) def on_close(self, cancelable=False): From 306d6db4059cccad61dcfa1de137434157c01eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:10 -0500 Subject: [PATCH 33/68] Migrate help to use the new teardown mechanism --- spyder/plugins/help/plugin.py | 56 ++++++++++++++++++++++++++++++++-- spyder/plugins/help/widgets.py | 3 +- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/spyder/plugins/help/plugin.py b/spyder/plugins/help/plugin.py index c2411f0e0bc..fc469eef707 100644 --- a/spyder/plugins/help/plugin.py +++ b/spyder/plugins/help/plugin.py @@ -17,7 +17,8 @@ # Local imports from spyder import __docs_url__, __forum_url__, __trouble_url__ from spyder.api.plugins import Plugins, SpyderDockablePlugin -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.config.base import get_conf_path from spyder.config.fonts import DEFAULT_SMALL_DELTA @@ -126,8 +127,8 @@ def on_shortcuts_available(self): shortcuts = self.get_plugin(Plugins.Shortcuts) # See: spyder-ide/spyder#6992 - shortcuts.sig_shortcuts_updated.connect( - lambda: self.show_intro_message()) + self._show_intro_message = lambda: self.show_intro_message() + shortcuts.sig_shortcuts_updated.connect(self._show_intro_message) if self.is_plugin_available(Plugins.MainMenu): self._setup_menus() @@ -140,6 +141,48 @@ def on_main_menu_available(self): else: self._setup_menus() + @on_plugin_teardown(plugin=Plugins.Console) + def on_console_teardown(self): + widget = self.get_widget() + internal_console = self.get_plugin(Plugins.Console) + internal_console.sig_help_requested.disconnect(self.set_object_text) + widget.set_internal_console(None) + + @on_plugin_teardown(plugin=Plugins.Editor) + def on_editor_teardown(self): + editor = self.get_plugin(Plugins.Editor) + editor.sig_help_requested.disconnect(self.set_editor_doc) + + @on_plugin_teardown(plugin=Plugins.IPythonConsole) + def on_ipython_console_teardown(self): + ipyconsole = self.get_plugin(Plugins.IPythonConsole) + + ipyconsole.sig_shellwidget_changed.disconnect(self.set_shellwidget) + ipyconsole.sig_shellwidget_created.disconnect( + self.set_shellwidget) + ipyconsole.sig_render_plain_text_requested.disconnect( + self.show_plain_text) + ipyconsole.sig_render_rich_text_requested.disconnect( + self.show_rich_text) + + ipyconsole.sig_help_requested.disconnect(self.set_object_text) + + @on_plugin_teardown(plugin=Plugins.Preferences) + def on_preferences_teardown(self): + preferences = self.get_plugin(Plugins.Preferences) + preferences.deregister_plugin_preferences(self) + + @on_plugin_teardown(plugin=Plugins.Shortcuts) + def on_shortcuts_teardown(self): + shortcuts = self.get_plugin(Plugins.Shortcuts) + self.shortcuts_available = False + shortcuts.sig_shortcuts_updated.disconnect(self._show_intro_message) + + @on_plugin_teardown(plugin=Plugins.MainMenu) + def on_main_menu_teardown(self): + self.main_menu_available = False + self._remove_menus() + def update_font(self): color_scheme = self.get_color_scheme() font = self.get_font() @@ -189,6 +232,13 @@ def _setup_menus(self): before=shortcuts_summary_action, before_section=HelpMenuSections.Support) + def _remove_menus(self): + from spyder.plugins.mainmenu.api import ApplicationMenus + mainmenu = self.get_plugin(Plugins.MainMenu) + mainmenu.remove_item_from_application_menu( + self.tutorial_action, + menu_id=ApplicationMenus.Help) + # --- Public API # ------------------------------------------------------------------------ def set_shellwidget(self, shellwidget): diff --git a/spyder/plugins/help/widgets.py b/spyder/plugins/help/widgets.py index 12bc411b5d8..c3fa1cef088 100644 --- a/spyder/plugins/help/widgets.py +++ b/spyder/plugins/help/widgets.py @@ -1143,4 +1143,5 @@ def set_internal_console(self, console): Console plugin. """ self.internal_console = console - self.internal_shell = console.get_widget().shell + if self.internal_console is not None: + self.internal_shell = console.get_widget().shell From 6bdcb1c3c458a94d9498763bc9b32c4e0cd71ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:10 -0500 Subject: [PATCH 34/68] Update call to remove_item_from_application_menu --- spyder/plugins/help/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder/plugins/help/plugin.py b/spyder/plugins/help/plugin.py index fc469eef707..71911ce0662 100644 --- a/spyder/plugins/help/plugin.py +++ b/spyder/plugins/help/plugin.py @@ -236,7 +236,7 @@ def _remove_menus(self): from spyder.plugins.mainmenu.api import ApplicationMenus mainmenu = self.get_plugin(Plugins.MainMenu) mainmenu.remove_item_from_application_menu( - self.tutorial_action, + HelpActions.ShowSpyderTutorialAction, menu_id=ApplicationMenus.Help) # --- Public API From 8f1e97cd2ab97d28f55a18338db5c18a4fd9c33b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:10 -0500 Subject: [PATCH 35/68] Apply review comments --- spyder/plugins/help/plugin.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/spyder/plugins/help/plugin.py b/spyder/plugins/help/plugin.py index 71911ce0662..8736bfc8a4c 100644 --- a/spyder/plugins/help/plugin.py +++ b/spyder/plugins/help/plugin.py @@ -175,12 +175,10 @@ def on_preferences_teardown(self): @on_plugin_teardown(plugin=Plugins.Shortcuts) def on_shortcuts_teardown(self): shortcuts = self.get_plugin(Plugins.Shortcuts) - self.shortcuts_available = False shortcuts.sig_shortcuts_updated.disconnect(self._show_intro_message) @on_plugin_teardown(plugin=Plugins.MainMenu) def on_main_menu_teardown(self): - self.main_menu_available = False self._remove_menus() def update_font(self): From b377a97f89f52c9c5ae4e5f60e67363162327e38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:10 -0500 Subject: [PATCH 36/68] Migrate history and layouts to use the new teardown mmechanism --- spyder/api/widgets/toolbars.py | 10 +++++++ spyder/plugins/history/plugin.py | 13 +++++++- spyder/plugins/layout/plugin.py | 46 ++++++++++++++++++++++++++++- spyder/plugins/toolbar/container.py | 19 ++++++++++++ spyder/plugins/toolbar/plugin.py | 24 +++++++++++++++ 5 files changed, 110 insertions(+), 2 deletions(-) diff --git a/spyder/api/widgets/toolbars.py b/spyder/api/widgets/toolbars.py index 8a87cf15727..2d5f5261b50 100644 --- a/spyder/api/widgets/toolbars.py +++ b/spyder/api/widgets/toolbars.py @@ -210,6 +210,16 @@ def add_item(self, action_or_widget: ToolbarItem, self.add_item(item, section=section, before=before, before_section=before_section) + def remove_item(self, item_id: str): + """Remove action or widget from toolbar by id.""" + item = self._item_map.pop(item_id) + for section in list(self._section_items.keys()): + section_items = self._section_items[section] + if item in section_items: + section_items.remove(item) + if len(section_items) == 0: + self._section_items.pop(section) + def _render(self): """ Create the toolbar taking into account sections and locations. diff --git a/spyder/plugins/history/plugin.py b/spyder/plugins/history/plugin.py index 40173f18661..c3b483a71aa 100644 --- a/spyder/plugins/history/plugin.py +++ b/spyder/plugins/history/plugin.py @@ -13,7 +13,8 @@ # Local imports from spyder.api.plugins import Plugins, SpyderDockablePlugin -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.plugins.history.confpage import HistoryConfigPage from spyder.plugins.history.widgets import HistoryWidget @@ -68,6 +69,16 @@ def on_console_available(self): console = self.get_plugin(Plugins.Console) console.sig_refreshed.connect(self.refresh) + @on_plugin_teardown(plugin=Plugins.Preferences) + def on_preferences_teardown(self): + preferences = self.get_plugin(Plugins.Preferences) + preferences.deregister_plugin_preferences(self) + + @on_plugin_teardown(plugin=Plugins.Console) + def on_console_teardown(self): + console = self.get_plugin(Plugins.Console) + console.sig_refreshed.disconnect(self.refresh) + def update_font(self): color_scheme = self.get_color_scheme() font = self.get_font() diff --git a/spyder/plugins/layout/plugin.py b/spyder/plugins/layout/plugin.py index d48b4d5b1e0..798cbacd091 100644 --- a/spyder/plugins/layout/plugin.py +++ b/spyder/plugins/layout/plugin.py @@ -18,7 +18,8 @@ # Local imports from spyder.api.exceptions import SpyderAPIError from spyder.api.plugins import Plugins, SpyderPluginV2 -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.api.utils import get_class_values from spyder.plugins.mainmenu.api import ApplicationMenus, ViewMenuSections @@ -146,6 +147,49 @@ def on_toolbar_available(self): before=PreferencesActions.Show ) + @on_plugin_teardown(plugin=Plugins.MainMenu) + def on_main_menu_teardown(self): + mainmenu = self.get_plugin(Plugins.MainMenu) + container = self.get_container() + # Remove Panes related actions from the View application menu + panes_items = [ + container._plugins_menu, + container._lock_interface_action, + container._close_dockwidget_action, + container._maximize_dockwidget_action] + for panes_item in panes_items: + mainmenu.remove_item_from_application_menu( + panes_item, + menu_id=ApplicationMenus.View) + # Remove layouts menu from the View application menu + layout_items = [ + container._layouts_menu, + container._toggle_next_layout_action, + container._toggle_previous_layout_action] + for layout_item in layout_items: + mainmenu.remove_item_from_application_menu( + layout_item, + menu_id=ApplicationMenus.View) + # Remove fullscreen action from the View application menu + mainmenu.remove_item_from_application_menu( + container._fullscreen_action, + menu_id=ApplicationMenus.View) + + @on_plugin_teardown(plugin=Plugins.Toolbar) + def on_toolbar_teardown(self): + container = self.get_container() + toolbars = self.get_plugin(Plugins.Toolbar) + # Remove actions from the Main application toolbar + before_action = self.get_action( + PreferencesActions.Show, + plugin=Plugins.Preferences + ) + + toolbars.remove_item_from_application_toolbar( + container._maximize_dockwidget_action, + toolbar_id=ApplicationToolbars.Main + ) + def before_mainwindow_visible(self): # Update layout menu self.update_layout_menu_actions() diff --git a/spyder/plugins/toolbar/container.py b/spyder/plugins/toolbar/container.py index 338300947a5..f165e821626 100644 --- a/spyder/plugins/toolbar/container.py +++ b/spyder/plugins/toolbar/container.py @@ -250,6 +250,25 @@ def add_item_to_application_toolbar(self, toolbar.add_item(item, section=section, before=before, before_section=before_section, omit_id=omit_id) + def remove_item_from_application_toolbar(self, item_id: str, + toolbar_id: Optional[str] = None): + """ + Remove action or widget from given application toolbar by id. + + Parameters + ---------- + item: str + The item to add to the `toolbar`. + toolbar_id: str or None + The application toolbar unique string identifier. + """ + if toolbar_id not in self._APPLICATION_TOOLBARS: + raise SpyderAPIError( + '{} is not a valid toolbar_id'.format(toolbar_id)) + + toolbar = self.get_application_toolbar(toolbar_id) + toolbar.remove_item(item_id) + def get_application_toolbar(self, toolbar_id: str) -> ApplicationToolbar: """ Return an application toolbar by toolbar_id. diff --git a/spyder/plugins/toolbar/plugin.py b/spyder/plugins/toolbar/plugin.py index 90d0e72e44e..4dbb98390d7 100644 --- a/spyder/plugins/toolbar/plugin.py +++ b/spyder/plugins/toolbar/plugin.py @@ -184,6 +184,30 @@ def add_item_to_application_toolbar(self, omit_id=omit_id ) + def remove_item_from_application_toolbar(self, item, toolbar=None, + toolbar_id=None): + """ + Remove action or widget `item` from given application menu. + + Parameters + ---------- + item: SpyderAction or QWidget + The item to add to the `toolbar`. + toolbar: ApplicationToolbar or None + Instance of a Spyder application toolbar. + toolbar_id: str or None + The application toolbar unique string identifier. + + Notes + ----- + Must provide a `toolbar` or a `toolbar_id`. + """ + self.get_container().remove_item_from_application_toolbar( + item, + toolbar=toolbar, + toolbar_id=toolbar_id + ) + def get_application_toolbar(self, toolbar_id): """ Return an application toolbar by toolbar_id. From efd40acd4d4a2c840b0e8063b45aac818ba82186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:10 -0500 Subject: [PATCH 37/68] Update calls to main menu and toolbar to use ids --- spyder/plugins/layout/plugin.py | 29 ++++++++++++----------------- spyder/plugins/toolbar/plugin.py | 19 ++++++------------- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/spyder/plugins/layout/plugin.py b/spyder/plugins/layout/plugin.py index 798cbacd091..c8045adae0b 100644 --- a/spyder/plugins/layout/plugin.py +++ b/spyder/plugins/layout/plugin.py @@ -23,7 +23,8 @@ from spyder.api.translations import get_translation from spyder.api.utils import get_class_values from spyder.plugins.mainmenu.api import ApplicationMenus, ViewMenuSections -from spyder.plugins.layout.container import LayoutContainer +from spyder.plugins.layout.container import ( + LayoutContainer, LayoutContainerActions) from spyder.plugins.layout.layouts import (DefaultLayouts, HorizontalSplitLayout, MatlabLayout, RLayout, @@ -150,43 +151,37 @@ def on_toolbar_available(self): @on_plugin_teardown(plugin=Plugins.MainMenu) def on_main_menu_teardown(self): mainmenu = self.get_plugin(Plugins.MainMenu) - container = self.get_container() # Remove Panes related actions from the View application menu panes_items = [ - container._plugins_menu, - container._lock_interface_action, - container._close_dockwidget_action, - container._maximize_dockwidget_action] + "plugins_menu", + LayoutContainerActions.LockDockwidgetsAndToolbars, + LayoutContainerActions.CloseCurrentDockwidget, + LayoutContainerActions.MaximizeCurrentDockwidget] for panes_item in panes_items: mainmenu.remove_item_from_application_menu( panes_item, menu_id=ApplicationMenus.View) # Remove layouts menu from the View application menu layout_items = [ - container._layouts_menu, - container._toggle_next_layout_action, - container._toggle_previous_layout_action] + 'layouts_menu', + LayoutContainerActions.NextLayout, + LayoutContainerActions.PreviousLayout] for layout_item in layout_items: mainmenu.remove_item_from_application_menu( layout_item, menu_id=ApplicationMenus.View) # Remove fullscreen action from the View application menu mainmenu.remove_item_from_application_menu( - container._fullscreen_action, + LayoutContainerActions.Fullscreen, menu_id=ApplicationMenus.View) @on_plugin_teardown(plugin=Plugins.Toolbar) def on_toolbar_teardown(self): - container = self.get_container() toolbars = self.get_plugin(Plugins.Toolbar) - # Remove actions from the Main application toolbar - before_action = self.get_action( - PreferencesActions.Show, - plugin=Plugins.Preferences - ) + # Remove actions from the Main application toolbar toolbars.remove_item_from_application_toolbar( - container._maximize_dockwidget_action, + LayoutContainerActions.MaximizeCurrentDockwidget, toolbar_id=ApplicationToolbars.Main ) diff --git a/spyder/plugins/toolbar/plugin.py b/spyder/plugins/toolbar/plugin.py index 4dbb98390d7..39b54f5f600 100644 --- a/spyder/plugins/toolbar/plugin.py +++ b/spyder/plugins/toolbar/plugin.py @@ -184,27 +184,20 @@ def add_item_to_application_toolbar(self, omit_id=omit_id ) - def remove_item_from_application_toolbar(self, item, toolbar=None, - toolbar_id=None): + def remove_item_from_application_toolbar(self, item_id: str, + toolbar_id: Optional[str] = None): """ - Remove action or widget `item` from given application menu. + Remove action or widget `item` from given application menu by id. Parameters ---------- - item: SpyderAction or QWidget - The item to add to the `toolbar`. - toolbar: ApplicationToolbar or None - Instance of a Spyder application toolbar. + item_id: str + The item to remove from the toolbar. toolbar_id: str or None The application toolbar unique string identifier. - - Notes - ----- - Must provide a `toolbar` or a `toolbar_id`. """ self.get_container().remove_item_from_application_toolbar( - item, - toolbar=toolbar, + item_id, toolbar_id=toolbar_id ) From 0a3305b96d8e3f0335c57aa4ede8588e9776d370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:10 -0500 Subject: [PATCH 38/68] Address review comments --- spyder/api/widgets/toolbars.py | 2 ++ spyder/plugins/layout/container.py | 9 +++++++-- spyder/plugins/layout/plugin.py | 6 +++--- spyder/plugins/toolbar/container.py | 2 +- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/spyder/api/widgets/toolbars.py b/spyder/api/widgets/toolbars.py index 2d5f5261b50..3c9912bb4c3 100644 --- a/spyder/api/widgets/toolbars.py +++ b/spyder/api/widgets/toolbars.py @@ -219,6 +219,8 @@ def remove_item(self, item_id: str): section_items.remove(item) if len(section_items) == 0: self._section_items.pop(section) + self.clear() + self._render() def _render(self): """ diff --git a/spyder/plugins/layout/container.py b/spyder/plugins/layout/container.py index e25f055788e..18b2c6f47b4 100644 --- a/spyder/plugins/layout/container.py +++ b/spyder/plugins/layout/container.py @@ -58,6 +58,11 @@ class LayoutContainerActions: LockDockwidgetsAndToolbars = 'Lock unlock panes' +class LayoutPluginMenus: + PluginsMenu = "plugins_menu" + LayoutsMenu = 'layouts_menu' + + class LayoutContainer(PluginMainContainer): """ Plugin container class that handles the Spyder quick layouts functionality. @@ -155,10 +160,10 @@ def setup(self): # Layouts menu self._layouts_menu = self.create_menu( - "layouts_menu", _("Window layouts")) + LayoutPluginMenus.LayoutsMenu, _("Window layouts")) self._plugins_menu = self.create_menu( - "plugins_menu", _("Panes")) + LayoutPluginMenus.PluginsMenu, _("Panes")) self._plugins_menu.setObjectName('checkbox-padding') def update_actions(self): diff --git a/spyder/plugins/layout/plugin.py b/spyder/plugins/layout/plugin.py index c8045adae0b..e52b96a19c7 100644 --- a/spyder/plugins/layout/plugin.py +++ b/spyder/plugins/layout/plugin.py @@ -24,7 +24,7 @@ from spyder.api.utils import get_class_values from spyder.plugins.mainmenu.api import ApplicationMenus, ViewMenuSections from spyder.plugins.layout.container import ( - LayoutContainer, LayoutContainerActions) + LayoutContainer, LayoutContainerActions, LayoutPluginMenus) from spyder.plugins.layout.layouts import (DefaultLayouts, HorizontalSplitLayout, MatlabLayout, RLayout, @@ -153,7 +153,7 @@ def on_main_menu_teardown(self): mainmenu = self.get_plugin(Plugins.MainMenu) # Remove Panes related actions from the View application menu panes_items = [ - "plugins_menu", + LayoutPluginMenus.PluginsMenu, LayoutContainerActions.LockDockwidgetsAndToolbars, LayoutContainerActions.CloseCurrentDockwidget, LayoutContainerActions.MaximizeCurrentDockwidget] @@ -163,7 +163,7 @@ def on_main_menu_teardown(self): menu_id=ApplicationMenus.View) # Remove layouts menu from the View application menu layout_items = [ - 'layouts_menu', + LayoutPluginMenus.LayoutsMenu, LayoutContainerActions.NextLayout, LayoutContainerActions.PreviousLayout] for layout_item in layout_items: diff --git a/spyder/plugins/toolbar/container.py b/spyder/plugins/toolbar/container.py index f165e821626..0c7fe84c1a7 100644 --- a/spyder/plugins/toolbar/container.py +++ b/spyder/plugins/toolbar/container.py @@ -258,7 +258,7 @@ def remove_item_from_application_toolbar(self, item_id: str, Parameters ---------- item: str - The item to add to the `toolbar`. + The item to remove from the `toolbar`. toolbar_id: str or None The application toolbar unique string identifier. """ From cd999483541e266d66b4254f54c5b53058bc8e4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:10 -0500 Subject: [PATCH 39/68] Migrate Outline explorer, plots, preferences and profiler to use the new teardown mechanism --- spyder/plugins/maininterpreter/plugin.py | 16 +++++++++- spyder/plugins/outlineexplorer/plugin.py | 12 +++++++- spyder/plugins/plots/plugin.py | 9 ++++-- spyder/plugins/preferences/plugin.py | 38 ++++++++++++++++++++++-- spyder/plugins/profiler/plugin.py | 22 +++++++++++++- 5 files changed, 88 insertions(+), 9 deletions(-) diff --git a/spyder/plugins/maininterpreter/plugin.py b/spyder/plugins/maininterpreter/plugin.py index 8fb85779c3b..57250bf52bf 100644 --- a/spyder/plugins/maininterpreter/plugin.py +++ b/spyder/plugins/maininterpreter/plugin.py @@ -17,7 +17,8 @@ # Local imports from spyder.api.plugins import Plugins, SpyderPluginV2 -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.plugins.maininterpreter.confpage import MainInterpreterConfigPage from spyder.plugins.maininterpreter.container import MainInterpreterContainer @@ -90,6 +91,19 @@ def on_statusbar_available(self): if statusbar: statusbar.add_status_widget(self.interpreter_status) + @on_plugin_teardown(plugin=Plugins.Preferences) + def on_preferences_teardown(self): + # Deregister conf page + preferences = self.get_plugin(Plugins.Preferences) + preferences.deregister_plugin_preferences(self) + + @on_plugin_teardown(plugin=Plugins.StatusBar) + def on_statusbar_teardown(self): + # Add status widget + statusbar = self.get_plugin(Plugins.StatusBar) + if statusbar: + statusbar.remove_status_widget(self.interpreter_status.ID) + # ---- Public API def get_interpreter(self): """Get current interpreter.""" diff --git a/spyder/plugins/outlineexplorer/plugin.py b/spyder/plugins/outlineexplorer/plugin.py index a4fb71b3f2b..d6acb0c1e6e 100644 --- a/spyder/plugins/outlineexplorer/plugin.py +++ b/spyder/plugins/outlineexplorer/plugin.py @@ -10,7 +10,8 @@ from qtpy.QtCore import Slot # Local imports -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.api.plugins import SpyderDockablePlugin, Plugins from spyder.plugins.outlineexplorer.main_widget import OutlineExplorerWidget @@ -63,6 +64,15 @@ def on_editor_available(self): editor.sig_open_files_finished.connect( self.update_all_editors) + @on_plugin_teardown(plugin=Plugins.Completions) + def on_completions_teardown(self): + completions = self.get_plugin(Plugins.Completions) + + completions.sig_language_completions_available.disconnect( + self.start_symbol_services) + completions.sig_stop_completions.disconnect( + self.stop_symbol_services) + #------ Public API --------------------------------------------------------- def restore_scrollbar_position(self): """Restoring scrollbar position after main window is visible""" diff --git a/spyder/plugins/plots/plugin.py b/spyder/plugins/plots/plugin.py index 959f9724664..96b070d4222 100644 --- a/spyder/plugins/plots/plugin.py +++ b/spyder/plugins/plots/plugin.py @@ -13,7 +13,8 @@ # Local imports from spyder.api.plugins import Plugins, SpyderDockablePlugin -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.plugins.plots.widgets.main_widget import PlotsWidget @@ -62,14 +63,16 @@ def on_ipython_console_available(self): ipyconsole.sig_shellwidget_deleted.connect( self.remove_shellwidget) - def unregister(self): + @on_plugin_teardown(plugin=Plugins.IPythonConsole) + def on_ipython_console_teardown(self): # Plugins ipyconsole = self.get_plugin(Plugins.IPythonConsole) # Signals + ipyconsole.sig_shellwidget_changed.disconnect(self.set_shellwidget) ipyconsole.sig_shellwidget_created.disconnect( self.add_shellwidget) - ipyconsole.sig_shellwidget_deleted.connect( + ipyconsole.sig_shellwidget_deleted.disconnect( self.remove_shellwidget) # ---- Public API diff --git a/spyder/plugins/preferences/plugin.py b/spyder/plugins/preferences/plugin.py index b755a4f0e94..7122b74df8c 100644 --- a/spyder/plugins/preferences/plugin.py +++ b/spyder/plugins/preferences/plugin.py @@ -25,12 +25,14 @@ # Local imports from spyder.api.plugins import Plugins, SpyderPluginV2, SpyderPlugin -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.config.base import _ from spyder.config.main import CONF_VERSION from spyder.config.user import NoDefault from spyder.plugins.mainmenu.api import ApplicationMenus, ToolsMenuSections -from spyder.plugins.preferences.widgets.container import PreferencesContainer +from spyder.plugins.preferences.widgets.container import ( + PreferencesActions, PreferencesContainer) from spyder.plugins.toolbar.api import ApplicationToolbars, MainToolbarSections logger = logging.getLogger(__name__) @@ -303,6 +305,36 @@ def on_application_available(self): container = self.get_container() container.sig_reset_preferences_requested.connect(self.reset) + + @on_plugin_teardown(plugin=Plugins.MainMenu) + def on_main_menu_teardown(self): + container = self.get_container() + main_menu = self.get_plugin(Plugins.MainMenu) + + main_menu.remove_item_from_application_menu( + PreferencesActions.Show, + menu_id=ApplicationMenus.Tools, + ) + + main_menu.remove_item_from_application_menu( + PreferencesActions.Reset, + menu_id=ApplicationMenus.Tools, + ) + + @on_plugin_teardown(plugin=Plugins.Toolbar) + def on_toolbar_teardown(self): + container = self.get_container() + toolbar = self.get_plugin(Plugins.Toolbar) + toolbar.remove_item_from_application_toolbar( + PreferencesActions.Show, + toolbar_id=ApplicationToolbars.Main + ) + + @on_plugin_teardown(plugin=Plugins.Application) + def on_application_teardown(self): + container = self.get_container() + container.sig_reset_preferences_requested.disconnect(self.reset) + @Slot() def reset(self): answer = QMessageBox.warning(self.main, _("Warning"), @@ -314,6 +346,6 @@ def reset(self): application = self.get_plugin(Plugins.Application) application.sig_restart_requested.emit() - def on_close(self, cancelable=False) -> bool: + def can_close(self) -> bool: container = self.get_container() return not container.is_dialog_open() diff --git a/spyder/plugins/profiler/plugin.py b/spyder/plugins/profiler/plugin.py index 884328032be..4ee998ea58e 100644 --- a/spyder/plugins/profiler/plugin.py +++ b/spyder/plugins/profiler/plugin.py @@ -16,7 +16,8 @@ # Local imports from spyder.api.plugins import Plugins, SpyderDockablePlugin -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.plugins.mainmenu.api import ApplicationMenus from spyder.plugins.profiler.confpage import ProfilerConfigPage @@ -105,6 +106,25 @@ def on_main_menu_available(self): mainmenu.add_item_to_application_menu( run_action, menu_id=ApplicationMenus.Run) + @on_plugin_teardown(plugin=Plugins.Editor) + def on_editor_teardown(self): + widget = self.get_widget() + editor = self.get_plugin(Plugins.Editor) + widget.sig_edit_goto_requested.disconnect(editor.load) + + @on_plugin_teardown(plugin=Plugins.Preferences) + def on_preferences_teardown(self): + preferences = self.get_plugin(Plugins.Preferences) + preferences.deregister_plugin_preferences(self) + + @on_plugin_teardown(plugin=Plugins.MainMenu) + def on_main_menu_teardown(self): + mainmenu = self.get_plugin(Plugins.MainMenu) + run_action = self.get_action(ProfilerActions.ProfileCurrentFile) + + run_menu = mainmenu.get_application_menu(ApplicationMenus.Run) + mainmenu.remove_item_from_application_menu(run_action, menu=run_menu) + # --- Public API # ------------------------------------------------------------------------ def run_profiler(self): From 6dcaa6b0b431adf675fe71a087249ef7238bc8f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:11 -0500 Subject: [PATCH 40/68] Update profiler calls to remove_item_from_application_menu --- spyder/plugins/profiler/plugin.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spyder/plugins/profiler/plugin.py b/spyder/plugins/profiler/plugin.py index 4ee998ea58e..8281ccb15f7 100644 --- a/spyder/plugins/profiler/plugin.py +++ b/spyder/plugins/profiler/plugin.py @@ -120,10 +120,9 @@ def on_preferences_teardown(self): @on_plugin_teardown(plugin=Plugins.MainMenu) def on_main_menu_teardown(self): mainmenu = self.get_plugin(Plugins.MainMenu) - run_action = self.get_action(ProfilerActions.ProfileCurrentFile) - run_menu = mainmenu.get_application_menu(ApplicationMenus.Run) - mainmenu.remove_item_from_application_menu(run_action, menu=run_menu) + mainmenu.remove_item_from_application_menu( + ProfilerActions.ProfileCurrentFile, menu_id=ApplicationMenus.Run) # --- Public API # ------------------------------------------------------------------------ From dd784a8d449cba9d554fd3d799e2539ae963eb40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:11 -0500 Subject: [PATCH 41/68] Address review comments --- spyder/plugins/maininterpreter/plugin.py | 6 ++---- spyder/plugins/outlineexplorer/plugin.py | 7 +++++++ spyder/plugins/profiler/plugin.py | 4 +++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/spyder/plugins/maininterpreter/plugin.py b/spyder/plugins/maininterpreter/plugin.py index 57250bf52bf..c43748b630d 100644 --- a/spyder/plugins/maininterpreter/plugin.py +++ b/spyder/plugins/maininterpreter/plugin.py @@ -88,8 +88,7 @@ def on_preferences_available(self): def on_statusbar_available(self): # Add status widget statusbar = self.get_plugin(Plugins.StatusBar) - if statusbar: - statusbar.add_status_widget(self.interpreter_status) + statusbar.add_status_widget(self.interpreter_status) @on_plugin_teardown(plugin=Plugins.Preferences) def on_preferences_teardown(self): @@ -101,8 +100,7 @@ def on_preferences_teardown(self): def on_statusbar_teardown(self): # Add status widget statusbar = self.get_plugin(Plugins.StatusBar) - if statusbar: - statusbar.remove_status_widget(self.interpreter_status.ID) + statusbar.remove_status_widget(self.interpreter_status.ID) # ---- Public API def get_interpreter(self): diff --git a/spyder/plugins/outlineexplorer/plugin.py b/spyder/plugins/outlineexplorer/plugin.py index d6acb0c1e6e..2b159d6cad1 100644 --- a/spyder/plugins/outlineexplorer/plugin.py +++ b/spyder/plugins/outlineexplorer/plugin.py @@ -73,6 +73,13 @@ def on_completions_teardown(self): completions.sig_stop_completions.disconnect( self.stop_symbol_services) + @on_plugin_teardown(plugin=Plugins.Editor) + def on_editor_teardown(self): + editor = self.get_plugin(Plugins.Editor) + + editor.sig_open_files_finished.disconnect( + self.update_all_editors) + #------ Public API --------------------------------------------------------- def restore_scrollbar_position(self): """Restoring scrollbar position after main window is visible""" diff --git a/spyder/plugins/profiler/plugin.py b/spyder/plugins/profiler/plugin.py index 8281ccb15f7..8bcd5fab05a 100644 --- a/spyder/plugins/profiler/plugin.py +++ b/spyder/plugins/profiler/plugin.py @@ -122,7 +122,9 @@ def on_main_menu_teardown(self): mainmenu = self.get_plugin(Plugins.MainMenu) mainmenu.remove_item_from_application_menu( - ProfilerActions.ProfileCurrentFile, menu_id=ApplicationMenus.Run) + ProfilerActions.ProfileCurrentFile, + menu_id=ApplicationMenus.Run + ) # --- Public API # ------------------------------------------------------------------------ From a426c07b73fb8b3de5693945d0fe06d364f5a460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:11 -0500 Subject: [PATCH 42/68] Migrate projects to use the new teardown mechanism --- spyder/plugins/projects/plugin.py | 150 +++++++++++++++++++++++++----- 1 file changed, 127 insertions(+), 23 deletions(-) diff --git a/spyder/plugins/projects/plugin.py b/spyder/plugins/projects/plugin.py index 8287ddd70e4..812c6de33ff 100644 --- a/spyder/plugins/projects/plugin.py +++ b/spyder/plugins/projects/plugin.py @@ -26,7 +26,8 @@ # Local imports from spyder.api.exceptions import SpyderAPIError -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.api.plugins import Plugins, SpyderDockablePlugin from spyder.config.base import (get_home_dir, get_project_config_folder, @@ -188,18 +189,22 @@ def on_editor_available(self): treewidget.sig_renamed.connect(self.editor.renamed) treewidget.sig_tree_renamed.connect(self.editor.renamed_tree) treewidget.sig_module_created.connect(self.editor.new) - treewidget.sig_file_created.connect( - lambda t: self.editor.new(text=t)) - self.sig_project_loaded.connect( - lambda v: self.editor.setup_open_files()) - self.sig_project_closed[bool].connect( - lambda v: self.editor.setup_open_files()) + self._editor_new = lambda t: self.editor.new(text=t) + treewidget.sig_file_created.connect(self._editor_new) + + self._editor_setup_files = lambda v: self.editor.setup_open_files() + self.sig_project_loaded.connect(self._editor_setup_files) + self.sig_project_closed[bool].connect(self._editor_setup_files) + self.editor.set_projects(self) - self.sig_project_loaded.connect( + + self._editor_path = ( lambda v: self.editor.set_current_project_path(v)) - self.sig_project_closed.connect( + self._editor_unset_path = ( lambda v: self.editor.set_current_project_path()) + self.sig_project_loaded.connect(self._editor_path) + self.sig_project_closed.connect(self._editor_unset_path) @on_plugin_available(plugin=Plugins.Completions) def on_completions_available(self): @@ -212,14 +217,19 @@ def on_completions_available(self): # self.start_workspace_services()) self.completions.sig_stop_completions.connect( self.stop_workspace_services) - self.sig_project_loaded.connect( - functools.partial(self.completions.project_path_update, - update_kind=WorkspaceUpdateKind.ADDITION, - instance=self)) - self.sig_project_closed.connect( - functools.partial(self.completions.project_path_update, - update_kind=WorkspaceUpdateKind.DELETION, - instance=self)) + + self._addition_path_update = functools.partial( + self.completions.project_path_update, + update_kind=WorkspaceUpdateKind.ADDITION, + instance=self) + + self._deletion_path_update = functools.partial( + self.completions.project_path_update, + update_kind=WorkspaceUpdateKind.DELETION, + instance=self) + + self.sig_project_loaded.connect(self._addition_path_update) + self.sig_project_closed.connect(self._deletion_path_update) @on_plugin_available(plugin=Plugins.IPythonConsole) def on_ipython_console_available(self): @@ -227,15 +237,25 @@ def on_ipython_console_available(self): widget = self.get_widget() treewidget = widget.treewidget - treewidget.sig_open_interpreter_requested.connect( - self.ipyconsole.create_client_from_path) - treewidget.sig_run_requested.connect( + self._ipython_run_script = ( lambda fname: - self.ipyconsole.run_script( - fname, osp.dirname(fname), '', False, False, False, True, - False) + self.ipyconsole.run_script( + fname, osp.dirname(fname), '', False, False, False, True, + False + ) ) + treewidget.sig_open_interpreter_requested.connect( + self.ipyconsole.create_client_from_path) + treewidget.sig_run_requested.connect(self._ipython_run_script) + + @on_plugin_available(plugin=Plugins.OutlineExplorer) + def on_outline_explorer_available(self): + outline_explorer = self.get_plugin(Plugins.OutlineExplorer) + self._outline_update = lambda v: outline_explorer.update_all_editors() + self.sig_project_loaded.connect(self._outline_update) + self.sig_project_closed.connect(self._outline_update) + @on_plugin_available(plugin=Plugins.MainMenu) def on_main_menu_available(self): main_menu = self.get_plugin(Plugins.MainMenu) @@ -263,6 +283,90 @@ def on_main_menu_available(self): menu_id=ApplicationMenus.Projects, section=ProjectsMenuSections.Extras) + @on_plugin_teardown(plugin=Plugins.Editor) + def on_editor_teardown(self): + self.editor = self.get_plugin(Plugins.Editor) + widget = self.get_widget() + treewidget = widget.treewidget + + treewidget.sig_open_file_requested.disconnect(self.editor.load) + treewidget.sig_removed.disconnect(self.editor.removed) + treewidget.sig_tree_removed.disconnect(self.editor.removed_tree) + treewidget.sig_renamed.disconnect(self.editor.renamed) + treewidget.sig_tree_renamed.disconnect(self.editor.renamed_tree) + treewidget.sig_module_created.disconnect(self.editor.new) + treewidget.sig_file_created.disconnect(self._editor_new) + + self.sig_project_loaded.disconnect(self._editor_setup_files) + self.sig_project_closed[bool].disconnect(self._editor_setup_files) + self.editor.set_projects(None) + self.sig_project_loaded.disconnect(self._editor_path) + self.sig_project_closed.disconnect(self._editor_unset_path) + + self._editor_new = None + self._editor_setup_files = None + self._editor_path = None + self._editor_unset_path = None + self.editor = None + + @on_plugin_teardown(plugin=Plugins.Completions) + def on_completions_teardown(self): + self.completions = self.get_plugin(Plugins.Completions) + + self.completions.sig_stop_completions.disconnect( + self.stop_workspace_services) + + self.sig_project_loaded.disconnect(self._addition_path_update) + self.sig_project_closed.disconnect(self._deletion_path_update) + + self._addition_path_update = None + self._deletion_path_update = None + self.completions = None + + @on_plugin_teardown(plugin=Plugins.IPythonConsole) + def on_ipython_console_teardown(self): + self.ipyconsole = self.get_plugin(Plugins.IPythonConsole) + widget = self.get_widget() + treewidget = widget.treewidget + + treewidget.sig_open_interpreter_requested.disconnect( + self.ipyconsole.create_client_from_path) + treewidget.sig_run_requested.disconnect(self._ipython_run_script) + + self._ipython_run_script = None + self.ipyconsole = None + + @on_plugin_teardown(plugin=Plugins.OutlineExplorer) + def on_outline_explorer_teardown(self): + outline_explorer = self.get_plugin(Plugins.OutlineExplorer) + self.sig_project_loaded.disconnect(self._outline_update) + self.sig_project_closed.disconnect(self._outline_update) + self._outline_update = None + + @on_plugin_teardown(plugin=Plugins.MainMenu) + def on_main_menu_teardown(self): + main_menu = self.get_plugin(Plugins.MainMenu) + new_project_action = self.get_action(ProjectsActions.NewProject) + open_project_action = self.get_action(ProjectsActions.OpenProject) + + projects_menu = main_menu.get_application_menu( + ApplicationMenus.Projects) + projects_menu.aboutToShow.disconnect(self.is_invalid_active_project) + + main_menu.remove_item_from_application_menu( + new_project_action, + menu=projects_menu) + + for item in [open_project_action, self.close_project_action, + self.delete_project_action]: + main_menu.remove_item_from_application_menu( + item, + menu=projects_menu) + + main_menu.remove_item_from_application_menu( + self.recent_project_menu, + menu=projects_menu) + def setup(self): """Setup the plugin actions.""" self.create_action( From 34c0933db9717af760031a1f2433d09cec193794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:11 -0500 Subject: [PATCH 43/68] Update calls to remove_item_from_application_menu --- spyder/plugins/projects/plugin.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/spyder/plugins/projects/plugin.py b/spyder/plugins/projects/plugin.py index 812c6de33ff..f15d6c0202b 100644 --- a/spyder/plugins/projects/plugin.py +++ b/spyder/plugins/projects/plugin.py @@ -354,18 +354,19 @@ def on_main_menu_teardown(self): projects_menu.aboutToShow.disconnect(self.is_invalid_active_project) main_menu.remove_item_from_application_menu( - new_project_action, - menu=projects_menu) + ProjectsActions.NewProject, + menu_id=ApplicationMenus.Projects) - for item in [open_project_action, self.close_project_action, - self.delete_project_action]: + for item in [ProjectsActions.OpenProject, + ProjectsActions.CloseProject, + ProjectsActions.DeleteProject]: main_menu.remove_item_from_application_menu( item, - menu=projects_menu) + menu_id=ApplicationMenus.Projects) main_menu.remove_item_from_application_menu( - self.recent_project_menu, - menu=projects_menu) + ProjectsMenuSubmenus.RecentProjects, + menu_id=ApplicationMenus.Projects) def setup(self): """Setup the plugin actions.""" From 529294932a8305d2eae854456c1d06b38ea60c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:11 -0500 Subject: [PATCH 44/68] Address review comments --- spyder/plugins/projects/plugin.py | 136 +++++++++++------------------- 1 file changed, 50 insertions(+), 86 deletions(-) diff --git a/spyder/plugins/projects/plugin.py b/spyder/plugins/projects/plugin.py index f15d6c0202b..594ca540b2a 100644 --- a/spyder/plugins/projects/plugin.py +++ b/spyder/plugins/projects/plugin.py @@ -189,22 +189,14 @@ def on_editor_available(self): treewidget.sig_renamed.connect(self.editor.renamed) treewidget.sig_tree_renamed.connect(self.editor.renamed_tree) treewidget.sig_module_created.connect(self.editor.new) + treewidget.sig_file_created.connect(self._new_editor) - self._editor_new = lambda t: self.editor.new(text=t) - treewidget.sig_file_created.connect(self._editor_new) - - self._editor_setup_files = lambda v: self.editor.setup_open_files() - self.sig_project_loaded.connect(self._editor_setup_files) - self.sig_project_closed[bool].connect(self._editor_setup_files) + self.sig_project_loaded.connect(self._editor_open_files) + self.sig_project_closed[bool].connect(self._editor_open_files) self.editor.set_projects(self) - - self._editor_path = ( - lambda v: self.editor.set_current_project_path(v)) - self._editor_unset_path = ( - lambda v: self.editor.set_current_project_path()) - self.sig_project_loaded.connect(self._editor_path) - self.sig_project_closed.connect(self._editor_unset_path) + self.sig_project_loaded.connect(self._set_path_in_editor) + self.sig_project_closed.connect(self._unset_path_in_editor) @on_plugin_available(plugin=Plugins.Completions) def on_completions_available(self): @@ -215,46 +207,17 @@ def on_completions_available(self): # completions.sig_language_completions_available.connect( # lambda settings, language: # self.start_workspace_services()) - self.completions.sig_stop_completions.connect( - self.stop_workspace_services) - - self._addition_path_update = functools.partial( - self.completions.project_path_update, - update_kind=WorkspaceUpdateKind.ADDITION, - instance=self) - - self._deletion_path_update = functools.partial( - self.completions.project_path_update, - update_kind=WorkspaceUpdateKind.DELETION, - instance=self) - - self.sig_project_loaded.connect(self._addition_path_update) - self.sig_project_closed.connect(self._deletion_path_update) + self.sig_project_loaded.connect(self._add_path_to_completions) + self.sig_project_closed.connect(self._remove_path_from_completions) @on_plugin_available(plugin=Plugins.IPythonConsole) def on_ipython_console_available(self): self.ipyconsole = self.get_plugin(Plugins.IPythonConsole) widget = self.get_widget() treewidget = widget.treewidget - - self._ipython_run_script = ( - lambda fname: - self.ipyconsole.run_script( - fname, osp.dirname(fname), '', False, False, False, True, - False - ) - ) - treewidget.sig_open_interpreter_requested.connect( self.ipyconsole.create_client_from_path) - treewidget.sig_run_requested.connect(self._ipython_run_script) - - @on_plugin_available(plugin=Plugins.OutlineExplorer) - def on_outline_explorer_available(self): - outline_explorer = self.get_plugin(Plugins.OutlineExplorer) - self._outline_update = lambda v: outline_explorer.update_all_editors() - self.sig_project_loaded.connect(self._outline_update) - self.sig_project_closed.connect(self._outline_update) + treewidget.sig_run_requested.connect(self._run_file_in_ipyconsole) @on_plugin_available(plugin=Plugins.MainMenu) def on_main_menu_available(self): @@ -297,16 +260,12 @@ def on_editor_teardown(self): treewidget.sig_module_created.disconnect(self.editor.new) treewidget.sig_file_created.disconnect(self._editor_new) - self.sig_project_loaded.disconnect(self._editor_setup_files) - self.sig_project_closed[bool].disconnect(self._editor_setup_files) + self.sig_project_loaded.disconnect(self._editor_open_files) + self.sig_project_closed[bool].disconnect(self._editor_open_files) self.editor.set_projects(None) - self.sig_project_loaded.disconnect(self._editor_path) - self.sig_project_closed.disconnect(self._editor_unset_path) + self.sig_project_loaded.disconnect(self._set_path_in_editor) + self.sig_project_closed.disconnect(self._unset_path_in_editor) - self._editor_new = None - self._editor_setup_files = None - self._editor_path = None - self._editor_unset_path = None self.editor = None @on_plugin_teardown(plugin=Plugins.Completions) @@ -316,11 +275,9 @@ def on_completions_teardown(self): self.completions.sig_stop_completions.disconnect( self.stop_workspace_services) - self.sig_project_loaded.disconnect(self._addition_path_update) - self.sig_project_closed.disconnect(self._deletion_path_update) + self.sig_project_loaded.disconnect(self._add_path_to_completions) + self.sig_project_closed.disconnect(self._remove_path_from_completions) - self._addition_path_update = None - self._deletion_path_update = None self.completions = None @on_plugin_teardown(plugin=Plugins.IPythonConsole) @@ -331,42 +288,15 @@ def on_ipython_console_teardown(self): treewidget.sig_open_interpreter_requested.disconnect( self.ipyconsole.create_client_from_path) - treewidget.sig_run_requested.disconnect(self._ipython_run_script) + treewidget.sig_run_requested.disconnect(self._run_file_in_ipyconsole) self._ipython_run_script = None self.ipyconsole = None - @on_plugin_teardown(plugin=Plugins.OutlineExplorer) - def on_outline_explorer_teardown(self): - outline_explorer = self.get_plugin(Plugins.OutlineExplorer) - self.sig_project_loaded.disconnect(self._outline_update) - self.sig_project_closed.disconnect(self._outline_update) - self._outline_update = None - @on_plugin_teardown(plugin=Plugins.MainMenu) def on_main_menu_teardown(self): main_menu = self.get_plugin(Plugins.MainMenu) - new_project_action = self.get_action(ProjectsActions.NewProject) - open_project_action = self.get_action(ProjectsActions.OpenProject) - - projects_menu = main_menu.get_application_menu( - ApplicationMenus.Projects) - projects_menu.aboutToShow.disconnect(self.is_invalid_active_project) - - main_menu.remove_item_from_application_menu( - ProjectsActions.NewProject, - menu_id=ApplicationMenus.Projects) - - for item in [ProjectsActions.OpenProject, - ProjectsActions.CloseProject, - ProjectsActions.DeleteProject]: - main_menu.remove_item_from_application_menu( - item, - menu_id=ApplicationMenus.Projects) - - main_menu.remove_item_from_application_menu( - ProjectsMenuSubmenus.RecentProjects, - menu_id=ApplicationMenus.Projects) + main_menu.remove_application_menu(ApplicationMenus.Projects) def setup(self): """Setup the plugin actions.""" @@ -1009,3 +939,37 @@ def get_project_types(self): are project type classes. """ return self._project_types + + # --- Private API + # ------------------------------------------------------------------------- + def _new_editor(self, text): + self.editor.new(text=text) + + def _editor_open_files(self, _unused): + self.editor.setup_open_files() + + def _set_path_in_editor(self, path): + self.editor.set_current_project_path(path) + + def _unset_path_in_editor(self, __unused): + self.editor.set_current_project_path() + + def _add_path_to_completions(self, path): + self.completions.project_path_update( + path, + update_kind=WorkspaceUpdateKind.ADDITION, + instance=self + ) + + def _remove_path_from_completions(self, path): + self.completions.project_path_update( + path, + update_kind=WorkspaceUpdateKind.DELETION, + instance=self + ) + + def _run_file_in_ipyconsole(self, fname): + self.ipyconsole.run_script( + fname, osp.dirname(fname), '', False, False, False, True, + False + ) From ea4824f1e028d002266f2bca8ad7856f4304962f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:11 -0500 Subject: [PATCH 45/68] Address further review comments --- spyder/plugins/projects/plugin.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spyder/plugins/projects/plugin.py b/spyder/plugins/projects/plugin.py index 594ca540b2a..68946133f37 100644 --- a/spyder/plugins/projects/plugin.py +++ b/spyder/plugins/projects/plugin.py @@ -191,8 +191,8 @@ def on_editor_available(self): treewidget.sig_module_created.connect(self.editor.new) treewidget.sig_file_created.connect(self._new_editor) - self.sig_project_loaded.connect(self._editor_open_files) - self.sig_project_closed[bool].connect(self._editor_open_files) + self.sig_project_loaded.connect(self._setup_editor_files) + self.sig_project_closed[bool].connect(self._setup_editor_files) self.editor.set_projects(self) self.sig_project_loaded.connect(self._set_path_in_editor) @@ -260,8 +260,8 @@ def on_editor_teardown(self): treewidget.sig_module_created.disconnect(self.editor.new) treewidget.sig_file_created.disconnect(self._editor_new) - self.sig_project_loaded.disconnect(self._editor_open_files) - self.sig_project_closed[bool].disconnect(self._editor_open_files) + self.sig_project_loaded.disconnect(self._setup_editor_files) + self.sig_project_closed[bool].disconnect(self._setup_editor_files) self.editor.set_projects(None) self.sig_project_loaded.disconnect(self._set_path_in_editor) self.sig_project_closed.disconnect(self._unset_path_in_editor) @@ -945,7 +945,7 @@ def get_project_types(self): def _new_editor(self, text): self.editor.new(text=text) - def _editor_open_files(self, _unused): + def _setup_editor_files(self, __unused): self.editor.setup_open_files() def _set_path_in_editor(self, path): From bab393e3813476943d64b90304b4251d8ae72712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:11 -0500 Subject: [PATCH 46/68] Minor error correction --- spyder/plugins/projects/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder/plugins/projects/plugin.py b/spyder/plugins/projects/plugin.py index 68946133f37..3d5bf126bdd 100644 --- a/spyder/plugins/projects/plugin.py +++ b/spyder/plugins/projects/plugin.py @@ -258,7 +258,7 @@ def on_editor_teardown(self): treewidget.sig_renamed.disconnect(self.editor.renamed) treewidget.sig_tree_renamed.disconnect(self.editor.renamed_tree) treewidget.sig_module_created.disconnect(self.editor.new) - treewidget.sig_file_created.disconnect(self._editor_new) + treewidget.sig_file_created.disconnect(self._new_editor) self.sig_project_loaded.disconnect(self._setup_editor_files) self.sig_project_closed[bool].disconnect(self._setup_editor_files) From b968e570333f88387f981508c4d38a6cbcec2e70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:12 -0500 Subject: [PATCH 47/68] Restore sig_stop_completions connection --- spyder/plugins/projects/plugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spyder/plugins/projects/plugin.py b/spyder/plugins/projects/plugin.py index 3d5bf126bdd..56a923fdf2e 100644 --- a/spyder/plugins/projects/plugin.py +++ b/spyder/plugins/projects/plugin.py @@ -207,6 +207,8 @@ def on_completions_available(self): # completions.sig_language_completions_available.connect( # lambda settings, language: # self.start_workspace_services()) + self.completions.sig_stop_completions.connect( + self.stop_workspace_services) self.sig_project_loaded.connect(self._add_path_to_completions) self.sig_project_closed.connect(self._remove_path_from_completions) From ce5931c360da6fbd8ce4eec47768f463f734e2cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:12 -0500 Subject: [PATCH 48/68] Migrate pylint, run and shortcuts to use the new teardown mechanism --- spyder/plugins/pylint/plugin.py | 52 +++++++++++++++++++++++++++--- spyder/plugins/run/plugin.py | 8 ++++- spyder/plugins/shortcuts/plugin.py | 21 +++++++++++- 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/spyder/plugins/pylint/plugin.py b/spyder/plugins/pylint/plugin.py index 752f623d285..a1f2563cf26 100644 --- a/spyder/plugins/pylint/plugin.py +++ b/spyder/plugins/pylint/plugin.py @@ -16,7 +16,8 @@ # Local imports from spyder.api.plugins import Plugins, SpyderDockablePlugin -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.utils.programs import is_module_installed from spyder.plugins.mainmenu.api import ApplicationMenus @@ -116,10 +117,13 @@ def on_projects_available(self): # Connect to projects projects = self.get_plugin(Plugins.Projects) - projects.sig_project_loaded.connect( + self._set_project_dir = ( lambda value: widget.set_conf("project_dir", value)) - projects.sig_project_closed.connect( - lambda value: widget.set_conf("project_dir", None)) + self._unset_project_dir = ( + lambda value: widget.set_conf("project_dir", value)) + + projects.sig_project_loaded.connect(self._set_project_dir) + projects.sig_project_closed.connect(self._unset_project_dir) @on_plugin_available(plugin=Plugins.MainMenu) def on_main_menu_available(self): @@ -129,6 +133,46 @@ def on_main_menu_available(self): mainmenu.add_item_to_application_menu( pylint_act, menu_id=ApplicationMenus.Source) + @on_plugin_teardown(plugin=Plugins.Editor) + def on_editor_teardown(self): + widget = self.get_widget() + editor = self.get_plugin(Plugins.Editor) + + # Connect to Editor + widget.sig_edit_goto_requested.disconnect(editor.load) + editor.sig_editor_focus_changed.disconnect(self._set_filename) + + pylint_act = self.get_action(PylintActions.AnalyzeCurrentFile) + + # TODO: use new API when editor has migrated + editor.pythonfile_dependent_actions.remove(pylint_act) + + @on_plugin_teardown(plugin=Plugins.Preferences) + def on_preferences_teardown(self): + preferences = self.get_plugin(Plugins.Preferences) + preferences.deregister_plugin_preferences(self) + + @on_plugin_teardown(plugin=Plugins.Projects) + def on_projects_teardown(self): + widget = self.get_widget() + + # Connect to projects + projects = self.get_plugin(Plugins.Projects) + projects.sig_project_loaded.disconnect(self._set_project_dir) + projects.sig_project_closed.disconnect(self._unset_project_dir) + + self._set_project_dir = None + self._unset_project_dir = None + + @on_plugin_teardown(plugin=Plugins.MainMenu) + def on_main_menu_teardown(self): + mainmenu = self.get_plugin(Plugins.MainMenu) + + pylint_act = self.get_action(PylintActions.AnalyzeCurrentFile) + source_menu = mainmenu.get_application_menu( + ApplicationMenus.Source) + mainmenu.remove_item_from_application_menu(pylint_act, menu=source_menu) + # --- Private API # ------------------------------------------------------------------------ @Slot() diff --git a/spyder/plugins/run/plugin.py b/spyder/plugins/run/plugin.py index 34b1a6c4a61..3d0c0e42d30 100644 --- a/spyder/plugins/run/plugin.py +++ b/spyder/plugins/run/plugin.py @@ -12,7 +12,8 @@ # Local imports from spyder.api.plugins import Plugins, SpyderPluginV2 -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.plugins.run.confpage import RunConfigPage @@ -54,5 +55,10 @@ def on_preferences_available(self): preferences = self.get_plugin(Plugins.Preferences) preferences.register_plugin_preferences(self) + @on_plugin_teardown(plugin=Plugins.Preferences) + def on_preferences_teardown(self): + preferences = self.get_plugin(Plugins.Preferences) + preferences.deregister_plugin_preferences(self) + # --- Public API # ------------------------------------------------------------------------ diff --git a/spyder/plugins/shortcuts/plugin.py b/spyder/plugins/shortcuts/plugin.py index 72291f19873..f7e37a90833 100644 --- a/spyder/plugins/shortcuts/plugin.py +++ b/spyder/plugins/shortcuts/plugin.py @@ -21,7 +21,8 @@ # Local imports from spyder.api.plugins import Plugins, SpyderPluginV2 -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.plugins.mainmenu.api import ApplicationMenus, HelpMenuSections from spyder.plugins.shortcuts.confpage import ShortcutsConfigPage @@ -97,6 +98,24 @@ def on_main_menu_available(self): section=HelpMenuSections.Documentation, ) + @on_plugin_teardown(plugin=Plugins.Preferences) + def on_preferences_teardown(self): + preferences = self.get_plugin(Plugins.Preferences) + preferences.deregister_plugin_preferences(self) + + @on_plugin_teardown(plugin=Plugins.MainMenu) + def on_main_menu_teardown(self): + mainmenu = self.get_plugin(Plugins.MainMenu) + shortcuts_action = self.get_action( + ShortcutActions.ShortcutSummaryAction) + + # Add to Help menu. + help_menu = mainmenu.get_application_menu(ApplicationMenus.Help) + mainmenu.remove_item_from_application_menu( + shortcuts_action, + menu=help_menu + ) + def on_mainwindow_visible(self): self.apply_shortcuts() From 05baf63f3eda50007d73501046c8c51eb743db10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:12 -0500 Subject: [PATCH 49/68] Remove hard reference to editor in pylint --- spyder/plugins/pylint/plugin.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/spyder/plugins/pylint/plugin.py b/spyder/plugins/pylint/plugin.py index a1f2563cf26..76214553e31 100644 --- a/spyder/plugins/pylint/plugin.py +++ b/spyder/plugins/pylint/plugin.py @@ -15,6 +15,7 @@ from qtpy.QtCore import Qt, Signal, Slot # Local imports +from spyder.api.exceptions import SpyderAPIError from spyder.api.plugins import Plugins, SpyderDockablePlugin from spyder.api.plugin_registration.decorators import ( on_plugin_available, on_plugin_teardown) @@ -180,9 +181,13 @@ def _set_filename(self): """ Set filename without code analysis. """ - editor = self.get_plugin(Plugins.Editor) - if editor: - self.get_widget().set_filename(editor.get_current_filename()) + try: + editor = self.get_plugin(Plugins.Editor) + if editor: + self.get_widget().set_filename(editor.get_current_filename()) + except SpyderAPIError: + # Editor was deleted + pass # --- Public API # ------------------------------------------------------------------------ From 1b007b25454cd976ea0f23d8598abd484632d4e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:12 -0500 Subject: [PATCH 50/68] Update calls to remove_item_from_application_menu --- spyder/plugins/pylint/plugin.py | 7 ++----- spyder/plugins/shortcuts/plugin.py | 9 ++------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/spyder/plugins/pylint/plugin.py b/spyder/plugins/pylint/plugin.py index 76214553e31..c188f61d291 100644 --- a/spyder/plugins/pylint/plugin.py +++ b/spyder/plugins/pylint/plugin.py @@ -168,11 +168,8 @@ def on_projects_teardown(self): @on_plugin_teardown(plugin=Plugins.MainMenu) def on_main_menu_teardown(self): mainmenu = self.get_plugin(Plugins.MainMenu) - - pylint_act = self.get_action(PylintActions.AnalyzeCurrentFile) - source_menu = mainmenu.get_application_menu( - ApplicationMenus.Source) - mainmenu.remove_item_from_application_menu(pylint_act, menu=source_menu) + mainmenu.remove_item_from_application_menu( + PylintActions.AnalyzeCurrentFile, menu_id=ApplicationMenus.Source) # --- Private API # ------------------------------------------------------------------------ diff --git a/spyder/plugins/shortcuts/plugin.py b/spyder/plugins/shortcuts/plugin.py index f7e37a90833..3e347f1d685 100644 --- a/spyder/plugins/shortcuts/plugin.py +++ b/spyder/plugins/shortcuts/plugin.py @@ -106,14 +106,9 @@ def on_preferences_teardown(self): @on_plugin_teardown(plugin=Plugins.MainMenu) def on_main_menu_teardown(self): mainmenu = self.get_plugin(Plugins.MainMenu) - shortcuts_action = self.get_action( - ShortcutActions.ShortcutSummaryAction) - - # Add to Help menu. - help_menu = mainmenu.get_application_menu(ApplicationMenus.Help) mainmenu.remove_item_from_application_menu( - shortcuts_action, - menu=help_menu + ShortcutActions.ShortcutSummaryAction, + menu_id=ApplicationMenus.Help ) def on_mainwindow_visible(self): From b7831d8a9f9b33298eb62dc5f44f8f99fa69e907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:12 -0500 Subject: [PATCH 51/68] Address review comments --- spyder/plugins/pylint/plugin.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/spyder/plugins/pylint/plugin.py b/spyder/plugins/pylint/plugin.py index c188f61d291..b213b37eb05 100644 --- a/spyder/plugins/pylint/plugin.py +++ b/spyder/plugins/pylint/plugin.py @@ -118,10 +118,6 @@ def on_projects_available(self): # Connect to projects projects = self.get_plugin(Plugins.Projects) - self._set_project_dir = ( - lambda value: widget.set_conf("project_dir", value)) - self._unset_project_dir = ( - lambda value: widget.set_conf("project_dir", value)) projects.sig_project_loaded.connect(self._set_project_dir) projects.sig_project_closed.connect(self._unset_project_dir) @@ -146,6 +142,7 @@ def on_editor_teardown(self): pylint_act = self.get_action(PylintActions.AnalyzeCurrentFile) # TODO: use new API when editor has migrated + pylint_act.setVisible(False) editor.pythonfile_dependent_actions.remove(pylint_act) @on_plugin_teardown(plugin=Plugins.Preferences) @@ -169,7 +166,9 @@ def on_projects_teardown(self): def on_main_menu_teardown(self): mainmenu = self.get_plugin(Plugins.MainMenu) mainmenu.remove_item_from_application_menu( - PylintActions.AnalyzeCurrentFile, menu_id=ApplicationMenus.Source) + PylintActions.AnalyzeCurrentFile, + menu_id=ApplicationMenus.Source + ) # --- Private API # ------------------------------------------------------------------------ @@ -186,6 +185,14 @@ def _set_filename(self): # Editor was deleted pass + def _set_project_dir(self, value): + widget = self.get_widget() + widget.set_conf("project_dir", value) + + def _unset_project_dir(self, _unused): + widget = self.get_widget() + widget.set_conf("project_dir", None) + # --- Public API # ------------------------------------------------------------------------ def change_history_depth(self, value=None): From cd8dcac425bbb36ceae50845e7d017f31b9d0f62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:12 -0500 Subject: [PATCH 52/68] Final review comments --- spyder/plugins/pylint/plugin.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/spyder/plugins/pylint/plugin.py b/spyder/plugins/pylint/plugin.py index b213b37eb05..6307c162910 100644 --- a/spyder/plugins/pylint/plugin.py +++ b/spyder/plugins/pylint/plugin.py @@ -152,16 +152,11 @@ def on_preferences_teardown(self): @on_plugin_teardown(plugin=Plugins.Projects) def on_projects_teardown(self): - widget = self.get_widget() - - # Connect to projects + # Disconnect from projects projects = self.get_plugin(Plugins.Projects) projects.sig_project_loaded.disconnect(self._set_project_dir) projects.sig_project_closed.disconnect(self._unset_project_dir) - self._set_project_dir = None - self._unset_project_dir = None - @on_plugin_teardown(plugin=Plugins.MainMenu) def on_main_menu_teardown(self): mainmenu = self.get_plugin(Plugins.MainMenu) From 739b5e4d7dfea38575826ab1a63685b66fda35c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:12 -0500 Subject: [PATCH 53/68] Migrate statusbar, toolbar and tours to use the new teardown mechanism --- spyder/plugins/statusbar/plugin.py | 8 +++++++- spyder/plugins/toolbar/plugin.py | 14 +++++++++++++- spyder/plugins/tours/plugin.py | 10 +++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/spyder/plugins/statusbar/plugin.py b/spyder/plugins/statusbar/plugin.py index 58a5bc0cea7..45b7fc55c56 100644 --- a/spyder/plugins/statusbar/plugin.py +++ b/spyder/plugins/statusbar/plugin.py @@ -14,7 +14,8 @@ # Local imports from spyder.api.exceptions import SpyderAPIError from spyder.api.plugins import Plugins, SpyderPluginV2 -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.api.widgets.status import StatusBarWidget from spyder.config.base import running_under_pytest @@ -72,6 +73,11 @@ def on_preferences_available(self): preferences = self.get_plugin(Plugins.Preferences) preferences.register_plugin_preferences(self) + @on_plugin_teardown(plugin=Plugins.Preferences) + def on_preferences_teardown(self): + preferences = self.get_plugin(Plugins.Preferences) + preferences.deregister_plugin_preferences(self) + def after_container_creation(self): container = self.get_container() container.sig_show_status_bar_requested.connect( diff --git a/spyder/plugins/toolbar/plugin.py b/spyder/plugins/toolbar/plugin.py index 39b54f5f600..32de6394df7 100644 --- a/spyder/plugins/toolbar/plugin.py +++ b/spyder/plugins/toolbar/plugin.py @@ -15,7 +15,8 @@ # Local imports from spyder.api.exceptions import SpyderAPIError from spyder.api.plugins import SpyderPluginV2, Plugins -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.plugins.mainmenu.api import ApplicationMenus, ViewMenuSections from spyder.plugins.toolbar.api import ApplicationToolbars @@ -71,6 +72,17 @@ def on_main_menu_available(self): section=ViewMenuSections.Toolbar, before_section=ViewMenuSections.Layout) + @on_plugin_teardown(plugin=Plugins.MainMenu) + def on_main_menu_teardown(self): + mainmenu = self.get_plugin(Plugins.MainMenu) + # View menu Toolbar section + mainmenu.remove_item_from_application_menu( + self.toolbars_menu, + menu_id=ApplicationMenus.View) + mainmenu.remove_item_from_application_menu( + self.show_toolbars_action, + menu_id=ApplicationMenus.View) + def on_mainwindow_visible(self): container = self.get_container() diff --git a/spyder/plugins/tours/plugin.py b/spyder/plugins/tours/plugin.py index 8b041e1ce5c..4509f6f7129 100644 --- a/spyder/plugins/tours/plugin.py +++ b/spyder/plugins/tours/plugin.py @@ -12,7 +12,8 @@ # Local imports from spyder.api.plugins import Plugins, SpyderPluginV2 -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.config.base import get_safe_mode, running_under_pytest from spyder.plugins.application.api import ApplicationActions @@ -64,6 +65,13 @@ def on_main_menu_available(self): section=HelpMenuSections.Documentation, before=ApplicationActions.SpyderDocumentationAction) + @on_plugin_teardown(plugin=Plugins.MainMenu) + def on_main_menu_teardown(self): + mainmenu = self.get_plugin(Plugins.MainMenu) + mainmenu.remove_item_from_application_menu( + self.get_container().tour_action, + menu_id=ApplicationMenus.Help) + def on_mainwindow_visible(self): self.show_tour_message() From 66ea40b7ce30fffd75fdabf139c7417d71349587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:12 -0500 Subject: [PATCH 54/68] Update calls to remove_item_from_application_menu --- spyder/plugins/toolbar/plugin.py | 4 ++-- spyder/plugins/tours/plugin.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spyder/plugins/toolbar/plugin.py b/spyder/plugins/toolbar/plugin.py index 32de6394df7..0c22acf14b4 100644 --- a/spyder/plugins/toolbar/plugin.py +++ b/spyder/plugins/toolbar/plugin.py @@ -77,10 +77,10 @@ def on_main_menu_teardown(self): mainmenu = self.get_plugin(Plugins.MainMenu) # View menu Toolbar section mainmenu.remove_item_from_application_menu( - self.toolbars_menu, + 'toolbars_menu', menu_id=ApplicationMenus.View) mainmenu.remove_item_from_application_menu( - self.show_toolbars_action, + 'show toolbars', menu_id=ApplicationMenus.View) def on_mainwindow_visible(self): diff --git a/spyder/plugins/tours/plugin.py b/spyder/plugins/tours/plugin.py index 4509f6f7129..f413b077a20 100644 --- a/spyder/plugins/tours/plugin.py +++ b/spyder/plugins/tours/plugin.py @@ -17,7 +17,7 @@ from spyder.api.translations import get_translation from spyder.config.base import get_safe_mode, running_under_pytest from spyder.plugins.application.api import ApplicationActions -from spyder.plugins.tours.container import ToursContainer +from spyder.plugins.tours.container import TourActions, ToursContainer from spyder.plugins.tours.tours import INTRO_TOUR, TourIdentifiers from spyder.plugins.mainmenu.api import ApplicationMenus, HelpMenuSections @@ -69,7 +69,7 @@ def on_main_menu_available(self): def on_main_menu_teardown(self): mainmenu = self.get_plugin(Plugins.MainMenu) mainmenu.remove_item_from_application_menu( - self.get_container().tour_action, + TourActions.ShowTour, menu_id=ApplicationMenus.Help) def on_mainwindow_visible(self): From 65833aad3146ed29c3388bba99f46e7debb268d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:12 -0500 Subject: [PATCH 55/68] Address review comments --- spyder/plugins/statusbar/plugin.py | 3 +++ spyder/plugins/toolbar/container.py | 3 +++ spyder/plugins/toolbar/plugin.py | 12 +++++++----- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/spyder/plugins/statusbar/plugin.py b/spyder/plugins/statusbar/plugin.py index 45b7fc55c56..7c8e49ac89b 100644 --- a/spyder/plugins/statusbar/plugin.py +++ b/spyder/plugins/statusbar/plugin.py @@ -68,6 +68,9 @@ def on_initialize(self): self.add_status_widget( self.clock_status, StatusBarWidgetPosition.Right) + def on_close(self): + self._statusbar.setVisible(False) + @on_plugin_available(plugin=Plugins.Preferences) def on_preferences_available(self): preferences = self.get_plugin(Plugins.Preferences) diff --git a/spyder/plugins/toolbar/container.py b/spyder/plugins/toolbar/container.py index 0c7fe84c1a7..a6bcb9324ce 100644 --- a/spyder/plugins/toolbar/container.py +++ b/spyder/plugins/toolbar/container.py @@ -323,6 +323,9 @@ def load_last_visible_toolbars(self): else: self._get_visible_toolbars() + for toolbar in self._visible_toolbars: + toolbar.setVisible(True) + self.update_actions() def create_toolbars_menu(self): diff --git a/spyder/plugins/toolbar/plugin.py b/spyder/plugins/toolbar/plugin.py index 0c22acf14b4..c22e059505f 100644 --- a/spyder/plugins/toolbar/plugin.py +++ b/spyder/plugins/toolbar/plugin.py @@ -20,7 +20,8 @@ from spyder.api.translations import get_translation from spyder.plugins.mainmenu.api import ApplicationMenus, ViewMenuSections from spyder.plugins.toolbar.api import ApplicationToolbars -from spyder.plugins.toolbar.container import ToolbarContainer +from spyder.plugins.toolbar.container import ( + ToolbarContainer, ToolbarMenus, ToolbarActions) # Third-party imports from qtpy.QtWidgets import QWidget @@ -77,10 +78,10 @@ def on_main_menu_teardown(self): mainmenu = self.get_plugin(Plugins.MainMenu) # View menu Toolbar section mainmenu.remove_item_from_application_menu( - 'toolbars_menu', + ToolbarMenus.ToolbarsMenu, menu_id=ApplicationMenus.View) mainmenu.remove_item_from_application_menu( - 'show toolbars', + ToolbarActions.ShowToolbars, menu_id=ApplicationMenus.View) def on_mainwindow_visible(self): @@ -115,8 +116,9 @@ def on_mainwindow_visible(self): def on_close(self, _unused): container = self.get_container() - if container._visible_toolbars: - container._save_visible_toolbars() + container._save_visible_toolbars() + for toolbar in container._visible_toolbars: + toolbar.setVisible(False) # --- Public API # ------------------------------------------------------------------------ From bbc8d53183460b80944556b10f15a8410cd5e820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:13 -0500 Subject: [PATCH 56/68] Add proper signature to statusbar on_close method --- spyder/plugins/statusbar/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder/plugins/statusbar/plugin.py b/spyder/plugins/statusbar/plugin.py index 7c8e49ac89b..db87cbe708c 100644 --- a/spyder/plugins/statusbar/plugin.py +++ b/spyder/plugins/statusbar/plugin.py @@ -68,7 +68,7 @@ def on_initialize(self): self.add_status_widget( self.clock_status, StatusBarWidgetPosition.Right) - def on_close(self): + def on_close(self, _unused): self._statusbar.setVisible(False) @on_plugin_available(plugin=Plugins.Preferences) From a8dbe84fc88d1a4006862464fe8e60b4272fd7ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:13 -0500 Subject: [PATCH 57/68] Address review comments --- spyder/plugins/toolbar/plugin.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spyder/plugins/toolbar/plugin.py b/spyder/plugins/toolbar/plugin.py index c22e059505f..d5072b824de 100644 --- a/spyder/plugins/toolbar/plugin.py +++ b/spyder/plugins/toolbar/plugin.py @@ -58,6 +58,12 @@ def on_initialize(self): create_app_toolbar(ApplicationToolbars.Debug, _("Debug toolbar")) create_app_toolbar(ApplicationToolbars.Main, _("Main toolbar")) + def on_close(self): + container = self.get_container() + container._save_visible_toolbars() + for toolbar in container._visible_toolbars: + toolbar.setVisible(False) + @on_plugin_available(plugin=Plugins.MainMenu) def on_main_menu_available(self): mainmenu = self.get_plugin(Plugins.MainMenu) From b93c040532b7965d5ae0344a43f45e37dc83e8a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:13 -0500 Subject: [PATCH 58/68] Migrate variable explorer and working directory to use the new teardown mechanism --- spyder/plugins/toolbar/container.py | 22 +++++++ spyder/plugins/toolbar/plugin.py | 14 +++++ spyder/plugins/variableexplorer/plugin.py | 12 +++- spyder/plugins/workingdirectory/plugin.py | 72 ++++++++++++++++++++--- 4 files changed, 108 insertions(+), 12 deletions(-) diff --git a/spyder/plugins/toolbar/container.py b/spyder/plugins/toolbar/container.py index a6bcb9324ce..30dc948776f 100644 --- a/spyder/plugins/toolbar/container.py +++ b/spyder/plugins/toolbar/container.py @@ -212,6 +212,28 @@ def add_application_toolbar(self, toolbar, mainwindow=None): self._add_missing_toolbar_elements(toolbar, toolbar_id) + def remove_application_toolbar(self, toolbar_id: str, mainwindow=None): + """ + Add toolbar from application toolbars. + + Parameters + ---------- + toolbar: str + The application toolbar to add to the `mainwindow`. + mainwindow: QMainWindow + The main application window. + """ + + if toolbar_id not in self._ADDED_TOOLBARS: + raise SpyderAPIError( + 'Toolbar with ID "{}" is not in the main window'.format( + toolbar_id)) + + toolbar = self._ADDED_TOOLBARS.pop(toolbar_id) + self._toolbarslist.remove(toolbar) + + if mainwindow: + mainwindow.removeToolBar(toolbar) def add_item_to_application_toolbar(self, item: ToolbarItem, diff --git a/spyder/plugins/toolbar/plugin.py b/spyder/plugins/toolbar/plugin.py index d5072b824de..8dcbd5518e0 100644 --- a/spyder/plugins/toolbar/plugin.py +++ b/spyder/plugins/toolbar/plugin.py @@ -163,6 +163,20 @@ def add_application_toolbar(self, toolbar): """ self.get_container().add_application_toolbar(toolbar, self._main) + def remove_application_toolbar(self, toolbar_id: str): + """ + Remove toolbar from the application toolbars. + + This can be used to remove a custom toolbar. The `WorkingDirectory` + plugin is an example of this. + + Parameters + ---------- + toolbar: str + The application toolbar to remove from the main window. + """ + self.get_container().remove_application_toolbar(toolbar_id, self._main) + def add_item_to_application_toolbar(self, item: Union[SpyderAction, QWidget], toolbar_id: Optional[str] = None, diff --git a/spyder/plugins/variableexplorer/plugin.py b/spyder/plugins/variableexplorer/plugin.py index 279cc6c88e5..e4ad4d8d281 100644 --- a/spyder/plugins/variableexplorer/plugin.py +++ b/spyder/plugins/variableexplorer/plugin.py @@ -10,7 +10,8 @@ # Local imports from spyder.api.plugins import Plugins, SpyderDockablePlugin -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.plugins.variableexplorer.confpage import ( VariableExplorerConfigPage) @@ -67,8 +68,13 @@ def on_ipyconsole_available(self): ipyconsole.sig_shellwidget_deleted.connect( self.remove_shellwidget) - def unregister(self): - # Plugins + @on_plugin_teardown(plugin=Plugins.Preferences) + def on_preferences_teardown(self): + preferences = self.get_plugin(Plugins.Preferences) + preferences.deregister_plugin_preferences(self) + + @on_plugin_teardown(plugin=Plugins.IPythonConsole) + def on_ipyconsole_teardown(self): ipyconsole = self.get_plugin(Plugins.IPythonConsole) # Signals diff --git a/spyder/plugins/workingdirectory/plugin.py b/spyder/plugins/workingdirectory/plugin.py index b46e7d36e82..8c698939de9 100644 --- a/spyder/plugins/workingdirectory/plugin.py +++ b/spyder/plugins/workingdirectory/plugin.py @@ -16,7 +16,8 @@ # Local imports from spyder.api.plugins import SpyderPluginV2, Plugins -from spyder.api.plugin_registration.decorators import on_plugin_available +from spyder.api.plugin_registration.decorators import ( + on_plugin_available, on_plugin_teardown) from spyder.api.translations import get_translation from spyder.config.base import get_conf_path from spyder.plugins.workingdirectory.confpage import WorkingDirectoryConfigPage @@ -90,18 +91,21 @@ def on_preferences_available(self): @on_plugin_available(plugin=Plugins.Editor) def on_editor_available(self): editor = self.get_plugin(Plugins.Editor) - editor.sig_dir_opened.connect( + self._editor_dir_chdir = ( lambda path, plugin=editor: self.chdir(path, editor)) + editor.sig_dir_opened.connect(self._editor_dir_chdir) @on_plugin_available(plugin=Plugins.Explorer) def on_explorer_available(self): explorer = self.get_plugin(Plugins.Explorer) - - self.sig_current_directory_changed.connect( + self._explorer_path_chdir = ( lambda path: explorer.chdir(path, emit=False)) - explorer.sig_dir_opened.connect( + self._explorer_dir_opened = ( lambda path, plugin=explorer: self.chdir(path, plugin)) + self.sig_current_directory_changed.connect(self._explorer_path_chdir) + explorer.sig_dir_opened.connect(self._explorer_dir_opened) + @on_plugin_available(plugin=Plugins.IPythonConsole) def on_ipyconsole_available(self): ipyconsole = self.get_plugin(Plugins.IPythonConsole) @@ -110,27 +114,77 @@ def on_ipyconsole_available(self): ipyconsole.set_current_client_working_directory) # TODO: chdir_current_client might follow a better naming # convention - ipyconsole.sig_current_directory_changed.connect( + self._ipyconsole_chdir = ( lambda path, plugin=ipyconsole: self.chdir(path, plugin)) + ipyconsole.sig_current_directory_changed.connect( + self._ipyconsole_chdir) @on_plugin_available(plugin=Plugins.Projects) def on_projects_available(self): projects = self.get_plugin(Plugins.Projects) - projects.sig_project_loaded.connect( + self._projects_loaded = ( lambda path: self.chdir( directory=path, sender_plugin=projects ) ) - - projects.sig_project_closed[object].connect( + self._projects_closed = ( lambda path: self.chdir( directory=projects.get_last_working_dir(), sender_plugin=projects ) ) + projects.sig_project_loaded.connect(self._projects_loaded) + projects.sig_project_closed[object].connect(self._projects_closed) + + @on_plugin_teardown(plugin=Plugins.Toolbar) + def on_toolbar_teardown(self): + container = self.get_container() + toolbar = self.get_plugin(Plugins.Toolbar) + toolbar.remove_application_toolbar(container.toolbar) + + @on_plugin_teardown(plugin=Plugins.Preferences) + def on_preferences_teardown(self): + preferences = self.get_plugin(Plugins.Preferences) + preferences.deregister_plugin_preferences(self) + + @on_plugin_teardown(plugin=Plugins.Editor) + def on_editor_teardown(self): + editor = self.get_plugin(Plugins.Editor) + editor.sig_dir_opened.disconnect(self._editor_dir_chdir) + self._editor_dir_chdir = None + + @on_plugin_teardown(plugin=Plugins.Explorer) + def on_explorer_teardown(self): + explorer = self.get_plugin(Plugins.Explorer) + self.sig_current_directory_changed.disconnect(self._explorer_path_chdir) + explorer.sig_dir_opened.disconnect(self._explorer_dir_opened) + + self._explorer_path_chdir = None + self._explorer_dir_opened = None + + @on_plugin_teardown(plugin=Plugins.IPythonConsole) + def on_ipyconsole_teardown(self): + ipyconsole = self.get_plugin(Plugins.IPythonConsole) + + self.sig_current_directory_changed.disconnect( + ipyconsole.set_current_client_working_directory) + ipyconsole.sig_current_directory_changed.disconnect( + self._ipyconsole_chdir) + + self._ipyconsole_chdir = None + + @on_plugin_teardown(plugin=Plugins.Projects) + def on_projects_teardown(self): + projects = self.get_plugin(Plugins.Projects) + projects.sig_project_loaded.disconnect(self._projects_loaded) + projects.sig_project_closed[object].disconnect(self._projects_closed) + + self._projects_loaded = None + self._projects_closed = None + # --- Public API # ------------------------------------------------------------------------ def chdir(self, directory, sender_plugin=None): From 86479280088363abb08bc7de38185e476926f939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:13 -0500 Subject: [PATCH 59/68] Update calls to remove_item_from_application_menu --- spyder/plugins/workingdirectory/plugin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spyder/plugins/workingdirectory/plugin.py b/spyder/plugins/workingdirectory/plugin.py index 8c698939de9..a46ea394c1e 100644 --- a/spyder/plugins/workingdirectory/plugin.py +++ b/spyder/plugins/workingdirectory/plugin.py @@ -141,9 +141,8 @@ def on_projects_available(self): @on_plugin_teardown(plugin=Plugins.Toolbar) def on_toolbar_teardown(self): - container = self.get_container() toolbar = self.get_plugin(Plugins.Toolbar) - toolbar.remove_application_toolbar(container.toolbar) + toolbar.remove_application_toolbar('working_directory_toolbar') @on_plugin_teardown(plugin=Plugins.Preferences) def on_preferences_teardown(self): From 80b47d54af8ee6ab5515f06f51f6376edbd2f276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:13 -0500 Subject: [PATCH 60/68] Apply review comments --- spyder/plugins/toolbar/container.py | 4 +- spyder/plugins/workingdirectory/plugin.py | 86 +++++++++++------------ 2 files changed, 43 insertions(+), 47 deletions(-) diff --git a/spyder/plugins/toolbar/container.py b/spyder/plugins/toolbar/container.py index 30dc948776f..d5b735ab4dd 100644 --- a/spyder/plugins/toolbar/container.py +++ b/spyder/plugins/toolbar/container.py @@ -214,12 +214,12 @@ def add_application_toolbar(self, toolbar, mainwindow=None): def remove_application_toolbar(self, toolbar_id: str, mainwindow=None): """ - Add toolbar from application toolbars. + Remove toolbar from application toolbars. Parameters ---------- toolbar: str - The application toolbar to add to the `mainwindow`. + The application toolbar to remove from the `mainwindow`. mainwindow: QMainWindow The main application window. """ diff --git a/spyder/plugins/workingdirectory/plugin.py b/spyder/plugins/workingdirectory/plugin.py index a46ea394c1e..cdf08fd5888 100644 --- a/spyder/plugins/workingdirectory/plugin.py +++ b/spyder/plugins/workingdirectory/plugin.py @@ -23,6 +23,7 @@ from spyder.plugins.workingdirectory.confpage import WorkingDirectoryConfigPage from spyder.plugins.workingdirectory.container import ( WorkingDirectoryContainer) +from spyder.plugins.toolbar.api import ApplicationToolbars from spyder.utils import encoding # Localization @@ -91,19 +92,12 @@ def on_preferences_available(self): @on_plugin_available(plugin=Plugins.Editor) def on_editor_available(self): editor = self.get_plugin(Plugins.Editor) - self._editor_dir_chdir = ( - lambda path, plugin=editor: self.chdir(path, editor)) - editor.sig_dir_opened.connect(self._editor_dir_chdir) + editor.sig_dir_opened.connect(self._editor_change_dir) @on_plugin_available(plugin=Plugins.Explorer) def on_explorer_available(self): explorer = self.get_plugin(Plugins.Explorer) - self._explorer_path_chdir = ( - lambda path: explorer.chdir(path, emit=False)) - self._explorer_dir_opened = ( - lambda path, plugin=explorer: self.chdir(path, plugin)) - - self.sig_current_directory_changed.connect(self._explorer_path_chdir) + self.sig_current_directory_changed.connect(self._explorer_change_dir) explorer.sig_dir_opened.connect(self._explorer_dir_opened) @on_plugin_available(plugin=Plugins.IPythonConsole) @@ -112,37 +106,20 @@ def on_ipyconsole_available(self): self.sig_current_directory_changed.connect( ipyconsole.set_current_client_working_directory) - # TODO: chdir_current_client might follow a better naming - # convention - self._ipyconsole_chdir = ( - lambda path, plugin=ipyconsole: self.chdir(path, plugin)) ipyconsole.sig_current_directory_changed.connect( - self._ipyconsole_chdir) + self._ipyconsole_change_dir) @on_plugin_available(plugin=Plugins.Projects) def on_projects_available(self): projects = self.get_plugin(Plugins.Projects) - self._projects_loaded = ( - lambda path: - self.chdir( - directory=path, - sender_plugin=projects - ) - ) - self._projects_closed = ( - lambda path: self.chdir( - directory=projects.get_last_working_dir(), - sender_plugin=projects - ) - ) - - projects.sig_project_loaded.connect(self._projects_loaded) - projects.sig_project_closed[object].connect(self._projects_closed) + projects.sig_project_loaded.connect(self._project_loaded) + projects.sig_project_closed[object].connect(self._project_closed) @on_plugin_teardown(plugin=Plugins.Toolbar) def on_toolbar_teardown(self): toolbar = self.get_plugin(Plugins.Toolbar) - toolbar.remove_application_toolbar('working_directory_toolbar') + toolbar.remove_application_toolbar( + ApplicationToolbars.WorkingDirectory) @on_plugin_teardown(plugin=Plugins.Preferences) def on_preferences_teardown(self): @@ -152,18 +129,14 @@ def on_preferences_teardown(self): @on_plugin_teardown(plugin=Plugins.Editor) def on_editor_teardown(self): editor = self.get_plugin(Plugins.Editor) - editor.sig_dir_opened.disconnect(self._editor_dir_chdir) - self._editor_dir_chdir = None + editor.sig_dir_opened.disconnect(self._editor_change_dir) @on_plugin_teardown(plugin=Plugins.Explorer) def on_explorer_teardown(self): explorer = self.get_plugin(Plugins.Explorer) - self.sig_current_directory_changed.disconnect(self._explorer_path_chdir) + self.sig_current_directory_changed.disconnect(self._explorer_change_dir) explorer.sig_dir_opened.disconnect(self._explorer_dir_opened) - self._explorer_path_chdir = None - self._explorer_dir_opened = None - @on_plugin_teardown(plugin=Plugins.IPythonConsole) def on_ipyconsole_teardown(self): ipyconsole = self.get_plugin(Plugins.IPythonConsole) @@ -171,18 +144,13 @@ def on_ipyconsole_teardown(self): self.sig_current_directory_changed.disconnect( ipyconsole.set_current_client_working_directory) ipyconsole.sig_current_directory_changed.disconnect( - self._ipyconsole_chdir) - - self._ipyconsole_chdir = None + self._ipyconsole_change_dir) @on_plugin_teardown(plugin=Plugins.Projects) def on_projects_teardown(self): projects = self.get_plugin(Plugins.Projects) - projects.sig_project_loaded.disconnect(self._projects_loaded) - projects.sig_project_closed[object].disconnect(self._projects_closed) - - self._projects_loaded = None - self._projects_closed = None + projects.sig_project_loaded.disconnect(self._project_loaded) + projects.sig_project_closed[object].disconnect(self._project_closed) # --- Public API # ------------------------------------------------------------------------ @@ -258,3 +226,31 @@ def get_workdir(self): Current working directory. """ return self.get_container().get_workdir() + + # -------------------------- Private API ---------------------------------- + def _editor_change_dir(self, path): + editor = self.get_plugin(Plugins.Editor) + self.chdir(path, editor) + + def _explorer_change_dir(self, path): + explorer = self.get_plugin(Plugins.Explorer) + explorer.chdir(path, emit=False) + + def _explorer_dir_opened(self, path): + explorer = self.get_plugin(Plugins.Explorer) + self.chdir(path, explorer) + + def _ipyconsole_change_dir(self, path): + ipyconsole = self.get_plugin(Plugins.IPythonConsole) + self.chdir(path, ipyconsole) + + def _project_loaded(self, path): + projects = self.get_plugin(Plugins.Projects) + self.chdir(directory=path, sender_plugin=projects) + + def _project_closed(self, path): + projects = self.get_plugin(Plugins.Projects) + self.chdir( + directory=projects.get_last_working_dir(), + sender_plugin=projects + ) From 1a743edb97d53ef7af8af15ebe2886c560f726a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:13 -0500 Subject: [PATCH 61/68] Remove duplicate on__close --- spyder/plugins/toolbar/plugin.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/spyder/plugins/toolbar/plugin.py b/spyder/plugins/toolbar/plugin.py index 8dcbd5518e0..5e7f688b0e0 100644 --- a/spyder/plugins/toolbar/plugin.py +++ b/spyder/plugins/toolbar/plugin.py @@ -58,12 +58,6 @@ def on_initialize(self): create_app_toolbar(ApplicationToolbars.Debug, _("Debug toolbar")) create_app_toolbar(ApplicationToolbars.Main, _("Main toolbar")) - def on_close(self): - container = self.get_container() - container._save_visible_toolbars() - for toolbar in container._visible_toolbars: - toolbar.setVisible(False) - @on_plugin_available(plugin=Plugins.MainMenu) def on_main_menu_available(self): mainmenu = self.get_plugin(Plugins.MainMenu) From e14b5ac1a0f9db9b7e1bab1257e521e3f0388bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:14 -0500 Subject: [PATCH 62/68] Add preference page to enable and disable plugins --- spyder/api/plugin_registration/confpage.py | 108 +++++++++++++++++++++ spyder/api/plugin_registration/registry.py | 66 ++++++++++++- spyder/api/plugins/new_api.py | 3 +- spyder/api/preferences.py | 8 +- spyder/app/mainwindow.py | 15 ++- spyder/config/manager.py | 3 + spyder/plugins/appearance/plugin.py | 3 +- spyder/plugins/application/plugin.py | 3 +- spyder/plugins/breakpoints/plugin.py | 3 +- spyder/plugins/completion/plugin.py | 3 +- spyder/plugins/console/plugin.py | 3 +- spyder/plugins/editor/plugin.py | 5 +- spyder/plugins/explorer/plugin.py | 3 +- spyder/plugins/findinfiles/plugin.py | 3 +- spyder/plugins/help/plugin.py | 3 +- spyder/plugins/history/plugin.py | 3 +- spyder/plugins/ipythonconsole/plugin.py | 7 +- spyder/plugins/layout/plugin.py | 3 +- spyder/plugins/maininterpreter/plugin.py | 3 +- spyder/plugins/mainmenu/plugin.py | 3 +- spyder/plugins/onlinehelp/plugin.py | 3 +- spyder/plugins/outlineexplorer/plugin.py | 3 +- spyder/plugins/plots/plugin.py | 3 +- spyder/plugins/preferences/plugin.py | 3 +- spyder/plugins/profiler/plugin.py | 3 +- spyder/plugins/projects/plugin.py | 3 +- spyder/plugins/pylint/plugin.py | 3 +- spyder/plugins/run/plugin.py | 3 +- spyder/plugins/shortcuts/plugin.py | 3 +- spyder/plugins/statusbar/plugin.py | 3 +- spyder/plugins/toolbar/plugin.py | 3 +- spyder/plugins/tours/plugin.py | 3 +- spyder/plugins/variableexplorer/plugin.py | 3 +- spyder/plugins/workingdirectory/plugin.py | 3 +- spyder/utils/icon_manager.py | 2 + 35 files changed, 261 insertions(+), 34 deletions(-) create mode 100644 spyder/api/plugin_registration/confpage.py diff --git a/spyder/api/plugin_registration/confpage.py b/spyder/api/plugin_registration/confpage.py new file mode 100644 index 00000000000..0b8d6b04f2a --- /dev/null +++ b/spyder/api/plugin_registration/confpage.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +# +# Copyright © Spyder Project Contributors +# Licensed under the terms of the MIT License +# (see spyder/__init__.py for details) + +"""Spyder completion plugin configuration page.""" + +# Third party imports +from qtpy.QtWidgets import (QGroupBox, QVBoxLayout, QCheckBox, + QGridLayout, QLabel) + +# Local imports +from spyder.config.base import _ +from spyder.config.manager import CONF +from spyder.api.preferences import PluginConfigPage +from spyder.api.plugins import SpyderPlugin + + +class PluginsConfigPage(PluginConfigPage): + def setup_page(self): + newcb = self.create_checkbox + self.plugins_checkboxes = {} + + header_label = QLabel( + _("Spyder can run with a reduced number of internal and external " + "plugins in order to provide a lighter experience. Any plugin " + "unchecked in this page will be unloaded immediately and will " + "not be loaded 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 issubclass(PluginClass, SpyderPlugin): + # Do not add Spyder 4 plugins to the disable page + continue + + if not PluginClass.CAN_BE_DISABLED: + # Do not list core plugins that can not be disabled + continue + + plugin_loc_name = PluginClass.get_name() + plugin_state = CONF.get(conf_section_name, 'enable', True) + cb = newcb(plugin_loc_name, 'enable', default=True, + section=conf_section_name) + 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] + + if issubclass(PluginClass, SpyderPlugin): + # Do not add Spyder 4 plugins to the disable page + continue + + plugin_loc_name = PluginClass.get_name() + cb = newcb(_('Enable {0} plugin').format(plugin_loc_name), + 'enable', default=True, section=conf_section_name) + 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 + + self.plugin.register_plugin(self.main, PluginClass, + external=external) + elif not cb.isChecked() and previous_state: + self.plugin.delete_plugin(plugin_name) + return set({}) diff --git a/spyder/api/plugin_registration/registry.py b/spyder/api/plugin_registration/registry.py index 0f7102c5426..f35cb4b91f1 100644 --- a/spyder/api/plugin_registration/registry.py +++ b/spyder/api/plugin_registration/registry.py @@ -14,11 +14,17 @@ from qtpy.QtCore import QObject, Signal # Local imports +from spyder import dependencies +from spyder.config.base import _, running_under_pytest from spyder.config.manager import CONF +from spyder.api.config.mixins import SpyderConfigurationAccessor +from spyder.api.plugin_registration.confpage import PluginsConfigPage +from spyder.api.plugins.enum import Plugins from spyder.api.exceptions import SpyderAPIError from spyder.api.plugins import ( Plugins, SpyderPluginV2, SpyderDockablePlugin, SpyderPluginWidget, SpyderPlugin) +from spyder.utils.icon_manager import ima # TODO: Remove SpyderPlugin and SpyderPluginWidget once the migration @@ -34,7 +40,23 @@ logger = logging.getLogger(__name__) -class SpyderPluginRegistry(QObject): +class PreferencesAdapter(SpyderConfigurationAccessor): + # Fake class constants used to register the configuration page + CONF_WIDGET_CLASS = PluginsConfigPage + NAME = 'plugin_registry' + CONF_VERSION = None + ADDITIONAL_CONF_OPTIONS = None + ADDITIONAL_CONF_TABS = None + CONF_SECTION = "" + + def apply_plugin_settings(self, _unused): + pass + + def apply_conf(self, _unused): + pass + + +class SpyderPluginRegistry(QObject, PreferencesAdapter): """ Global plugin registry. @@ -66,6 +88,10 @@ class SpyderPluginRegistry(QObject): def __init__(self): super().__init__() + PreferencesAdapter.__init__(self) + + self.main = None + # Dictionary that maps a plugin name to a list of the plugin names # that depend on it. self.plugin_dependents = {} # type: Dict[str, Dict[str, List[str]]] @@ -92,6 +118,12 @@ def __init__(self): # Set that stores the names of the external plugins self.external_plugins = set({}) # type: set[str] + # Dictionary that contains all the internal plugins (enabled or not) + self.all_internal_plugins = {} # type: Dict[str, Tuple[str, Type[SpyderPluginClass]]] + + # Dictionary that contains all the external plugins (enabled or not) + self.all_external_plugins = {} # type: Dict[str, Tuple[str, Type[SpyderPluginClass]]] + # ------------------------- PRIVATE API ----------------------------------- def _update_dependents(self, plugin: str, dependent_plugin: str, key: str): """Add `dependent_plugin` to the list of dependents of `plugin`.""" @@ -170,6 +202,17 @@ def _instantiate_spyder5_plugin( else: self.internal_plugins |= {plugin_name} + if external: + if not running_under_pytest(): + # These attributes come from spyder.app.find_plugins + module = PluginClass._spyder_module_name + package_name = PluginClass._spyder_package_name + version = PluginClass._spyder_version + description = instance.get_description() + dependencies.add(module, package_name, description, + version, None, + kind=dependencies.PLUGIN) + return plugin_instance def _instantiate_spyder4_plugin( @@ -344,6 +387,10 @@ def notify_plugin_availability(self, plugin_name: str, plugin_instance = self.plugin_registry[plugin] plugin_instance._on_plugin_available(plugin_name) + if plugin_name == Plugins.Preferences and not running_under_pytest(): + plugin_instance = self.plugin_registry[plugin_name] + plugin_instance.register_plugin_preferences(self) + def delete_plugin(self, plugin_name: str) -> bool: """ Remove and delete a plugin from the registry by its name. @@ -589,6 +636,23 @@ def reset(self): # Omit failures if there are no slots connected pass + def set_all_internal_plugins( + self, all_plugins: Dict[str, Type[SpyderPluginClass]]): + self.all_internal_plugins = all_plugins + + def set_all_external_plugins( + self, all_plugins: Dict[str, Type[SpyderPluginClass]]): + self.all_external_plugins = all_plugins + + def set_main(self, main): + self.main = main + + def get_icon(self): + return ima.icon('plugins') + + def get_name(self): + return _('Plugins') + def __contains__(self, plugin_name: str) -> bool: """ Determine if a plugin name is contained in the registry. diff --git a/spyder/api/plugins/new_api.py b/spyder/api/plugins/new_api.py index a4929f2b96e..8ae380d6382 100644 --- a/spyder/api/plugins/new_api.py +++ b/spyder/api/plugins/new_api.py @@ -650,7 +650,8 @@ def get_font(cls, rich_text=False): # --- API: Mandatory methods to define ----------------------------------- # ------------------------------------------------------------------------ - def get_name(self): + @staticmethod + def get_name(): """ Return the plugin localized name. diff --git a/spyder/api/preferences.py b/spyder/api/preferences.py index 85a04361b04..bca573fdde6 100644 --- a/spyder/api/preferences.py +++ b/spyder/api/preferences.py @@ -82,9 +82,13 @@ class PluginConfigPage(SpyderConfigPage): def __init__(self, plugin, parent): self.plugin = plugin - self.CONF_SECTION = plugin.CONF_SECTION self.main = parent.main - self.get_font = plugin.get_font + + if hasattr(plugin, 'CONF_SECTION'): + self.CONF_SECTION = plugin.CONF_SECTION + + if hasattr(plugin, 'get_font'): + self.get_font = plugin.get_font if not self.APPLY_CONF_PAGE_SETTINGS: self._patch_apply_settings(plugin) diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py index ca738e1cb82..68de164bca9 100644 --- a/spyder/app/mainwindow.py +++ b/spyder/app/mainwindow.py @@ -765,6 +765,8 @@ def setup(self): lambda plugin_name, omit_conf: self.register_plugin( plugin_name, omit_conf=omit_conf)) + PLUGIN_REGISTRY.set_main(self) + # TODO: Remove circular dependency between help and ipython console # and remove this import. Help plugin should take care of it from spyder.plugins.help.utils.sphinxify import CSS_PATH, DARK_CSS_PATH @@ -851,12 +853,20 @@ def setup(self): # Determine 'enable' config for the plugins that have it enabled_plugins = {} + registry_internal_plugins = {} + registry_external_plugins = {} for plugin in all_plugins.values(): plugin_name = plugin.NAME plugin_main_attribute_name = ( self._INTERNAL_PLUGINS_MAPPING[plugin_name] if plugin_name in self._INTERNAL_PLUGINS_MAPPING else plugin_name) + if plugin_name in internal_plugins: + registry_internal_plugins[plugin_name] = ( + plugin_main_attribute_name, plugin) + else: + registry_external_plugins[plugin_name] = ( + plugin_main_attribute_name, plugin) try: if CONF.get(plugin_main_attribute_name, "enable"): enabled_plugins[plugin_name] = plugin @@ -865,6 +875,9 @@ def setup(self): enabled_plugins[plugin_name] = plugin PLUGIN_REGISTRY.set_plugin_enabled(plugin_name) + PLUGIN_REGISTRY.set_all_internal_plugins(registry_internal_plugins) + PLUGIN_REGISTRY.set_all_external_plugins(registry_external_plugins) + # Instantiate internal Spyder 5 plugins for plugin_name in internal_plugins: if plugin_name in enabled_plugins: @@ -1197,7 +1210,7 @@ def post_visible_setup(self): # Show Help and Consoles by default plugins_to_show = [self.ipyconsole] - if self.help is not None: + if hasattr(self, 'help'): plugins_to_show.append(self.help) for plugin in plugins_to_show: if plugin.dockwidget.isVisible(): diff --git a/spyder/config/manager.py b/spyder/config/manager.py index 9eedb99141b..124398994e5 100644 --- a/spyder/config/manager.py +++ b/spyder/config/manager.py @@ -614,6 +614,9 @@ def config_shortcut(self, action, context, name, parent): def iter_shortcuts(self): """Iterate over keyboard shortcuts.""" for context_name, keystr in self._user_config.items('shortcuts'): + if context_name == 'enable': + continue + if 'additional_configuration' not in context_name: context, name = context_name.split('/', 1) yield context, name, keystr diff --git a/spyder/plugins/appearance/plugin.py b/spyder/plugins/appearance/plugin.py index 6232032dbb1..3e09c5908f1 100644 --- a/spyder/plugins/appearance/plugin.py +++ b/spyder/plugins/appearance/plugin.py @@ -39,7 +39,8 @@ class Appearance(SpyderPluginV2): # --- SpyderPluginV2 API # ------------------------------------------------------------------------ - def get_name(self): + @staticmethod + def get_name(): return _("Appearance") def get_description(self): diff --git a/spyder/plugins/application/plugin.py b/spyder/plugins/application/plugin.py index d2267d84e32..2f7918f3c6b 100644 --- a/spyder/plugins/application/plugin.py +++ b/spyder/plugins/application/plugin.py @@ -50,7 +50,8 @@ class Application(SpyderPluginV2): CONF_WIDGET_CLASS = ApplicationConfigPage CAN_BE_DISABLED = False - def get_name(self): + @staticmethod + def get_name(): return _('Application') def get_icon(self): diff --git a/spyder/plugins/breakpoints/plugin.py b/spyder/plugins/breakpoints/plugin.py index 302e2e24cff..66ec9b8d0b4 100644 --- a/spyder/plugins/breakpoints/plugin.py +++ b/spyder/plugins/breakpoints/plugin.py @@ -89,7 +89,8 @@ class Breakpoints(SpyderDockablePlugin): # --- SpyderDockablePlugin API # ------------------------------------------------------------------------ - def get_name(self): + @staticmethod + def get_name(): return _("Breakpoints") def get_description(self): diff --git a/spyder/plugins/completion/plugin.py b/spyder/plugins/completion/plugin.py index bd985aab521..03586a4b91f 100644 --- a/spyder/plugins/completion/plugin.py +++ b/spyder/plugins/completion/plugin.py @@ -254,7 +254,8 @@ def __init__(self, parent, configuration=None): self.ADDITIONAL_CONF_TABS = {'completions': conf_tabs} # ---------------- Public Spyder API required methods --------------------- - def get_name(self) -> str: + @staticmethod + def get_name() -> str: return _('Completion and linting') def get_description(self) -> str: diff --git a/spyder/plugins/console/plugin.py b/spyder/plugins/console/plugin.py index 7efde26f2c8..32c9d2a6443 100644 --- a/spyder/plugins/console/plugin.py +++ b/spyder/plugins/console/plugin.py @@ -76,7 +76,8 @@ class Console(SpyderDockablePlugin): # --- SpyderDockablePlugin API # ------------------------------------------------------------------------ - def get_name(self): + @staticmethod + def get_name(): return _('Internal console') def get_icon(self): diff --git a/spyder/plugins/editor/plugin.py b/spyder/plugins/editor/plugin.py index e4bf9cad6ad..6f2db0eafdb 100644 --- a/spyder/plugins/editor/plugin.py +++ b/spyder/plugins/editor/plugin.py @@ -416,8 +416,11 @@ def _rpc_call(self, method, args, kwargs): meth(*args, **kwargs) #------ SpyderPluginWidget API --------------------------------------------- - def get_plugin_title(self): + @staticmethod + def get_plugin_title(): """Return widget title""" + # TODO: This is a temporary measure to get the title of the plugins + # without creating an instance title = _('Editor') return title diff --git a/spyder/plugins/explorer/plugin.py b/spyder/plugins/explorer/plugin.py index fd82c7229bf..c643e360bc3 100644 --- a/spyder/plugins/explorer/plugin.py +++ b/spyder/plugins/explorer/plugin.py @@ -153,7 +153,8 @@ class Explorer(SpyderDockablePlugin): # ---- SpyderDockablePlugin API # ------------------------------------------------------------------------ - def get_name(self): + @staticmethod + def get_name(): """Return widget title""" return _("Files") diff --git a/spyder/plugins/findinfiles/plugin.py b/spyder/plugins/findinfiles/plugin.py index d3ceb8aab8d..2615203c486 100644 --- a/spyder/plugins/findinfiles/plugin.py +++ b/spyder/plugins/findinfiles/plugin.py @@ -46,7 +46,8 @@ class FindInFiles(SpyderDockablePlugin): # --- SpyderDocakblePlugin API # ------------------------------------------------------------------------ - def get_name(self): + @staticmethod + def get_name(): return _("Find") def get_description(self): diff --git a/spyder/plugins/help/plugin.py b/spyder/plugins/help/plugin.py index 8736bfc8a4c..62b64527acd 100644 --- a/spyder/plugins/help/plugin.py +++ b/spyder/plugins/help/plugin.py @@ -63,7 +63,8 @@ class Help(SpyderDockablePlugin): # --- SpyderDocakblePlugin API # ----------------------------------------------------------------------- - def get_name(self): + @staticmethod + def get_name(): return _('Help') def get_description(self): diff --git a/spyder/plugins/history/plugin.py b/spyder/plugins/history/plugin.py index c3b483a71aa..b8d50b78981 100644 --- a/spyder/plugins/history/plugin.py +++ b/spyder/plugins/history/plugin.py @@ -46,7 +46,8 @@ class HistoryLog(SpyderDockablePlugin): # --- SpyderDockablePlugin API # ------------------------------------------------------------------------ - def get_name(self): + @staticmethod + def get_name(): return _('History') def get_description(self): diff --git a/spyder/plugins/ipythonconsole/plugin.py b/spyder/plugins/ipythonconsole/plugin.py index 6a7e3503c7b..7231fd8c400 100644 --- a/spyder/plugins/ipythonconsole/plugin.py +++ b/spyder/plugins/ipythonconsole/plugin.py @@ -520,8 +520,11 @@ def toggle_view(self, checked): self.dockwidget.hide() #------ SpyderPluginWidget API -------------------------------------------- - def get_plugin_title(self): + @staticmethod + def get_plugin_title(): """Return widget title""" + # TODO: This is a temporary measure to get the title of the plugins + # without creating an instance return _('IPython console') def get_plugin_icon(self): @@ -760,7 +763,7 @@ def register_plugin(self): self.main.sig_pythonpath_changed.connect(self.update_path) # Show history file if no console is visible - if not self._isvisible and self.main.historylog: + if not self._isvisible and hasattr(self.main, 'historylog'): self.main.historylog.add_history(get_conf_path('history.py')) #------ Public API (for clients) ------------------------------------------ diff --git a/spyder/plugins/layout/plugin.py b/spyder/plugins/layout/plugin.py index e52b96a19c7..c2abf8e7066 100644 --- a/spyder/plugins/layout/plugin.py +++ b/spyder/plugins/layout/plugin.py @@ -72,7 +72,8 @@ class Layout(SpyderPluginV2): # --- SpyderDockablePlugin API # ------------------------------------------------------------------------ - def get_name(self): + @staticmethod + def get_name(): return _("Layout") def get_description(self): diff --git a/spyder/plugins/maininterpreter/plugin.py b/spyder/plugins/maininterpreter/plugin.py index c43748b630d..91ad3be6949 100644 --- a/spyder/plugins/maininterpreter/plugin.py +++ b/spyder/plugins/maininterpreter/plugin.py @@ -42,7 +42,8 @@ class MainInterpreter(SpyderPluginV2): CONF_FILE = False # ---- SpyderPluginV2 API - def get_name(self): + @staticmethod + def get_name(): return _("Python interpreter") def get_description(self): diff --git a/spyder/plugins/mainmenu/plugin.py b/spyder/plugins/mainmenu/plugin.py index 452009cca15..9b97421645a 100644 --- a/spyder/plugins/mainmenu/plugin.py +++ b/spyder/plugins/mainmenu/plugin.py @@ -40,7 +40,8 @@ class MainMenu(SpyderPluginV2): CONF_SECTION = NAME CONF_FILE = False - def get_name(self): + @staticmethod + def get_name(): return _('Main menus') def get_icon(self): diff --git a/spyder/plugins/onlinehelp/plugin.py b/spyder/plugins/onlinehelp/plugin.py index ba9dd218574..664c7fab4fd 100644 --- a/spyder/plugins/onlinehelp/plugin.py +++ b/spyder/plugins/onlinehelp/plugin.py @@ -45,7 +45,8 @@ class OnlineHelp(SpyderDockablePlugin): # --- SpyderDockablePlugin API # ------------------------------------------------------------------------ - def get_name(self): + @staticmethod + def get_name(): return _('Online help') def get_description(self): diff --git a/spyder/plugins/outlineexplorer/plugin.py b/spyder/plugins/outlineexplorer/plugin.py index 2b159d6cad1..3839f01e0a5 100644 --- a/spyder/plugins/outlineexplorer/plugin.py +++ b/spyder/plugins/outlineexplorer/plugin.py @@ -31,7 +31,8 @@ class OutlineExplorer(SpyderDockablePlugin): # ---- SpyderDockablePlugin API # ------------------------------------------------------------------------ - def get_name(self) -> str: + @staticmethod + def get_name() -> str: """Return widget title.""" return _('Outline Explorer') diff --git a/spyder/plugins/plots/plugin.py b/spyder/plugins/plots/plugin.py index 96b070d4222..629de029fc0 100644 --- a/spyder/plugins/plots/plugin.py +++ b/spyder/plugins/plots/plugin.py @@ -37,7 +37,8 @@ class Plots(SpyderDockablePlugin): # ---- SpyderDockablePlugin API # ------------------------------------------------------------------------ - def get_name(self): + @staticmethod + def get_name(): return _('Plots') def get_description(self): diff --git a/spyder/plugins/preferences/plugin.py b/spyder/plugins/preferences/plugin.py index 7122b74df8c..44370d58215 100644 --- a/spyder/plugins/preferences/plugin.py +++ b/spyder/plugins/preferences/plugin.py @@ -257,7 +257,8 @@ def open_dialog(self, prefs_dialog_size): self.get_main()) # ---------------- Public Spyder API required methods --------------------- - def get_name(self) -> str: + @staticmethod + def get_name() -> str: return _('Preferences') def get_description(self) -> str: diff --git a/spyder/plugins/profiler/plugin.py b/spyder/plugins/profiler/plugin.py index 8bcd5fab05a..e19bc89209d 100644 --- a/spyder/plugins/profiler/plugin.py +++ b/spyder/plugins/profiler/plugin.py @@ -62,7 +62,8 @@ class Profiler(SpyderDockablePlugin): # --- SpyderDockablePlugin API # ------------------------------------------------------------------------ - def get_name(self): + @staticmethod + def get_name(): return _("Profiler") def get_description(self): diff --git a/spyder/plugins/projects/plugin.py b/spyder/plugins/projects/plugin.py index 56a923fdf2e..3188ace10f6 100644 --- a/spyder/plugins/projects/plugin.py +++ b/spyder/plugins/projects/plugin.py @@ -138,7 +138,8 @@ def __init__(self, parent=None, configuration=None): # ---- SpyderDockablePlugin API # ------------------------------------------------------------------------ - def get_name(self): + @staticmethod + def get_name(): return _("Project") def get_description(self): diff --git a/spyder/plugins/pylint/plugin.py b/spyder/plugins/pylint/plugin.py index 6307c162910..1adf79faeee 100644 --- a/spyder/plugins/pylint/plugin.py +++ b/spyder/plugins/pylint/plugin.py @@ -62,7 +62,8 @@ class Pylint(SpyderDockablePlugin): Word to select on given row. """ - def get_name(self): + @staticmethod + def get_name(): return _("Code Analysis") def get_description(self): diff --git a/spyder/plugins/run/plugin.py b/spyder/plugins/run/plugin.py index 3d0c0e42d30..c8f196991ab 100644 --- a/spyder/plugins/run/plugin.py +++ b/spyder/plugins/run/plugin.py @@ -38,7 +38,8 @@ class Run(SpyderPluginV2): # --- SpyderPluginV2 API # ------------------------------------------------------------------------ - def get_name(self): + @staticmethod + def get_name(): return _("Run") def get_description(self): diff --git a/spyder/plugins/shortcuts/plugin.py b/spyder/plugins/shortcuts/plugin.py index 3e347f1d685..360ddd25380 100644 --- a/spyder/plugins/shortcuts/plugin.py +++ b/spyder/plugins/shortcuts/plugin.py @@ -61,7 +61,8 @@ class Shortcuts(SpyderPluginV2): # --- SpyderPluginV2 API # ------------------------------------------------------------------------ - def get_name(self): + @staticmethod + def get_name(): return _("Keyboard shortcuts") def get_description(self): diff --git a/spyder/plugins/statusbar/plugin.py b/spyder/plugins/statusbar/plugin.py index db87cbe708c..1f38affb8fc 100644 --- a/spyder/plugins/statusbar/plugin.py +++ b/spyder/plugins/statusbar/plugin.py @@ -52,7 +52,8 @@ class StatusBar(SpyderPluginV2): 'vcs_status', 'interpreter_status', 'lsp_status', 'kite_status'} # ---- SpyderPluginV2 API - def get_name(self): + @staticmethod + def get_name(): return _('Status bar') def get_icon(self): diff --git a/spyder/plugins/toolbar/plugin.py b/spyder/plugins/toolbar/plugin.py index 5e7f688b0e0..b7e796ed0e5 100644 --- a/spyder/plugins/toolbar/plugin.py +++ b/spyder/plugins/toolbar/plugin.py @@ -42,7 +42,8 @@ class Toolbar(SpyderPluginV2): # --- SpyderDocakblePlugin API # ----------------------------------------------------------------------- - def get_name(self): + @staticmethod + def get_name(): return _('Toolbar') def get_description(self): diff --git a/spyder/plugins/tours/plugin.py b/spyder/plugins/tours/plugin.py index f413b077a20..59fbfe68927 100644 --- a/spyder/plugins/tours/plugin.py +++ b/spyder/plugins/tours/plugin.py @@ -39,7 +39,8 @@ class Tours(SpyderPluginV2): # --- SpyderPluginV2 API # ------------------------------------------------------------------------ - def get_name(self): + @staticmethod + def get_name(): return _("Interactive tours") def get_description(self): diff --git a/spyder/plugins/variableexplorer/plugin.py b/spyder/plugins/variableexplorer/plugin.py index e4ad4d8d281..ddc79c882eb 100644 --- a/spyder/plugins/variableexplorer/plugin.py +++ b/spyder/plugins/variableexplorer/plugin.py @@ -38,7 +38,8 @@ class VariableExplorer(SpyderDockablePlugin): # ---- SpyderDockablePlugin API # ------------------------------------------------------------------------ - def get_name(self): + @staticmethod + def get_name(): return _('Variable explorer') def get_description(self): diff --git a/spyder/plugins/workingdirectory/plugin.py b/spyder/plugins/workingdirectory/plugin.py index cdf08fd5888..0bd75855856 100644 --- a/spyder/plugins/workingdirectory/plugin.py +++ b/spyder/plugins/workingdirectory/plugin.py @@ -59,7 +59,8 @@ class WorkingDirectory(SpyderPluginV2): # --- SpyderPluginV2 API # ------------------------------------------------------------------------ - def get_name(self): + @staticmethod + def get_name(): return _('Current working directory') def get_description(self): diff --git a/spyder/utils/icon_manager.py b/spyder/utils/icon_manager.py index 11c404c7014..8bc7a153594 100644 --- a/spyder/utils/icon_manager.py +++ b/spyder/utils/icon_manager.py @@ -334,6 +334,8 @@ def __init__(self): # --- Status bar -------------------------------------------------------- 'code_fork': [('mdi.source-fork',), {'color': self.MAIN_FG_COLOR}], 'statusbar': [('mdi.dock-bottom',), {'color': self.MAIN_FG_COLOR}], + # --- Plugin registry --------------------------------------------------- + 'plugins': [('mdi.puzzle',), {'color': self.MAIN_FG_COLOR}], } def get_std_icon(self, name, size=None): From bff2395f4ecf18b4546189bc566094f4f1188450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:14 -0500 Subject: [PATCH 63/68] Restart Spyder after a plugin is enabled/disabled --- spyder/api/plugin_registration/confpage.py | 19 ++-- spyder/api/plugin_registration/registry.py | 15 +-- spyder/app/mainwindow.py | 32 ++++-- spyder/plugins/application/plugin.py | 4 +- spyder/plugins/preferences/api.py | 116 ++++++++++++--------- 5 files changed, 113 insertions(+), 73 deletions(-) diff --git a/spyder/api/plugin_registration/confpage.py b/spyder/api/plugin_registration/confpage.py index 0b8d6b04f2a..87b7460ac53 100644 --- a/spyder/api/plugin_registration/confpage.py +++ b/spyder/api/plugin_registration/confpage.py @@ -49,7 +49,7 @@ def setup_page(self): plugin_loc_name = PluginClass.get_name() plugin_state = CONF.get(conf_section_name, 'enable', True) cb = newcb(plugin_loc_name, 'enable', default=True, - section=conf_section_name) + section=conf_section_name, restart=True) internal_layout.addWidget(cb, i // 2, i % 2) self.plugins_checkboxes[plugin_name] = (cb, plugin_state) i += 1 @@ -70,8 +70,8 @@ def setup_page(self): continue plugin_loc_name = PluginClass.get_name() - cb = newcb(_('Enable {0} plugin').format(plugin_loc_name), - 'enable', default=True, section=conf_section_name) + 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 @@ -101,8 +101,15 @@ def apply_settings(self): PluginClass) = self.plugin.all_external_plugins[plugin_name] external = True - self.plugin.register_plugin(self.main, PluginClass, - external=external) + # 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: - self.plugin.delete_plugin(plugin_name) + # 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({}) diff --git a/spyder/api/plugin_registration/registry.py b/spyder/api/plugin_registration/registry.py index f35cb4b91f1..4b4e81b8306 100644 --- a/spyder/api/plugin_registration/registry.py +++ b/spyder/api/plugin_registration/registry.py @@ -465,7 +465,8 @@ def delete_plugin(self, plugin_name: str) -> bool: self.plugin_registry.pop(plugin_name) return True - def delete_all_plugins(self, excluding: Optional[Set[str]] = None) -> bool: + def delete_all_plugins(self, excluding: Optional[Set[str]] = None, + close_immediately: bool = False) -> bool: """ Remove all plugins from the registry. @@ -477,6 +478,8 @@ def delete_all_plugins(self, excluding: Optional[Set[str]] = None) -> bool: ---------- excluding: Optional[Set[str]] A set that lists plugins (by name) that will not be deleted. + close_immediately: bool + If true, then the `can_close` status will be ignored. Returns ------- @@ -492,7 +495,7 @@ def delete_all_plugins(self, excluding: Optional[Set[str]] = None) -> bool: plugin_instance = self.plugin_registry[plugin_name] if isinstance(plugin_instance, SpyderPlugin): can_close &= self.delete_plugin(plugin_name) - if not can_close: + if not can_close and not close_immediately: break if not can_close: @@ -504,10 +507,10 @@ def delete_all_plugins(self, excluding: Optional[Set[str]] = None) -> bool: plugin_instance = self.plugin_registry[plugin_name] if isinstance(plugin_instance, SpyderPluginV2): can_close &= self.delete_plugin(plugin_name) - if not can_close: + if not can_close and not close_immediately: break - if not can_close: + if not can_close and not close_immediately: return False # Delete Spyder 4 internal plugins @@ -516,7 +519,7 @@ def delete_all_plugins(self, excluding: Optional[Set[str]] = None) -> bool: plugin_instance = self.plugin_registry[plugin_name] if isinstance(plugin_instance, SpyderPlugin): can_close &= self.delete_plugin(plugin_name) - if not can_close: + if not can_close and not close_immediately: break if not can_close: @@ -527,7 +530,7 @@ def delete_all_plugins(self, excluding: Optional[Set[str]] = None) -> bool: plugin_instance = self.plugin_registry[plugin_name] if isinstance(plugin_instance, SpyderPluginV2): can_close &= self.delete_plugin(plugin_name) - if not can_close: + if not can_close and not close_immediately: break return can_close diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py index 68de164bca9..542b101f7c8 100644 --- a/spyder/app/mainwindow.py +++ b/spyder/app/mainwindow.py @@ -1474,10 +1474,9 @@ def moveEvent(self, event): if hasattr(self, 'layouts'): if not self.isMaximized() and not self.layouts.get_fullscreen_flag(): self.window_position = self.pos() - QMainWindow.moveEvent(self, event) - - # To be used by the tour to be able to move - self.sig_moved.emit(event) + QMainWindow.moveEvent(self, event) + # To be used by the tour to be able to move + self.sig_moved.emit(event) def hideEvent(self, event): """Reimplement Qt method""" @@ -1508,7 +1507,7 @@ def change_last_focused_widget(self, old, now): self.previous_focused_widget = old - def closing(self, cancelable=False): + def closing(self, cancelable=False, close_immediately=False): """Exit tasks""" if self.already_closed or self.is_starting_up: return True @@ -1524,9 +1523,9 @@ def closing(self, cancelable=False): self.open_files_server.close() can_close = PLUGIN_REGISTRY.delete_all_plugins( - excluding={Plugins.Layout}) + excluding={Plugins.Layout}, close_immediately=close_immediately) - if not can_close: + if not can_close and not close_immediately: return False # Save window settings *after* closing all plugin windows, in order @@ -1827,6 +1826,25 @@ def start_open_files_server(self): self.sig_open_external_file.emit(fname) req.sendall(b' ') + # ---- Quit and restart, and reset spyder defaults + @Slot() + def reset_spyder(self): + """ + Quit and reset Spyder and then Restart application. + """ + answer = QMessageBox.warning(self, _("Warning"), + _("Spyder will restart and reset to default settings:

" + "Do you want to continue?"), + QMessageBox.Yes | QMessageBox.No) + if answer == QMessageBox.Yes: + self.restart(reset=True) + + @Slot() + def restart(self, reset=False, close_immediately=False): + """Wrapper to handle plugins request to restart Spyder.""" + self.application.restart( + reset=reset, close_immediately=close_immediately) + # ---- Global Switcher def open_switcher(self, symbol=False): """Open switcher dialog box.""" diff --git a/spyder/plugins/application/plugin.py b/spyder/plugins/application/plugin.py index 2f7918f3c6b..ac5ba0df33c 100644 --- a/spyder/plugins/application/plugin.py +++ b/spyder/plugins/application/plugin.py @@ -327,7 +327,7 @@ def apply_settings(self): self._main.apply_settings() @Slot() - def restart(self): + def restart(self, reset=False, close_immediately=False): """ Quit and Restart Spyder application. @@ -383,7 +383,7 @@ def restart(self): command = command.format(python, restart_script) try: - if self.main.closing(True): + if self.main.closing(True, close_immediately=close_immediately): subprocess.Popen(command, shell=shell, env=env, startupinfo=startupinfo) console.quit() diff --git a/spyder/plugins/preferences/api.py b/spyder/plugins/preferences/api.py index 23bdaa32a3b..cd266acaa6c 100644 --- a/spyder/plugins/preferences/api.py +++ b/spyder/plugins/preferences/api.py @@ -10,6 +10,7 @@ # Standard library imports import ast +import functools import os.path as osp # Third party imports @@ -211,25 +212,27 @@ def load_from_conf(self): """Load settings from configuration file.""" for checkbox, (sec, option, default) in list(self.checkboxes.items()): checkbox.setChecked(self.get_option(option, default, section=sec)) - checkbox.clicked.connect(lambda _, opt=option: - self.has_been_modified(opt)) + checkbox.clicked.connect(lambda _, opt=option, sect=sec: + self.has_been_modified(sect, opt)) + if checkbox.restart_required: + self.restart_options[(sec, option)] = checkbox.text() for radiobutton, (sec, option, default) in list( self.radiobuttons.items()): radiobutton.setChecked(self.get_option(option, default, section=sec)) - radiobutton.toggled.connect(lambda _foo, opt=option: - self.has_been_modified(opt)) + radiobutton.toggled.connect(lambda _foo, opt=option, sect=sec: + self.has_been_modified(sect, opt)) if radiobutton.restart_required: - self.restart_options[option] = radiobutton.label_text + self.restart_options[(sec, option)] = radiobutton.label_text for lineedit, (sec, option, default) in list(self.lineedits.items()): data = self.get_option(option, default, section=sec) if getattr(lineedit, 'content_type', None) == list: data = ', '.join(data) lineedit.setText(data) - lineedit.textChanged.connect(lambda _, opt=option: - self.has_been_modified(opt)) + lineedit.textChanged.connect(lambda _, opt=option, sect=sec: + self.has_been_modified(sect, opt)) if lineedit.restart_required: - self.restart_options[option] = lineedit.label_text + self.restart_options[(sec, option)] = lineedit.label_text for textedit, (sec, option, default) in list(self.textedits.items()): data = self.get_option(option, default, section=sec) if getattr(textedit, 'content_type', None) == list: @@ -237,14 +240,14 @@ def load_from_conf(self): elif getattr(textedit, 'content_type', None) == dict: data = to_text_string(data) textedit.setPlainText(data) - textedit.textChanged.connect(lambda opt=option: - self.has_been_modified(opt)) + textedit.textChanged.connect(lambda opt=option, sect=sec: + self.has_been_modified(sect, opt)) if textedit.restart_required: - self.restart_options[option] = textedit.label_text + self.restart_options[(sec, option)] = textedit.label_text for spinbox, (sec, option, default) in list(self.spinboxes.items()): spinbox.setValue(self.get_option(option, default, section=sec)) - spinbox.valueChanged.connect(lambda _foo, opt=option: - self.has_been_modified(opt)) + spinbox.valueChanged.connect(lambda _foo, opt=option, sect=sec: + self.has_been_modified(sect, opt)) for combobox, (sec, option, default) in list(self.comboboxes.items()): value = self.get_option(option, default, section=sec) for index in range(combobox.count()): @@ -259,10 +262,11 @@ def load_from_conf(self): index = None if index: combobox.setCurrentIndex(index) - combobox.currentIndexChanged.connect(lambda _foo, opt=option: - self.has_been_modified(opt)) + combobox.currentIndexChanged.connect(lambda _foo, opt=option, sect=sec: + self.has_been_modified( + sect, opt)) if combobox.restart_required: - self.restart_options[option] = combobox.label_text + self.restart_options[(sec, option)] = combobox.label_text for (fontbox, sizebox), option in list(self.fontboxes.items()): rich_font = True if "rich" in option.lower() else False @@ -274,9 +278,11 @@ def load_from_conf(self): else: property = option fontbox.currentIndexChanged.connect(lambda _foo, opt=property: - self.has_been_modified(opt)) + self.has_been_modified( + self.CONF_SECTION, opt)) sizebox.valueChanged.connect(lambda _foo, opt=property: - self.has_been_modified(opt)) + self.has_been_modified( + self.CONF_SECTION, opt)) for clayout, (sec, option, default) in list(self.coloredits.items()): property = to_qvariant(option) edit = clayout.lineedit @@ -284,13 +290,13 @@ def load_from_conf(self): edit.setText(self.get_option(option, default, section=sec)) # QAbstractButton works differently for PySide and PyQt if not API == 'pyside': - btn.clicked.connect(lambda _foo, opt=option: - self.has_been_modified(opt)) + btn.clicked.connect(lambda _foo, opt=option, sect=sec: + self.has_been_modified(sect, opt)) else: - btn.clicked.connect(lambda opt=option: - self.has_been_modified(opt)) - edit.textChanged.connect(lambda _foo, opt=option: - self.has_been_modified(opt)) + btn.clicked.connect(lambda opt=option, sect=sec: + self.has_been_modified(sect, opt)) + edit.textChanged.connect(lambda _foo, opt=option, sect=sec: + self.has_been_modified(sect, opt)) for (clayout, cb_bold, cb_italic ), (sec, option, default) in list(self.scedits.items()): edit = clayout.lineedit @@ -302,39 +308,39 @@ def load_from_conf(self): cb_bold.setChecked(bold) cb_italic.setChecked(italic) - edit.textChanged.connect(lambda _foo, opt=option: - self.has_been_modified(opt)) + edit.textChanged.connect(lambda _foo, opt=option, sect=sec: + self.has_been_modified(sect, opt)) # QAbstractButton works differently for PySide and PyQt if not API == 'pyside': - btn.clicked.connect(lambda _foo, opt=option: - self.has_been_modified(opt)) - cb_bold.clicked.connect(lambda _foo, opt=option: - self.has_been_modified(opt)) - cb_italic.clicked.connect(lambda _foo, opt=option: - self.has_been_modified(opt)) + btn.clicked.connect(lambda _foo, opt=option, sect=sec: + self.has_been_modified(sect, opt)) + cb_bold.clicked.connect(lambda _foo, opt=option, sect=sec: + self.has_been_modified(sect, opt)) + cb_italic.clicked.connect(lambda _foo, opt=option, sect=sec: + self.has_been_modified(sect, opt)) else: - btn.clicked.connect(lambda opt=option: - self.has_been_modified(opt)) - cb_bold.clicked.connect(lambda opt=option: - self.has_been_modified(opt)) - cb_italic.clicked.connect(lambda opt=option: - self.has_been_modified(opt)) + btn.clicked.connect(lambda opt=option, sect=sec: + self.has_been_modified(sect, opt)) + cb_bold.clicked.connect(lambda opt=option, sect=sec: + self.has_been_modified(sect, opt)) + cb_italic.clicked.connect(lambda opt=option, sect=sec: + self.has_been_modified(sect, opt)) def save_to_conf(self): """Save settings to configuration file""" for checkbox, (sec, option, _default) in list( self.checkboxes.items()): - if option in self.changed_options: + if (sec, option) in self.changed_options: value = checkbox.isChecked() self.set_option(option, value, section=sec, recursive_notification=False) for radiobutton, (sec, option, _default) in list( self.radiobuttons.items()): - if option in self.changed_options: + if (sec, option) in self.changed_options: self.set_option(option, radiobutton.isChecked(), section=sec, recursive_notification=False) for lineedit, (sec, option, _default) in list(self.lineedits.items()): - if option in self.changed_options: + if (sec, option) in self.changed_options: data = lineedit.text() content_type = getattr(lineedit, 'content_type', None) if content_type == list: @@ -344,7 +350,7 @@ def save_to_conf(self): self.set_option(option, data, section=sec, recursive_notification=False) for textedit, (sec, option, _default) in list(self.textedits.items()): - if option in self.changed_options: + if (sec, option) in self.changed_options: data = textedit.toPlainText() content_type = getattr(textedit, 'content_type', None) if content_type == dict: @@ -359,27 +365,27 @@ def save_to_conf(self): self.set_option(option, data, section=sec, recursive_notification=False) for spinbox, (sec, option, _default) in list(self.spinboxes.items()): - if option in self.changed_options: + if (sec, option) in self.changed_options: self.set_option(option, spinbox.value(), section=sec, recursive_notification=False) for combobox, (sec, option, _default) in list(self.comboboxes.items()): - if option in self.changed_options: + if (sec, option) in self.changed_options: data = combobox.itemData(combobox.currentIndex()) self.set_option(option, from_qvariant(data, to_text_string), section=sec, recursive_notification=False) for (fontbox, sizebox), option in list(self.fontboxes.items()): - if option in self.changed_options: + if (self.CONF_SECTION, option) in self.changed_options: font = fontbox.currentFont() font.setPointSize(sizebox.value()) self.set_font(font, option) for clayout, (sec, option, _default) in list(self.coloredits.items()): - if option in self.changed_options: + if (sec, option) in self.changed_options: self.set_option(option, to_text_string(clayout.lineedit.text()), section=sec, recursive_notification=False) for (clayout, cb_bold, cb_italic), (sec, option, _default) in list( self.scedits.items()): - if option in self.changed_options: + if (sec, option) in self.changed_options: color = to_text_string(clayout.lineedit.text()) bold = cb_bold.isChecked() italic = cb_italic.isChecked() @@ -387,13 +393,13 @@ def save_to_conf(self): recursive_notification=False) @Slot(str) - def has_been_modified(self, option): + def has_been_modified(self, section, option): self.set_modified(True) - self.changed_options.add(option) + self.changed_options.add((section, option)) def create_checkbox(self, text, option, default=NoDefault, tip=None, msg_warning=None, msg_info=None, - msg_if_enabled=False, section=None): + msg_if_enabled=False, section=None, restart=False): checkbox = QCheckBox(text) self.checkboxes[checkbox] = (section, option, default) if section is not None and section != self.CONF_SECTION: @@ -410,6 +416,7 @@ def show_message(is_checked=False): QMessageBox.information(self, self.get_name(), msg_info, QMessageBox.Ok) checkbox.clicked.connect(show_message) + checkbox.restart_required = restart return checkbox def create_radiobutton(self, text, option, default=NoDefault, @@ -773,7 +780,8 @@ def create_button(self, text, callback): btn = QPushButton(text) btn.clicked.connect(callback) btn.clicked.connect( - lambda checked=False, opt='': self.has_been_modified(opt)) + lambda checked=False, opt='': self.has_been_modified( + self.CONF_SECTION, opt)) return btn def create_tab(self, *widgets): @@ -809,7 +817,11 @@ def prompt_restart_required(self): answer = QMessageBox.information(self, msg_title, msg, QMessageBox.Yes | QMessageBox.No) if answer == QMessageBox.Yes: - self.main.application.sig_restart_requested.emit() + self.restart() + + def restart(self): + """Restart Spyder.""" + self.main.restart(close_immediately=True) def add_tab(self, Widget): widget = Widget(self) From d01d43b9a4d6d6bcc02e5d0eca5d57d1f48df831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:14 -0500 Subject: [PATCH 64/68] Disable also Spyder 4 plugins --- spyder/api/plugin_registration/confpage.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/spyder/api/plugin_registration/confpage.py b/spyder/api/plugin_registration/confpage.py index 87b7460ac53..16fe42077af 100644 --- a/spyder/api/plugin_registration/confpage.py +++ b/spyder/api/plugin_registration/confpage.py @@ -38,15 +38,16 @@ def setup_page(self): (conf_section_name, PluginClass) = self.plugin.all_internal_plugins[plugin_name] - if issubclass(PluginClass, SpyderPlugin): - # Do not add Spyder 4 plugins to the disable page - continue - - if not PluginClass.CAN_BE_DISABLED: + if not getattr(PluginClass, 'CAN_BE_DISABLED', True): # Do not list core plugins that can not be disabled continue - plugin_loc_name = PluginClass.get_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() + plugin_state = CONF.get(conf_section_name, 'enable', True) cb = newcb(plugin_loc_name, 'enable', default=True, section=conf_section_name, restart=True) @@ -65,11 +66,12 @@ def setup_page(self): (conf_section_name, PluginClass) = self.plugin.all_external_plugins[plugin_name] - if issubclass(PluginClass, SpyderPlugin): - # Do not add Spyder 4 plugins to the disable page - 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_loc_name = PluginClass.get_name() cb = newcb(plugin_loc_name, 'enable', default=True, section=conf_section_name, restart=True) external_layout.addWidget(cb, i // 2, i % 2) From ca21fff48b330a6879796ddcec8a9bd2f0e37986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 12:02:14 -0500 Subject: [PATCH 65/68] Fix test_preferences_checkboxes_not_checked_regression --- spyder/api/preferences.py | 4 ++++ spyder/plugins/completion/plugin.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/spyder/api/preferences.py b/spyder/api/preferences.py index bca573fdde6..a9544a75f53 100644 --- a/spyder/api/preferences.py +++ b/spyder/api/preferences.py @@ -128,6 +128,10 @@ def aggregate_sections_partials(self, opts): """Aggregate options by sections in order to notify observers.""" to_update = {} for opt in opts: + if isinstance(opt, tuple): + if len(opt) == 2 and opt[0] is None: + opt = opt[1] + section = self.CONF_SECTION if opt in self.cross_section_options: section = self.cross_section_options[opt] diff --git a/spyder/plugins/completion/plugin.py b/spyder/plugins/completion/plugin.py index 03586a4b91f..8e7cb79834a 100644 --- a/spyder/plugins/completion/plugin.py +++ b/spyder/plugins/completion/plugin.py @@ -388,6 +388,8 @@ def after_configuration_update(self, options: List[Union[tuple, str]]): provider tabs. """ providers_to_update = set({}) + options = [x[1] if isinstance(x, tuple) and + len(x) == 2 and x[0] is None else x for x in options] for option in options: if option == 'completions_wait_for_ms': self.wait_for_ms = self.get_conf( From 8361e147340dffd4e3adcf99f119cf6b39a7500b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 5 Oct 2021 13:14:07 -0500 Subject: [PATCH 66/68] Address review comments --- .../{confpage.py => _confpage.py} | 14 +++++++------- spyder/api/plugin_registration/registry.py | 10 +++++----- spyder/api/preferences.py | 2 ++ spyder/app/mainwindow.py | 9 --------- spyder/plugins/editor/plugin.py | 2 +- spyder/plugins/ipythonconsole/plugin.py | 2 +- 6 files changed, 16 insertions(+), 23 deletions(-) rename spyder/api/plugin_registration/{confpage.py => _confpage.py} (92%) diff --git a/spyder/api/plugin_registration/confpage.py b/spyder/api/plugin_registration/_confpage.py similarity index 92% rename from spyder/api/plugin_registration/confpage.py rename to spyder/api/plugin_registration/_confpage.py index 16fe42077af..9da92ecf3e5 100644 --- a/spyder/api/plugin_registration/confpage.py +++ b/spyder/api/plugin_registration/_confpage.py @@ -4,17 +4,17 @@ # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) -"""Spyder completion plugin configuration page.""" +"""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 -from spyder.api.preferences import PluginConfigPage -from spyder.api.plugins import SpyderPlugin class PluginsConfigPage(PluginConfigPage): @@ -23,10 +23,10 @@ def setup_page(self): self.plugins_checkboxes = {} header_label = QLabel( - _("Spyder can run with a reduced number of internal and external " - "plugins in order to provide a lighter experience. Any plugin " - "unchecked in this page will be unloaded immediately and will " - "not be loaded next time Spyder starts.")) + _("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 --------------------- diff --git a/spyder/api/plugin_registration/registry.py b/spyder/api/plugin_registration/registry.py index 4b4e81b8306..c0f377a4fba 100644 --- a/spyder/api/plugin_registration/registry.py +++ b/spyder/api/plugin_registration/registry.py @@ -8,7 +8,7 @@ # Standard library imports import logging -from typing import Dict, List, Union, Type, Any, Set, Optional +from typing import Dict, List, Union, Type, Any, Set, Optional, Tuple # Third-party library imports from qtpy.QtCore import QObject, Signal @@ -18,7 +18,7 @@ from spyder.config.base import _, running_under_pytest from spyder.config.manager import CONF from spyder.api.config.mixins import SpyderConfigurationAccessor -from spyder.api.plugin_registration.confpage import PluginsConfigPage +from spyder.api.plugin_registration._confpage import PluginsConfigPage from spyder.api.plugins.enum import Plugins from spyder.api.exceptions import SpyderAPIError from spyder.api.plugins import ( @@ -90,6 +90,7 @@ def __init__(self): super().__init__() PreferencesAdapter.__init__(self) + # Reference to the main window self.main = None # Dictionary that maps a plugin name to a list of the plugin names @@ -208,10 +209,9 @@ def _instantiate_spyder5_plugin( module = PluginClass._spyder_module_name package_name = PluginClass._spyder_package_name version = PluginClass._spyder_version - description = instance.get_description() + description = plugin_instance.get_description() dependencies.add(module, package_name, description, - version, None, - kind=dependencies.PLUGIN) + version, None, kind=dependencies.PLUGIN) return plugin_instance diff --git a/spyder/api/preferences.py b/spyder/api/preferences.py index a9544a75f53..70602e81d4b 100644 --- a/spyder/api/preferences.py +++ b/spyder/api/preferences.py @@ -129,6 +129,8 @@ def aggregate_sections_partials(self, opts): to_update = {} for opt in opts: if isinstance(opt, tuple): + # This is necessary to filter tuple options that do not + # belong to a section. if len(opt) == 2 and opt[0] is None: opt = opt[1] diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py index 542b101f7c8..ef1c26c3105 100644 --- a/spyder/app/mainwindow.py +++ b/spyder/app/mainwindow.py @@ -909,15 +909,6 @@ def setup(self): try: plugin_instance = PLUGIN_REGISTRY.register_plugin( self, PluginClass, external=True) - - # These attributes come from spyder.app.find_plugins to - # add plugins to the dependencies dialog - module = PluginClass._spyder_module_name - package_name = PluginClass._spyder_package_name - version = PluginClass._spyder_version - description = plugin_instance.get_description() - dependencies.add(module, package_name, description, - version, None, kind=dependencies.PLUGIN) except Exception as error: print("%s: %s" % (PluginClass, str(error)), file=STDERR) traceback.print_exc(file=STDERR) diff --git a/spyder/plugins/editor/plugin.py b/spyder/plugins/editor/plugin.py index 6f2db0eafdb..b594cba43a9 100644 --- a/spyder/plugins/editor/plugin.py +++ b/spyder/plugins/editor/plugin.py @@ -419,7 +419,7 @@ def _rpc_call(self, method, args, kwargs): @staticmethod def get_plugin_title(): """Return widget title""" - # TODO: This is a temporary measure to get the title of the plugins + # TODO: This is a temporary measure to get the title of this plugin # without creating an instance title = _('Editor') return title diff --git a/spyder/plugins/ipythonconsole/plugin.py b/spyder/plugins/ipythonconsole/plugin.py index 7231fd8c400..5eaa3cd5bb6 100644 --- a/spyder/plugins/ipythonconsole/plugin.py +++ b/spyder/plugins/ipythonconsole/plugin.py @@ -523,7 +523,7 @@ def toggle_view(self, checked): @staticmethod def get_plugin_title(): """Return widget title""" - # TODO: This is a temporary measure to get the title of the plugins + # TODO: This is a temporary measure to get the title of this plugin # without creating an instance return _('IPython console') From 84747b28e80143dde53e8d6d46da0e97a367d755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Wed, 6 Oct 2021 13:25:32 -0500 Subject: [PATCH 67/68] Always add external plugin metadata --- spyder/api/plugin_registration/registry.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/spyder/api/plugin_registration/registry.py b/spyder/api/plugin_registration/registry.py index c0f377a4fba..b8a1437f17c 100644 --- a/spyder/api/plugin_registration/registry.py +++ b/spyder/api/plugin_registration/registry.py @@ -204,14 +204,13 @@ def _instantiate_spyder5_plugin( self.internal_plugins |= {plugin_name} if external: - if not running_under_pytest(): - # These attributes come from spyder.app.find_plugins - module = PluginClass._spyder_module_name - package_name = PluginClass._spyder_package_name - version = PluginClass._spyder_version - description = plugin_instance.get_description() - dependencies.add(module, package_name, description, - version, None, kind=dependencies.PLUGIN) + # These attributes come from spyder.app.find_plugins + module = PluginClass._spyder_module_name + package_name = PluginClass._spyder_package_name + version = PluginClass._spyder_version + description = plugin_instance.get_description() + dependencies.add(module, package_name, description, + version, None, kind=dependencies.PLUGIN) return plugin_instance From 0f34e0c3de89f16cd70f9564ff6484912d0ec10c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Wed, 6 Oct 2021 17:36:44 -0500 Subject: [PATCH 68/68] Revert Github Actions changes --- .github/workflows/test-linux.yml | 8 ++++---- .github/workflows/test-mac.yml | 8 ++++---- .github/workflows/test-win.yml | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml index 6a39bcd2ad8..067c2b5e260 100644 --- a/.github/workflows/test-linux.yml +++ b/.github/workflows/test-linux.yml @@ -16,10 +16,10 @@ on: - '**.sh' pull_request: - # branches: - # - master - # - 5.* - # - 4.x + branches: + - master + - 5.* + - 4.x paths: - '.github/scripts/*.sh' - '.github/workflows/*.yml' diff --git a/.github/workflows/test-mac.yml b/.github/workflows/test-mac.yml index 95a74ab2c35..37c2dbcde4f 100644 --- a/.github/workflows/test-mac.yml +++ b/.github/workflows/test-mac.yml @@ -16,10 +16,10 @@ on: - '**.sh' pull_request: - # branches: - # - master - # - 5.* - # - 4.x + branches: + - master + - 5.* + - 4.x paths: - '.github/scripts/*.sh' - '.github/workflows/*.yml' diff --git a/.github/workflows/test-win.yml b/.github/workflows/test-win.yml index 70c2f8bc776..c5f39301066 100644 --- a/.github/workflows/test-win.yml +++ b/.github/workflows/test-win.yml @@ -16,10 +16,10 @@ on: - '**.sh' pull_request: - # branches: - # - master - # - 5.* - # - 4.x + branches: + - master + - 5.* + - 4.x paths: - '.github/scripts/*.sh' - '.github/workflows/*.yml'