Repository: spyder-ide/spyder

Issue:
Canceling closing Spyder with unsaved changes closes editor and all files
<!--- **PLEASE READ:** When submitting here, please ensure you've completed the following checklist and checked the boxes to confirm. Issue reports without it may be closed. Thanks! --->

### Issue Report Checklist

* [X] Searched the [issues page](https://github.com/spyder-ide/spyder/issues?q=is%3Aissue) for similar reports
* [ ] Read the relevant sections of the [Spyder Troubleshooting Guide](https://github.com/spyder-ide/spyder/wiki/Troubleshooting-Guide-and-FAQ) and followed its advice
* [ ] Reproduced the issue after updating with ``conda update spyder`` (or ``pip``, if not using Anaconda)
* [ ] Could not reproduce inside ``jupyter qtconsole`` (if console-related)
* [ ] Tried basic troubleshooting (if a bug/error)
    * [X] Restarted Spyder
    * [X] Reset preferences with ``spyder --reset``
    * [ ] Reinstalled the latest version of [Anaconda](https://www.anaconda.com/download/)
    * [ ] Tried the other applicable steps from the Troubleshooting Guide
* [X] Completed the **Problem Description**, **Steps to Reproduce** and **Version** sections below


## Problem Description



### What steps reproduce the problem?

1. Close Spyder with unsaved changes in file
2. Choose "Cancel" on dialog
3. Editor/all files are closed, but Spyder remains running
4. Errors thrown when clicking on outline explorer of the closed file, IPython console or Source or File menu items (at least, possibly not limited to these).

![spyder_crash_close_cancel_2](https://user-images.githubusercontent.com/2330659/164481402-3295339e-4620-4557-8126-9c16c8a3ed9f.gif)

### What is the expected output? What do you see instead?
That Spyder remains running with all files open


### Paste Traceback/Error Below (if applicable)
<!--- Copy from error dialog or View > Panes > Internal Console --->
This traceback is from clicking the outline explorer
```python-traceback

Traceback (most recent call last):
  File "C:\Users\username\AppData\Local\Programs\Spyder\pkgs\spyder\plugins\editor\plugin.py", line 321, in <lambda>
    self.load(filenames=filenames, goto=goto, word=word,
  File "C:\Users\username\AppData\Local\Programs\Spyder\pkgs\spyder\plugins\editor\plugin.py", line 2136, in load
    self.switch_to_plugin()
  File "C:\Users\username\AppData\Local\Programs\Spyder\pkgs\spyder\plugins\editor\plugin.py", line 1705, in switch_to_plugin
    super(Editor, self).switch_to_plugin()
  File "C:\Users\username\AppData\Local\Programs\Spyder\pkgs\spyder\api\plugins\old_api.py", line 376, in switch_to_plugin
    super(BasePluginWidget, self)._switch_to_plugin()
  File "C:\Users\username\AppData\Local\Programs\Spyder\pkgs\spyder\plugins\base.py", line 293, in _switch_to_plugin
    if not self._toggle_view_action.isChecked():
RuntimeError: wrapped C/C++ object of type SpyderAction has been deleted

```

Traceback from clicking Source menu
```python-traceback
Traceback (most recent call last):
  File "C:\Users\username\AppData\Local\Programs\Spyder\pkgs\spyder\app\mainwindow.py", line 1307, in update_source_menu
    editor.refresh_formatter_name()
  File "C:\Users\username\AppData\Local\Programs\Spyder\pkgs\spyder\plugins\editor\plugin.py", line 1859, in refresh_formatter_name
    self.formatting_action.setText(
RuntimeError: wrapped C/C++ object of type SpyderAction has been deleted
```

## Versions
<!--- You can get this information from Help > About Spyder...
or (if Spyder won't launch) the "conda list" command
from the Anaconda Prompt/Terminal/command line. --->

* Spyder version: 5.3.0 None
* Python version: 3.8.10 64-bit
* Qt version: 5.15.2
* PyQt5 version: 5.15.6
* Operating System: Windows 10

### Dependencies
<!--- Please go to the menu entry Help > Dependencies,
press the Copy to clipboard button and paste below --->

```
# Mandatory:
atomicwrites >=1.2.0                 :  1.4.0 (OK)
chardet >=2.0.0                      :  4.0.0 (OK)
cloudpickle >=0.5.0                  :  2.0.0 (OK)
cookiecutter >=1.6.0                 :  1.7.3 (OK)
diff_match_patch >=20181111          :  20200713 (OK)
intervaltree                         :  None (OK)
IPython >=7.31.1;<8.0.0              :  7.32.0 (OK)
jedi >=0.17.2;<0.19.0                :  0.18.1 (OK)
jellyfish >=0.7                      :  0.9.0 (OK)
jsonschema >=3.2.0                   :  4.4.0 (OK)
keyring >=17.0.0                     :  23.5.0 (OK)
nbconvert >=4.0                      :  6.4.5 (OK)
numpydoc >=0.6.0                     :  1.2.1 (OK)
paramiko >=2.4.0                     :  2.10.3 (OK)
parso >=0.7.0;<0.9.0                 :  0.8.3 (OK)
pexpect >=4.4.0                      :  4.8.0 (OK)
pickleshare >=0.4                    :  0.7.5 (OK)
psutil >=5.3                         :  5.9.0 (OK)
pygments >=2.0                       :  2.11.2 (OK)
pylint >=2.5.0                       :  2.13.3 (OK)
pyls_spyder >=0.4.0                  :  0.4.0 (OK)
pylsp >=1.4.1;<1.5.0                 :  1.4.1 (OK)
pylsp_black >=1.2.0                  :  1.2.0 (OK)
qdarkstyle >=3.0.2;<3.1.0            :  3.0.3 (OK)
qstylizer >=0.1.10                   :  0.2.1 (OK)
qtawesome >=1.0.2                    :  1.1.1 (OK)
qtconsole >=5.3.0;<5.4.0             :  5.3.0 (OK)
qtpy >=2.0.1                         :  2.0.1 (OK)
rtree >=0.9.7                        :  0.9.7 (OK)
setuptools >=49.6.0                  :  61.2.0 (OK)
sphinx >=0.6.6                       :  4.5.0 (OK)
spyder_kernels >=2.3.0;<2.4.0        :  2.3.0 (OK)
textdistance >=4.2.0                 :  4.2.2 (OK)
three_merge >=0.1.1                  :  0.1.1 (OK)
watchdog                             :  2.1.7 (OK)
zmq >=17                             :  22.3.0 (OK)

# Optional:
cython >=0.21                        :  0.29.28 (OK)
matplotlib >=3.0.0                   :  3.5.1 (OK)
numpy >=1.7                          :  1.22.3 (OK)
pandas >=1.1.1                       :  1.4.1 (OK)
scipy >=0.17.0                       :  1.8.0 (OK)
sympy >=0.7.3                        :  1.10.1 (OK)

# Spyder plugins:
spyder_terminal.terminalplugin 1.2.2 :  1.2.2 (OK)

```



Discussion:
Hi @rhkarls thanks for the feedback! I was able to reproduce this we will check it for our next release :+1:

Resolving patch:
diff --git a/spyder/api/plugin_registration/registry.py b/spyder/api/plugin_registration/registry.py
--- a/spyder/api/plugin_registration/registry.py
+++ b/spyder/api/plugin_registration/registry.py
@@ -425,6 +425,28 @@ def delete_plugin(self, plugin_name: str, teardown: bool = True) -> bool:
             return False
 
         if isinstance(plugin_instance, SpyderPluginV2):
+            # Cleanly delete plugin widgets. This avoids segfautls with
+            # PyQt 5.15
+            if isinstance(plugin_instance, SpyderDockablePlugin):
+                try:
+                    plugin_instance.get_widget().close()
+                    plugin_instance.get_widget().deleteLater()
+                except RuntimeError:
+                    pass
+            else:
+                container = plugin_instance.get_container()
+                if container:
+                    try:
+                        container.close()
+                        container.deleteLater()
+                    except RuntimeError:
+                        pass
+
+            # Delete plugin
+            try:
+                plugin_instance.deleteLater()
+            except RuntimeError:
+                pass
             if teardown:
                 # Disconnect plugin from other plugins
                 self._teardown_plugin(plugin_name)
@@ -449,6 +471,10 @@ def delete_plugin(self, plugin_name: str, teardown: bool = True) -> bool:
             except RuntimeError:
                 pass
         elif isinstance(plugin_instance, SpyderPlugin):
+            try:
+                plugin_instance.deleteLater()
+            except RuntimeError:
+                pass
             if teardown:
                 # Disconnect depending plugins from the plugin to delete
                 self._notify_plugin_teardown(plugin_name)
@@ -535,10 +561,6 @@ def delete_all_plugins(self, excluding: Optional[Set[str]] = None,
             if plugin_name not in excluding:
                 plugin_instance = self.plugin_registry[plugin_name]
                 if isinstance(plugin_instance, SpyderPlugin):
-                    try:
-                        plugin_instance.deleteLater()
-                    except RuntimeError:
-                        pass
                     can_close &= self.delete_plugin(
                         plugin_name, teardown=False)
                     if not can_close and not close_immediately:
@@ -552,28 +574,6 @@ def delete_all_plugins(self, excluding: Optional[Set[str]] = None,
             if plugin_name not in excluding:
                 plugin_instance = self.plugin_registry[plugin_name]
                 if isinstance(plugin_instance, SpyderPluginV2):
-                    # Cleanly delete plugin widgets. This avoids segfautls with
-                    # PyQt 5.15
-                    if isinstance(plugin_instance, SpyderDockablePlugin):
-                        try:
-                            plugin_instance.get_widget().close()
-                            plugin_instance.get_widget().deleteLater()
-                        except RuntimeError:
-                            pass
-                    else:
-                        container = plugin_instance.get_container()
-                        if container:
-                            try:
-                                container.close()
-                                container.deleteLater()
-                            except RuntimeError:
-                                pass
-
-                    # Delete plugin
-                    try:
-                        plugin_instance.deleteLater()
-                    except RuntimeError:
-                        pass
                     can_close &= self.delete_plugin(
                         plugin_name, teardown=False)
                     if not can_close and not close_immediately:
@@ -587,28 +587,6 @@ def delete_all_plugins(self, excluding: Optional[Set[str]] = None,
             if plugin_name not in excluding:
                 plugin_instance = self.plugin_registry[plugin_name]
                 if isinstance(plugin_instance, SpyderPluginV2):
-                    # Cleanly delete plugin widgets. This avoids segfautls with
-                    # PyQt 5.15
-                    if isinstance(plugin_instance, SpyderDockablePlugin):
-                        try:
-                            plugin_instance.get_widget().close()
-                            plugin_instance.get_widget().deleteLater()
-                        except RuntimeError:
-                            pass
-                    else:
-                        container = plugin_instance.get_container()
-                        if container:
-                            try:
-                                container.close()
-                                container.deleteLater()
-                            except RuntimeError:
-                                pass
-
-                    # Delete plugin
-                    try:
-                        plugin_instance.deleteLater()
-                    except RuntimeError:
-                        pass
                     can_close &= self.delete_plugin(
                         plugin_name, teardown=False)
                     if not can_close and not close_immediately:


Test patch: