Repository: spyder-ide/spyder

Issue:
Crash after switching git branch that removes open 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
* [x] Read the relevant sections of the [Spyder Troubleshooting Guide](https://github.com/spyder-ide/spyder/wiki/Troubleshooting-Guide-and-FAQ) and followed its advice
* [x] 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
    * [ ] Reset preferences with ``spyder --reset``
    * [x] Reinstalled the latest version of [Anaconda](https://www.anaconda.com/download/)
    * [x] Tried the other applicable steps from the Troubleshooting Guide
* [x] Completed the **Problem Description**, **Steps to Reproduce** and **Version** sections below


## Problem Description
Working on a project where I'm simultaneously writing in two different git branches. Changing branches may remove, rename or otherwise relocate some files in the project. When a file that's currently open in spyder is changed outside of spyder, spyder prompts the user with a message asking whether to close the file or not. If I use `git checkout <other_branch>`, after being asked if I want to close the file, selecting yes results in spyder crashing with `Segmentation fault` in the console.

Something that makes this issue harder is that I couldn't reproduce this totally consistently. Sometimes closing the first two or three files does nothing and closing the last remaining open file crashes spyder. I'll update if I can reproduce more consistently. This sounds similar in description to an old issue I found, #4453.

### What steps reproduce the problem?

1. Make a repo and a file in that repo.
2. Make a new branch in the repo, switch to the new branch and rename the file.
3. Open the file in spyder.
4. Switch to the old branch and go back to spyder. This will propt the user with a message asking if spyder should close the file.
5. Confirm closing the file, spyder crashes with `Segmentation fault`.

### What is the expected output? What do you see instead?

Well... I'd expect spyder to simply close the file and not crash.

### Paste Traceback/Error Below (if applicable)
<!--- Copy from error dialog or View > Panes > Internal Console --->

```python-traceback

Segmentation fault

```
## 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.1.5
* Python version: 3.9.7 64-bit
* Qt version: 5.9.7
* PyQt version: 5.9.2
* Operating System name/version: Linux 4.9.0-18-amd64 

### 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.2 (OK)
diff_match_patch >=20181111   :  20200713 (OK)
intervaltree >=3.0.2          :  3.1.0 (OK)
IPython >=7.6.0               :  7.27.0 (OK)
jedi >=0.17.2;<0.19.0         :  0.17.2 (OK)
jsonschema >=3.2.0            :  3.2.0 (OK)
keyring >=17.0.0              :  23.1.0 (OK)
nbconvert >=4.0               :  6.1.0 (OK)
numpydoc >=0.6.0              :  1.1.0 (OK)
parso >=0.7.0;<0.9.0          :  0.7.0 (OK)
pexpect >=4.4.0               :  4.8.0 (OK)
pickleshare >=0.4             :  0.7.5 (OK)
psutil >=5.3                  :  5.8.0 (OK)
pygments >=2.0                :  2.10.0 (OK)
pylint >=2.5.0;<2.10.0        :  2.9.6 (OK)
pyls_spyder >=0.4.0           :  0.4.0 (OK)
pylsp >=1.2.2;<1.3.0          :  1.2.4 (OK)
pylsp_black >=1.0.0           :  None (OK)
qdarkstyle =3.0.2             :  3.0.2 (OK)
qstylizer >=0.1.10            :  0.1.10 (OK)
qtawesome >=1.0.2             :  1.0.2 (OK)
qtconsole >=5.1.0             :  5.1.1 (OK)
qtpy >=1.5.0                  :  1.10.0 (OK)
rtree >=0.9.7                 :  0.9.7 (OK)
setuptools >=49.6.0           :  58.0.4 (OK)
sphinx >=0.6.6                :  4.2.0 (OK)
spyder_kernels >=2.1.1;<2.2.0 :  2.1.3 (OK)
textdistance >=4.2.0          :  4.2.1 (OK)
three_merge >=0.1.1           :  0.1.1 (OK)
watchdog >=0.10.3             :  2.1.3 (OK)
xdg >=0.26                    :  0.27 (OK)
zmq >=17                      :  22.2.1 (OK)

# Optional:
cython >=0.21                 :  None (OK)
matplotlib >=2.0.0            :  3.4.2 (OK)
numpy >=1.7                   :  1.20.3 (OK)
pandas >=1.1.1                :  1.3.3 (OK)
scipy >=0.17.0                :  1.7.1 (OK)
sympy >=0.7.3                 :  1.9 (OK)
```



Discussion:
Hi @mwappner thank you for the feedback! Could it be possible for you to update to Spyder 5.3.2? Not totally sure if that could help, but just to be sure the problem is present in our latest release. To update/get the latest release, if you iuse conda, you will need to create a new env using conda-forge packages. For that, from a terminal you can run something like:

    conda create -n spyder-cf -c conda-forge spyder
    conda activate
    spyder

Also, if that doesn't help maybe running Spyder using gdb could help us spot where the issue is. For that you can install gdb from conda-forge too. You could also run spyder in debug-mode to check if there is some traceback with the `debug-info` flag: `spyder --debug-info verbose`

Let us know if something of the above helps or gives more info about what could be happening!

All of the following I tested in a fresh git repo with two branches and two files in each branch:
1. Still present in the 5.3.2 release. On a sidenote for this version, on launching I got
```traceback
fromIccProfile: failed minimal tag size sanity
[17967:17982:0728/184725.900287:ERROR:simple_version_upgrade.cc(165)] Disk cache backend fake index file has wrong magic number.
[17967:17982:0728/184725.900516:ERROR:simple_backend_impl.cc(757)] Simple Cache Backend: wrong file structure on disk: 3 path: /home/marcos/.cache/Spyder/QtWebEngine/Default/Cache
```
I don't know what those messages mean, but let me know if you want me to report this elsewhere. I know you are always looking for feedback on the latest releases. The suggested command didn't work, but I did `conda install -c conda-forge spyder=5.3.2` in a fresh environment, it should be exactly the same.

2. Regarding `gbd`, I'll need further instructions. Simply instlaling it and running `gdb spyder` returns 
```
"/home/marcos/anaconda3/envs/spyder-cf/bin/spyder": not in executable format: file format not recognized
```

3. Here's everything that's printed to the console between me changing branches and spyder crashing using debug mode, including focusing from the console back to spyder and interacting with the popup messages. The first two lines correspond to the last two things that happen when I open the files that will disappear when I change branches. As you can see there were tow open files `test1.py` and `test2.py` that I was prompted to close.
```traceback
2022-07-28 18:42:55,584 [DEBUG] [spyder.plugins.completion.providers.fallback.actor] -> Perform request textDocument/cursorEvent with id 23
2022-07-28 18:42:55,584 [DEBUG] [spyder.plugins.completion.providers.snippets.actor] -> Perform request textDocument/cursorEvent with id 23
2022-07-28 18:43:08,599 [DEBUG] [urllib3.connectionpool] -> Starting new HTTP connection (37): 127.0.0.1:46624
2022-07-28 18:43:25,696 [DEBUG] [spyder.plugins.editor.widgets.editorstack_helpers] -> Call ThreadManager's 'close_threads'
2022-07-28 18:43:25,699 [DEBUG] [spyder.plugins.editor.plugin] -> Perform request textDocument/didClose for: /home/marcos/Documents/testing/test1.py
2022-07-28 18:43:25,699 [DEBUG] [spyder.plugins.completion.providers.languageserver.client] -> Perform request textDocument/didClose with id 25
2022-07-28 18:43:25,700 [DEBUG] [spyder.plugins.completion.providers.fallback.actor] -> Perform request textDocument/didClose with id 24
2022-07-28 18:43:25,705 [DEBUG] [urllib3.connectionpool] -> Starting new HTTP connection (38): 127.0.0.1:46624
2022-07-28 18:43:25,706 [DEBUG] [spyder.plugins.completion.providers.snippets.actor] -> Perform request textDocument/didClose with id 24
2022-07-28 18:43:25,722 [DEBUG] [urllib3.connectionpool] -> Starting new HTTP connection (39): 127.0.0.1:46624
2022-07-28 18:43:26,656 [DEBUG] [spyder.plugins.editor.widgets.editorstack_helpers] -> Call ThreadManager's 'close_threads'
2022-07-28 18:43:26,658 [DEBUG] [spyder.plugins.editor.plugin] -> Perform request textDocument/didClose for: /home/marcos/Documents/testing/test2.py
2022-07-28 18:43:26,659 [DEBUG] [spyder.plugins.completion.providers.languageserver.client] -> Perform request textDocument/didClose with id 26
2022-07-28 18:43:26,659 [DEBUG] [spyder.plugins.completion.providers.fallback.actor] -> Perform request textDocument/didClose with id 25
2022-07-28 18:43:26,671 [DEBUG] [urllib3.connectionpool] -> Starting new HTTP connection (40): 127.0.0.1:46624
2022-07-28 18:43:26,673 [DEBUG] [spyder.plugins.completion.providers.snippets.actor] -> Perform request textDocument/didClose with id 25
2022-07-28 18:43:26,695 [DEBUG] [urllib3.connectionpool] -> Starting new HTTP connection (41): 127.0.0.1:46624
Segmentation fault
```
Thanks @mwappner for the new info! Regarding the usage of `gdb` I think running something like `gdb -return-child-result -batch -ex r -ex py-bt --args python /home/marcos/anaconda3/envs/spyder-cf/bin/spyder` from a terminal with the activated env should work.

Let us know!
Alright, that worked. Got the following traceback when crashing, I hope it's worth something.
```python-traceback
Thread 1 "python" received signal SIGSEGV, Segmentation fault.
0x00007fffe8c6c21d in QQmlData::isSignalConnected(QAbstractDeclarativeData*, QObject const*, int) () from /home/marcos/anaconda3/envs/spyder-cf/lib/python3.9/site-packages/PyQt5/../../../libQt5Qml.so.5
Traceback (most recent call first):
  <built-in method exec_ of QApplication object at remote 0x7fffe612b670>
  File "/home/marcos/anaconda3/envs/spyder-cf/lib/python3.9/site-packages/spyder/app/utils.py", line 333, in create_window
    app.exec_()
  File "/home/marcos/anaconda3/envs/spyder-cf/lib/python3.9/site-packages/spyder/app/mainwindow.py", line 2031, in main
    mainwindow = create_window(MainWindow, app, splash, options, args)
  File "/home/marcos/anaconda3/envs/spyder-cf/lib/python3.9/site-packages/spyder/app/start.py", line 267, in main
    mainwindow.main(options, args)
  File "/home/marcos/anaconda3/envs/spyder-cf/bin/spyder", line 11, in <module>
    sys.exit(main())

```

Resolving patch:
diff --git a/spyder/plugins/editor/widgets/editor.py b/spyder/plugins/editor/widgets/editor.py
--- a/spyder/plugins/editor/widgets/editor.py
+++ b/spyder/plugins/editor/widgets/editor.py
@@ -1669,15 +1669,25 @@ def close_file(self, index=None, force=False):
             self.refresh_file_dependent_actions.emit()
             self.update_plugin_title.emit()
 
-            editor = self.get_current_editor()
-            if editor:
-                editor.setFocus()
-
             if new_index is not None:
                 if index < new_index:
                     new_index -= 1
                 self.set_stack_index(new_index)
 
+            # Give focus to the previous editor in the stack
+            editor = self.get_current_editor()
+            if editor:
+                # This is necessary to avoid a segfault when closing several
+                # files that were removed outside Spyder one after the other.
+                # Fixes spyder-ide/spyder#18838
+                QApplication.processEvents()
+
+                # This allows to close files that were removed outside Spyder
+                # one after the other without refocusing each one.
+                self.__file_status_flag = False
+
+                editor.setFocus()
+
             self.add_last_closed_file(finfo.filename)
 
             if finfo.filename in self.autosave.file_hashes:
@@ -2327,9 +2337,15 @@ def __refresh_readonly(self, index):
             self.readonly_changed.emit(read_only)
 
     def __check_file_status(self, index):
-        """Check if file has been changed in any way outside Spyder:
-        1. removed, moved or renamed outside Spyder
-        2. modified outside Spyder"""
+        """
+        Check if file has been changed in any way outside Spyder.
+
+        Notes
+        -----
+        Possible ways are:
+        * The file was removed, moved or renamed outside Spyder.
+        * The file was modified outside Spyder.
+        """
         if self.__file_status_flag:
             # Avoid infinite loop: when the QMessageBox.question pops, it
             # gets focus and then give it back to the CodeEditor instance,
@@ -2347,40 +2363,44 @@ def __check_file_status(self, index):
             # File was just created (not yet saved): do nothing
             # (do not return because of the clean-up at the end of the method)
             pass
-
         elif not osp.isfile(finfo.filename):
             # File doesn't exist (removed, moved or offline):
             self.msgbox = QMessageBox(
-                    QMessageBox.Warning,
-                    self.title,
-                    _("<b>%s</b> is unavailable "
-                      "(this file may have been removed, moved "
-                      "or renamed outside Spyder)."
-                      "<br>Do you want to close it?") % name,
-                    QMessageBox.Yes | QMessageBox.No,
-                    self)
+                QMessageBox.Warning,
+                self.title,
+                _("The file <b>%s</b> is unavailable."
+                  "<br><br>"
+                  "It may have been removed, moved or renamed outside Spyder."
+                  "<br><br>"
+                  "Do you want to close it?") % name,
+                QMessageBox.Yes | QMessageBox.No,
+                self
+            )
+
             answer = self.msgbox.exec_()
             if answer == QMessageBox.Yes:
-                self.close_file(index)
+                self.close_file(index, force=True)
             else:
                 finfo.newly_created = True
                 finfo.editor.document().setModified(True)
                 self.modification_changed(index=index)
-
         else:
             # Else, testing if it has been modified elsewhere:
             lastm = QFileInfo(finfo.filename).lastModified()
-            if to_text_string(lastm.toString()) \
-               != to_text_string(finfo.lastmodified.toString()):
+            if str(lastm.toString()) != str(finfo.lastmodified.toString()):
                 if finfo.editor.document().isModified():
                     self.msgbox = QMessageBox(
                         QMessageBox.Question,
                         self.title,
-                        _("<b>%s</b> has been modified outside Spyder."
-                          "<br>Do you want to reload it and lose all "
-                          "your changes?") % name,
+                        _("The file <b>%s</b> has been modified outside "
+                          "Spyder."
+                          "<br><br>"
+                          "Do you want to reload it and lose all your "
+                          "changes?") % name,
                         QMessageBox.Yes | QMessageBox.No,
-                        self)
+                        self
+                    )
+
                     answer = self.msgbox.exec_()
                     if answer == QMessageBox.Yes:
                         self.reload(index)


Test patch: