Repository: spyder-ide/spyder

Issue:
Return value of EditorStack.has_filename not checked properly
While writing tests for the editor stack save functions for issue #5703, I noticed a separate issue in the way that the return value from `has_filename` was being used.  `has_filename` returns a list index for the location of the file in `self.data`, which can include `0` as a valid value.  If the file is not found, then the function returns `None`.

In some of the code that calls `has_filename` (including code from the save functions), the code was written as `if not has_filename(fname)`.  I believe the intention was to skip the code when the file wasn't found, but an unintentional side effect is that it was skipping the code when the index was 0.

During `save_as` and  `save_copy_as`, this skipped code was used to close a file if the new, selected `save as` name was the same as an existing, open file.

Here's another way to see a side-effect of skipping index 0:
1.  Open 2 or 3 files from the `Recent files` list.  When a file is opened, it is removed from recent files.
2.  Notice, however, that the file on tab 0 remains on the recent file list.
3.  Swap tab 0 and tab 1 in the editor.
4.  Look at recent files again.  Notice now that the filename for the file that had been in position 1 is now on recent files and that the file moved to position 1 has been removed from recent files.






Discussion:

Resolving patch:
diff --git a/spyder/plugins/editor.py b/spyder/plugins/editor.py
--- a/spyder/plugins/editor.py
+++ b/spyder/plugins/editor.py
@@ -1766,7 +1766,7 @@ def new(self, fname=None, editorstack=None, text=None):
             # QString when triggered by a Qt signal
             fname = osp.abspath(to_text_string(fname))
             index = current_es.has_filename(fname)
-            if index and not current_es.close_file(index):
+            if index is not None and not current_es.close_file(index):
                 return
         
         # Creating the editor widget in the first editorstack (the one that
@@ -1788,7 +1788,7 @@ def update_recent_file_menu(self):
         """Update recent file menu"""
         recent_files = []
         for fname in self.recent_files:
-            if not self.is_file_opened(fname) and osp.isfile(fname):
+            if self.is_file_opened(fname) is None and osp.isfile(fname):
                 recent_files.append(fname)
         self.recent_file_menu.clear()
         if recent_files:
diff --git a/spyder/widgets/editor.py b/spyder/widgets/editor.py
--- a/spyder/widgets/editor.py
+++ b/spyder/widgets/editor.py
@@ -1319,10 +1319,20 @@ def get_current_filename(self):
             return self.data[self.get_stack_index()].filename
 
     def has_filename(self, filename):
+        """Return the self.data index position for the filename.
+
+        Args:
+            filename: Name of the file to search for in self.data.
+
+        Returns:
+            The self.data index for the filename.  Returns None
+            if the filename is not found in self.data.
+        """
         fixpath = lambda path: osp.normcase(osp.realpath(path))
         for index, finfo in enumerate(self.data):
             if fixpath(filename) == fixpath(finfo.filename):
                 return index
+        return None
 
     def set_current_filename(self, filename):
         """Set current filename and return the associated editor instance"""
@@ -1334,6 +1344,18 @@ def set_current_filename(self, filename):
             return editor
 
     def is_file_opened(self, filename=None):
+        """Return if filename is in the editor stack.
+
+        Args:
+            filename: Name of the file to search for.  If filename is None,
+                then checks if any file is open.
+
+        Returns:
+            True: If filename is None and a file is open.
+            False: If filename is None and no files are open.
+            None: If filename is not None and the file isn't found.
+            integer: Index of file name in editor stack.
+        """
         if filename is None:
             # Is there any file opened?
             return len(self.data) > 0
@@ -1629,7 +1651,7 @@ def save_as(self, index=None):
         if filename:
             ao_index = self.has_filename(filename)
             # Note: ao_index == index --> saving an untitled file
-            if ao_index and ao_index != index:
+            if ao_index is not None and ao_index != index:
                 if not self.close_file(ao_index):
                     return
                 if ao_index < index:
@@ -1660,7 +1682,7 @@ def save_copy_as(self, index=None):
         if filename:
             ao_index = self.has_filename(filename)
             # Note: ao_index == index --> saving an untitled file
-            if ao_index and ao_index != index:
+            if ao_index is not None and ao_index != index:
                 if not self.close_file(ao_index):
                     return
                 if ao_index < index:


Test patch: