Repository: spyder-ide/spyder

Issue:
Autosave not removed when closing a changed file without saving
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
@@ -1595,7 +1595,8 @@ def save_if_changed(self, cancelable=False, index=None):
 
         This function controls the message box prompt for saving
         changed files.  The actual save is performed in save() for
-        each index processed.
+        each index processed. This function also removes autosave files
+        corresponding to files the user chooses not to save.
         """
         if index is None:
             indexes = list(range(self.get_stack_count()))
@@ -1613,13 +1614,15 @@ def save_if_changed(self, cancelable=False, index=None):
             return True
         if unsaved_nb > 1:
             buttons |= QMessageBox.YesToAll | QMessageBox.NoToAll
-        yes_all = False
+        yes_all = no_all = False
         for index in indexes:
             self.set_stack_index(index)
             finfo = self.data[index]
             if finfo.filename == self.tempfile_path or yes_all:
                 if not self.save(index):
                     return False
+            elif no_all:
+                self.autosave.remove_autosave_file(finfo)
             elif (finfo.editor.document().isModified() and
                   self.save_dialog_on_tests):
 
@@ -1636,12 +1639,15 @@ def save_if_changed(self, cancelable=False, index=None):
                 if answer == QMessageBox.Yes:
                     if not self.save(index):
                         return False
+                elif answer == QMessageBox.No:
+                    self.autosave.remove_autosave_file(finfo)
                 elif answer == QMessageBox.YesToAll:
                     if not self.save(index):
                         return False
                     yes_all = True
                 elif answer == QMessageBox.NoToAll:
-                    return True
+                    self.autosave.remove_autosave_file(finfo)
+                    no_all = True
                 elif answer == QMessageBox.Cancel:
                     return False
         return True


Test patch:
diff --git a/spyder/plugins/editor/widgets/tests/test_save.py b/spyder/plugins/editor/widgets/tests/test_save.py
--- a/spyder/plugins/editor/widgets/tests/test_save.py
+++ b/spyder/plugins/editor/widgets/tests/test_save.py
@@ -93,6 +93,7 @@ def test_save_if_changed(editor_bot, mocker):
     save_if_changed = editor_stack.save_if_changed
     mocker.patch.object(editor.QMessageBox, 'exec_')
     mocker.patch.object(editor_stack, 'save')
+    mocker.patch.object(editor_stack.autosave, 'remove_autosave_file')
     editor_stack.save.return_value = True
 
     # No file changed - returns True.
@@ -101,6 +102,7 @@ def test_save_if_changed(editor_bot, mocker):
     editor_stack.data[2].editor.document().setModified(False)
     assert save_if_changed() is True
     assert not editor_stack.save.called
+    assert not editor_stack.autosave.remove_autosave_file.called
     editor_stack.data[0].editor.document().setModified(True)
     editor_stack.data[1].editor.document().setModified(True)
     editor_stack.data[2].editor.document().setModified(True)
@@ -109,37 +111,45 @@ def test_save_if_changed(editor_bot, mocker):
     editor.QMessageBox.exec_.return_value = editor.QMessageBox.Cancel
     assert save_if_changed(index=0, cancelable=True) is False
     assert not editor_stack.save.called
+    assert not editor_stack.autosave.remove_autosave_file.called
     assert editor_stack.tabs.currentIndex() == 0
 
     # Yes button - return value from save().
     editor.QMessageBox.exec_.return_value = editor.QMessageBox.Yes
     assert save_if_changed(index=0, cancelable=True) is True
     assert editor_stack.save.called
+    assert not editor_stack.autosave.remove_autosave_file.called
 
     # YesToAll button - if any save() fails, then return False.
     editor_stack.save.reset_mock()
     editor.QMessageBox.exec_.return_value = editor.QMessageBox.YesToAll
     assert save_if_changed() is True
     assert editor_stack.save.call_count == 3
+    assert not editor_stack.autosave.remove_autosave_file.called
 
-    # No button - returns True.
+    # No button - remove autosave, returns True.
     editor_stack.save.reset_mock()
     editor.QMessageBox.exec_.return_value = editor.QMessageBox.No
     assert save_if_changed(index=0, cancelable=True) is True
     assert not editor_stack.save.called
+    assert editor_stack.autosave.remove_autosave_file.called
 
-    # NoToAll button - returns True.
+    # NoToAll button - remove autosave 3x, returns True.
     editor_stack.save.reset_mock()
+    editor_stack.autosave.remove_autosave_file.reset_mock()
     editor.QMessageBox.exec_.return_value = editor.QMessageBox.NoToAll
     assert save_if_changed() is True
     assert not editor_stack.save.called
+    assert editor_stack.autosave.remove_autosave_file.call_count == 3
 
     # Tempfile doesn't show message box - always calls save().
     editor.QMessageBox.exec_.reset_mock()
+    editor_stack.autosave.remove_autosave_file.reset_mock()
     editor_stack.set_tempfile_path(__file__)
     editor_stack.save.return_value = False
     assert save_if_changed(index=2, cancelable=True) is False
     assert editor_stack.save.called
+    assert not editor_stack.autosave.remove_autosave_file.called
     editor.QMessageBox.exec_.assert_not_called()