Repository: spyder-ide/spyder

Issue:
Text files are wrongly autosaved when opened without changing them
As reported on Gitter:

> I have one file, opened by default every time with a Project, that I don't modify or even tab to while Spyder is open (its just a .txt with a long list of tasks and Spyder-related code snippits, nothing special except that its 26KB long), but yet an autosave file is created anyway and not deleted when closing Spyder. I click "discard" every time I open and it is deleted in the autosave folder, but every time it comes back if I leave Spyder open long enough to make another autosave despite there being no changes. 

This is on the current HEAD. It is relevant that this is a `.txt` file; in the same setting `.py` and `.md` files are not autosaved.


Discussion:

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
@@ -1704,6 +1704,7 @@ def save(self, index=None, force=False):
                                  finfo.filename, finfo.filename)
 
             finfo.editor.document().setModified(False)
+            finfo.editor.document().changed_since_autosave = False
             self.modification_changed(index=index)
             self.analyze_script(index)
 
@@ -1714,10 +1715,6 @@ def save(self, index=None, force=False):
             # would be sufficient for outline explorer data.
             finfo.editor.rehighlight()
 
-            # rehighlight() calls textChanged(), so the change_since_autosave
-            # flag should be cleared after rehighlight()
-            finfo.editor.document().changed_since_autosave = False
-
             self._refresh_outlineexplorer(index)
 
             finfo.editor.notify_save()
@@ -2189,6 +2186,7 @@ def reload(self, index):
         position = finfo.editor.get_position('cursor')
         finfo.editor.set_text(txt)
         finfo.editor.document().setModified(False)
+        finfo.editor.document().changed_since_autosave = False
         finfo.editor.set_cursor_position(position)
 
         #XXX CodeEditor-only: re-scan the whole text to rebuild outline
@@ -2198,10 +2196,6 @@ def reload(self, index):
         # would be sufficient for outline explorer data.
         finfo.editor.rehighlight()
 
-        # rehighlight() calls textChanged(), so the change_since_autosave
-        # flag should be cleared after rehighlight()
-        finfo.editor.document().changed_since_autosave = False
-
         self._refresh_outlineexplorer(index)
 
     def revert(self):
@@ -2278,7 +2272,7 @@ def create_new_editor(self, fname, enc, txt, set_current, new=False,
         if cloned_from is None:
             editor.set_text(txt)
             editor.document().setModified(False)
-        editor.document().changed_since_autosave = False
+            editor.document().changed_since_autosave = False
         finfo.text_changed_at.connect(
                                     lambda fname, position:
                                     self.text_changed_at.emit(fname, position))
diff --git a/spyder/utils/syntaxhighlighters.py b/spyder/utils/syntaxhighlighters.py
--- a/spyder/utils/syntaxhighlighters.py
+++ b/spyder/utils/syntaxhighlighters.py
@@ -280,7 +280,11 @@ def get_outlineexplorer_data(self):
     def rehighlight(self):
         self.outlineexplorer_data = {}
         QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
+        # rehighlight() triggers the textChanged signal, which in our editor
+        # switches changed_since_autosave to True; we need to prevent this.
+        old_changed = getattr(self.document(), 'changed_since_autosave', False)
         QSyntaxHighlighter.rehighlight(self)
+        self.document().changed_since_autosave = old_changed
         QApplication.restoreOverrideCursor()
 
 


Test patch:
diff --git a/spyder/plugins/editor/widgets/tests/test_editor.py b/spyder/plugins/editor/widgets/tests/test_editor.py
--- a/spyder/plugins/editor/widgets/tests/test_editor.py
+++ b/spyder/plugins/editor/widgets/tests/test_editor.py
@@ -644,16 +644,21 @@ def test_autosave_does_not_save_new_files(editor_bot, mocker):
     editor_stack._write_to_file.assert_not_called()
 
 
-def test_autosave_does_not_save_after_open(base_editor_bot, mocker):
+@pytest.mark.parametrize('filename', ['ham.py', 'ham.txt'])
+def test_autosave_does_not_save_after_open(base_editor_bot, mocker, qtbot,
+                                           filename):
     """
     Test that autosave() does not save files immediately after opening.
 
     Files should only be autosaved after the user made changes.
+    Editors use different highlighters depending on the filename, so we test
+    both Python and text files. The latter covers issue #8654.
     """
     editor_stack = base_editor_bot
     txt = 'spam\n'
-    editor_stack.create_new_editor('ham.py', 'ascii', txt, set_current=True)
+    editor_stack.create_new_editor(filename, 'ascii', txt, set_current=True)
     mocker.patch.object(editor_stack, '_write_to_file')
+    qtbot.wait(100)  # Wait for PygmentsSH.makeCharlist() if applicable
     editor_stack.autosave.autosave(0)
     editor_stack._write_to_file.assert_not_called()
 
@@ -672,9 +677,7 @@ def test_autosave_does_not_save_after_reload(base_editor_bot, mocker):
     mocker.patch.object(editor_stack, '_write_to_file')
     mocker.patch('spyder.plugins.editor.widgets.editor.encoding.read',
                  return_value=(txt, 'ascii'))
-    print(editor_stack.data[0].editor.document().changed_since_autosave)
     editor_stack.reload(0)
-    print(editor_stack.data[0].editor.document().changed_since_autosave)
     editor_stack.autosave.autosave(0)
     editor_stack._write_to_file.assert_not_called()