From 3d27687a4397523fd4623c8939674671767c491c 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 01/48] 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 5448f611e704e29d2866f3164ba056f3b1e4a3be 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 02/48] 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 9ee88accb2880398bb0cdc64ac10299d8243a70d 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 03/48] 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 a644ac0934c8f2dd53e8b74f032c69576e350e00 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 04/48] 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 a7a6ba850cb5556967f95de97fa24563162e4f00 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 05/48] 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 6eaf52e3a0d751c42df3b1f798e741812d51d28f 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 06/48] 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 c064b4677dd71e85cab3584e3466a47fa6d6a9e8 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 07/48] 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 8d78ecbfa3d38dafc9a31642f7c7222d3b2ff09f 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 08/48] 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 18e997765d17250972d89e0b3faf66610bcc816c 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 09/48] 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 26e8eb98eae96829384da70f932090496abbfc11 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 10/48] 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 aef911c1815893f7a8e397ef43216735faf2d200 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 11/48] 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 b39e41696b9f1f8a0e1b5c060c64a9ad3f508aec 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 12/48] 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 a702949fc4f81f84298ff4d798705c964b7fbcfd 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 13/48] 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 b55159e18e977916b6b10f9657ba64071010c33f 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 14/48] 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 d2d5ee1cffa05e180456da35d5d5043b15ee4a45 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 15/48] 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 44872daedf9267250e96034c87bc0a5b8f106588 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 16/48] 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 c79bbf661039f236e97145f9591b56494784705b 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 17/48] 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 c32f2382ac5006ddd690bf2b6adaa0349d653a90 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 18/48] 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 f87ec3735a1a09df0552498cf3c39d1f57b2795a 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 19/48] 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 60e68101b220f26241d65bf0fe311f60560703f3 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 20/48] 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 be3014dd8480c85fa092c213612a1deef46939f6 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 21/48] 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 b0a05645bbc4ceba373311267977a8eb20d8bd7d 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 22/48] 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 4374df068d4ab335c9ce4f15686052ad1ddde498 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 23/48] 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 5fb2c0915d011a8fcfcedcd762d1db482be81edf 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 24/48] 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 d11869caaeee0a058f904b37699afd0e22a14953 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 25/48] 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 94c71be60fd82f376a55f37a414a26c8c4803df2 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 26/48] 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 7cc999e59e955fbf8b07397795a45bb9c71473a4 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 27/48] 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 e3464a124383899e05ee1a9b1a7c1c45699cf096 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 28/48] 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 9f5530fb67e1445b0504964176c6cf1b54cc267b 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 29/48] 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 67a929030625e6d2cb0c4f1bb7f48bd849c006e9 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 30/48] 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 3dbe853586e4ebbafb69295011b6a0ab9b3c607c 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 31/48] 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 ff81fe3f9f8fe1313421e6d1932048ee1b6a1218 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 32/48] 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 5b2ef7a68caa65119f497ac6f9a8a78898ebe8ae 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 33/48] 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 7bae6a5ed62cb2bb731c91749498c2d8e5218d26 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 34/48] 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 f894e526b2de1a3661358ee1895d1bd5617a1227 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 35/48] 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 258e4cf2d6ec4b41410499356f8e860681a1b33e 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 36/48] 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 fd5e08068c6e034da52bb77fc73ca4f86941f228 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 37/48] 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 dfd6dc8e879f389902215fb299c0d637985449b7 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 38/48] 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 46c110b26a4bac12c5db5d65efb802b31e8b2c38 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 39/48] 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 f3e22259266242a81b55da25163eb7acba37303d 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 40/48] 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 ff956264af3f4ca2833039788e23b07558674f94 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 41/48] 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 f82445e72e7d86b49f364f41a92d85f1dc9a2a53 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 42/48] 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 cf5beef9090b8680791a7860c4537ac8b99866f0 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 43/48] 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 5e57da5ec5ee94bd3ad03fda4365172bdebd5b71 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 44/48] 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 3a36062553e9cf9ef353d978a2835df29fd50362 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 45/48] 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 c1ae0d7cff104969d0b12747ddbf7018a48cba3a 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 46/48] 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 b6e8b9f5d40460f033494d8781d32e5b531ba500 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 47/48] 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 2b643c87a76da7f20bca9a4ca33c33b9c8e751f9 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 48/48] 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