Repository: spyder-ide/spyder

Issue:
Spyder won't load previously open files from any project with a clean prefs file
<!--- **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! --->


## Problem Description

As originally reported in #8415 and thought to be due to that ~~issue~~ PR (I couldn't repro it in ``master`` at the time), and experienced by the PR contributor as well. From a clean prefs file (``--safe-mode``), I get the below error when trying to open any project; the project opens but without its previous files open and a completely blank Editor. If I then close the project, the previously recorded open files are wiped. This occurs both on opening a project with, and without one open, and on both new and previous projects. Also, it even happens on closing a project if there were any Editor files open previously, since Spyder will run into the same error trying to restore the previously open files in the Editor.

### What steps reproduce the problem?

1. Open Spyder in ``--safe-mode``
2. Open an existing project with files in the workspace, or create, save and re-open a new one

### Paste Traceback/Error Below (if applicable)
<!--- Copy from error dialog or View > Panes > Internal Console --->

```python-traceback
  File "C:\Users\C. A. M. Gerlach\Documents\dev\SpyderDev\spyder\spyder\plugins\projects\plugin.py", line 147, in <lambda>
    lambda v: self.main.editor.setup_open_files())
  File "C:\Users\C. A. M. Gerlach\Documents\dev\SpyderDev\spyder\spyder\plugins\editor\plugin.py", line 2932, in setup_open_files
    is_vertical, cfname, clines = layout.get('splitsettings')[0]
AttributeError: 'NoneType' object has no attribute 'get'
```

## Proposed Solution

It seems to be that the ``layout_settings`` option for the Editor is either ``None`` or not present, which results in ``layout`` being ``None``. I already implemented and tested an if/else around the problem section that required ``layout``  to fix this, loading the files with the default focuswhich resulted in the projects and their saved workspaces being opened, switched, saved and closed successfully. I can go ahead with a PR, but is my understanding correct? Is there a better way to handle/implement this?

## 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: master
* Python version: 3.6.7
* Qt version: 5.9.6
* PyQt version: 5.9.2
* Operating System name/version: Windows 10

Spyder won't load previously open files from any project with a clean prefs file
<!--- **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! --->


## Problem Description

As originally reported in #8415 and thought to be due to that ~~issue~~ PR (I couldn't repro it in ``master`` at the time), and experienced by the PR contributor as well. From a clean prefs file (``--safe-mode``), I get the below error when trying to open any project; the project opens but without its previous files open and a completely blank Editor. If I then close the project, the previously recorded open files are wiped. This occurs both on opening a project with, and without one open, and on both new and previous projects. Also, it even happens on closing a project if there were any Editor files open previously, since Spyder will run into the same error trying to restore the previously open files in the Editor.

### What steps reproduce the problem?

1. Open Spyder in ``--safe-mode``
2. Open an existing project with files in the workspace, or create, save and re-open a new one

### Paste Traceback/Error Below (if applicable)
<!--- Copy from error dialog or View > Panes > Internal Console --->

```python-traceback
  File "C:\Users\C. A. M. Gerlach\Documents\dev\SpyderDev\spyder\spyder\plugins\projects\plugin.py", line 147, in <lambda>
    lambda v: self.main.editor.setup_open_files())
  File "C:\Users\C. A. M. Gerlach\Documents\dev\SpyderDev\spyder\spyder\plugins\editor\plugin.py", line 2932, in setup_open_files
    is_vertical, cfname, clines = layout.get('splitsettings')[0]
AttributeError: 'NoneType' object has no attribute 'get'
```

## Proposed Solution

It seems to be that the ``layout_settings`` option for the Editor is either ``None`` or not present, which results in ``layout`` being ``None``. I already implemented and tested an if/else around the problem section that required ``layout``  to fix this, loading the files with the default focuswhich resulted in the projects and their saved workspaces being opened, switched, saved and closed successfully. I can go ahead with a PR, but is my understanding correct? Is there a better way to handle/implement this?

## 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: master
* Python version: 3.6.7
* Qt version: 5.9.6
* PyQt version: 5.9.2
* Operating System name/version: Windows 10



Discussion:
> As originally reported in #8415 and thought to be due to that issue (I couldn't repro it in master at the time)

Of which issue are you talking about exactly @CAM-Gerlach? 

Also does this happen when not in safe mode?
My confusion is that, at some point it was hinted in PR #8415 that the error was maybe related to Issue #8375. I do not think what you reported here is related to Issue #8375.
> Of which issue are you talking about exactly @CAM-Gerlach?

Sorry, I meant "PR" (the one I linked). There was another error raised on opening a project in a VCS repo root that *was* caused by it and I couldn't get this error to repro in my testing on master either (since it wasn't in safe mode, I don't believe).

> Also does this happen when not in safe mode?

Not in my most recent testing at least, and the PR author on that one found that as well.

> My confusion is that, at some point it was hinted in PR #8415 that the error was maybe related to Issue #8375

Right, its not, but before I investigated I was uncertain since the symptoms were superficially similar (project sessions were not getting restored after closing/switching projects) and I hadn't noticed it before.
> Also does this happen when not in safe mode?

Ok, I can reproduce this too, it happens if you start Spyder with a clean .ini and try to open a project. Being in safe-mode or not does not seem to be relevant.

I think your explanation and solution is reasonable. I would open a PR to implement it if I were you.
> I think your explanation and solution is reasonable. I would open a PR to implement it if I were you.

I agree with @jnsebgosselin about this. And don't worry to create a test for this, the solution seems quite straightforward.
> it happens if you start Spyder with a clean .ini and try to open a project. Being in safe-mode or not does not seem to be relevant.

Right, that's what I presumed, since as I mentioned the observed criterion was

> a clean prefs file (--safe-mode)

> ...and while testing this, I wiped two of my Spyder projects, nice XD

Don't say I didn't warn you :P And I learned that from experiance testing this on pretty much *all* of my Spyder projects, heh.

But in all seriousness, <kbd>F</kbd>

> If I then close the project, the previously recorded open files are wiped. 
I'll go ahead with a PR, then. Thanks @ccordoba12 and @jnsebgosselin !

> As originally reported in #8415 and thought to be due to that issue (I couldn't repro it in master at the time)

Of which issue are you talking about exactly @CAM-Gerlach? 

Also does this happen when not in safe mode?
My confusion is that, at some point it was hinted in PR #8415 that the error was maybe related to Issue #8375. I do not think what you reported here is related to Issue #8375.
> Of which issue are you talking about exactly @CAM-Gerlach?

Sorry, I meant "PR" (the one I linked). There was another error raised on opening a project in a VCS repo root that *was* caused by it and I couldn't get this error to repro in my testing on master either (since it wasn't in safe mode, I don't believe).

> Also does this happen when not in safe mode?

Not in my most recent testing at least, and the PR author on that one found that as well.

> My confusion is that, at some point it was hinted in PR #8415 that the error was maybe related to Issue #8375

Right, its not, but before I investigated I was uncertain since the symptoms were superficially similar (project sessions were not getting restored after closing/switching projects) and I hadn't noticed it before.
> Also does this happen when not in safe mode?

Ok, I can reproduce this too, it happens if you start Spyder with a clean .ini and try to open a project. Being in safe-mode or not does not seem to be relevant.

I think your explanation and solution is reasonable. I would open a PR to implement it if I were you.
> I think your explanation and solution is reasonable. I would open a PR to implement it if I were you.

I agree with @jnsebgosselin about this. And don't worry to create a test for this, the solution seems quite straightforward.
> it happens if you start Spyder with a clean .ini and try to open a project. Being in safe-mode or not does not seem to be relevant.

Right, that's what I presumed, since as I mentioned the observed criterion was

> a clean prefs file (--safe-mode)

> ...and while testing this, I wiped two of my Spyder projects, nice XD

Don't say I didn't warn you :P And I learned that from experiance testing this on pretty much *all* of my Spyder projects, heh.

But in all seriousness, <kbd>F</kbd>

> If I then close the project, the previously recorded open files are wiped. 
I'll go ahead with a PR, then. Thanks @ccordoba12 and @jnsebgosselin !


Resolving patch:
diff --git a/spyder/plugins/editor/plugin.py b/spyder/plugins/editor/plugin.py
--- a/spyder/plugins/editor/plugin.py
+++ b/spyder/plugins/editor/plugin.py
@@ -2904,25 +2904,31 @@ def setup_open_files(self):
 
         if filenames and any([osp.isfile(f) for f in filenames]):
             layout = self.get_option('layout_settings', None)
-            is_vertical, cfname, clines = layout.get('splitsettings')[0]
-            if cfname in filenames:
-                index = filenames.index(cfname)
-                # First we load the last focused file.
-                self.load(filenames[index], goto=clines[index], set_focus=True)
-                # Then we load the files located to the left of the last
-                # focused file in the tabbar, while keeping the focus on
-                # the last focused file.
-                if index > 0:
-                    self.load(filenames[index::-1], goto=clines[index::-1],
-                              set_focus=False, add_where='start')
-                # Finally we load the files located to the right of the last
-                # focused file in the tabbar, while keeping the focus on
-                # the last focused file.
-                if index < (len(filenames) - 1):
-                    self.load(filenames[index+1:], goto=clines[index:],
-                              set_focus=False, add_where='end')
+            # Check if no saved layout settings exist, e.g. clean prefs file
+            # If not, load with default focus/layout, to fix issue #8458 .
+            if layout:
+                is_vertical, cfname, clines = layout.get('splitsettings')[0]
+                if cfname in filenames:
+                    index = filenames.index(cfname)
+                    # First we load the last focused file.
+                    self.load(filenames[index], goto=clines[index],
+                              set_focus=True)
+                    # Then we load the files located to the left of the last
+                    # focused file in the tabbar, while keeping the focus on
+                    # the last focused file.
+                    if index > 0:
+                        self.load(filenames[index::-1], goto=clines[index::-1],
+                                  set_focus=False, add_where='start')
+                    # Finally we load the files to the right of the last
+                    # focused file in the tabbar, while keeping the focus on
+                    # the last focused file.
+                    if index < (len(filenames) - 1):
+                        self.load(filenames[index+1:], goto=clines[index:],
+                                  set_focus=False, add_where='end')
+                else:
+                    self.load(filenames, goto=clines)
             else:
-                self.load(filenames, goto=clines)
+                self.load(filenames)
 
             if self.__first_open_files_setup:
                 self.__first_open_files_setup = False


Test patch:
diff --git a/spyder/plugins/editor/tests/test_plugin.py b/spyder/plugins/editor/tests/test_plugin.py
--- a/spyder/plugins/editor/tests/test_plugin.py
+++ b/spyder/plugins/editor/tests/test_plugin.py
@@ -1,29 +1,33 @@
 # -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Copyright (c) 2009- Spyder Project Contributors
 #
-# Copyright © Spyder Project Contributors
-# Licensed under the terms of the MIT License
-#
+# Distributed under the terms of the MIT License
+# (see spyder/__init__.py for details)
+# -----------------------------------------------------------------------------
+
 """Tests for the Editor plugin."""
 
 # Standard library imports
+import os
 import os.path as osp
 import shutil
-
-# Third party imports
-import pytest
-
 try:
     from unittest.mock import Mock
 except ImportError:
     from mock import Mock  # Python 2
 
+# Third party imports
+import pytest
 from qtpy.QtWidgets import QMainWindow
 
 # Local imports
 from spyder.utils.qthelpers import qapplication
 
 
-# ---- Qt Test Fixtures
+# =============================================================================
+# ---- Fixtures
+# =============================================================================
 @pytest.fixture
 def setup_editor(qtbot, monkeypatch):
     """Set up the Editor plugin."""
@@ -54,6 +58,7 @@ def get_spyder_pythonpath(*args):
     window.show()
 
     yield editor
+    editor.close()
 
 
 @pytest.fixture(scope="module")
@@ -72,6 +77,46 @@ def python_files(tmpdir_factory):
     return filenames, tmpdir
 
 
+@pytest.fixture
+def editor_open_files(request, setup_editor, python_files):
+    """
+    Setup an Editor with a set of open files, given a past file in focus.
+
+    If no/None ``last_focused_filename`` is passed, the ``"layout_settings"``
+    key is not included in the options dict.
+    If no/None ``expected_current_filename``, is assumed to be the first file.
+    """
+    def _get_editor_open_files(last_focused_filename,
+                               expected_current_filename):
+        editor = setup_editor
+        expected_filenames, tmpdir = python_files
+        if expected_current_filename is None:
+            expected_current_filename = expected_filenames[0]
+        expected_current_filename = osp.join(tmpdir, expected_current_filename)
+        options_dict = {
+            'filenames': expected_filenames,
+            'max_recent_files': 20,
+            }
+        if last_focused_filename is not None:
+            splitsettings = [(False,
+                              osp.join(tmpdir, last_focused_filename),
+                              [1] * len(expected_filenames))]
+            layout_dict = {'layout_settings': {'splitsettings': splitsettings}}
+            options_dict.update(layout_dict)
+
+        def get_option(option, default=None):
+            return options_dict.get(option)
+        editor.get_option = get_option
+
+        editor.setup_open_files()
+        return editor, expected_filenames, expected_current_filename
+
+    return _get_editor_open_files
+
+
+# =============================================================================
+# ---- Tests
+# =============================================================================
 def test_basic_initialization(setup_editor):
     """Test Editor plugin initialization."""
     editor = setup_editor
@@ -87,28 +132,17 @@ def test_basic_initialization(setup_editor):
      ('file2.py', 'file2.py'),
      ('file4.py', 'file4.py')
      ])
-def test_setup_open_files(setup_editor, last_focused_filename,
-                          expected_current_filename, python_files):
+def test_setup_open_files(editor_open_files, last_focused_filename,
+                          expected_current_filename):
     """Test Editor plugin open files setup.
 
     Test that the file order is preserved during the Editor plugin setup and
     that the current file correspond to the last focused file.
     """
-    editor = setup_editor
-    expected_filenames, tmpdir = python_files
-    expected_current_filename = osp.join(tmpdir, expected_current_filename)
-
-    def get_option(option, default=None):
-        splitsettings = [(False,
-                          osp.join(tmpdir, last_focused_filename),
-                          [1, 1, 1, 1])]
-        return {'layout_settings': {'splitsettings': splitsettings},
-                'filenames': expected_filenames,
-                'max_recent_files': 20
-                }.get(option)
-    editor.get_option = get_option
-
-    editor.setup_open_files()
+    editor_factory = editor_open_files
+    editor, expected_filenames, expected_current_filename = (
+        editor_factory(last_focused_filename, expected_current_filename))
+
     current_filename = editor.get_current_editorstack().get_current_filename()
     current_filename = osp.normcase(current_filename)
     assert current_filename == expected_current_filename
@@ -117,6 +151,23 @@ def get_option(option, default=None):
     assert filenames == expected_filenames
 
 
+def test_setup_open_files_cleanprefs(editor_open_files):
+    """Test that Editor successfully opens files if layout is not defined.
+
+    Regression test for #8458 .
+    """
+    editor_factory = editor_open_files
+    editor, expected_filenames, expected_current_filename = (
+        editor_factory(None, None))
+
+    filenames = editor.get_current_editorstack().get_filenames()
+    filenames = [osp.normcase(f) for f in filenames]
+    assert filenames == expected_filenames
+    current_filename = editor.get_current_editorstack().get_current_filename()
+    current_filename = osp.normcase(current_filename)
+    assert current_filename == expected_current_filename
+
+
 def test_renamed_tree(setup_editor, mocker):
     """Test editor.renamed_tree().
 
@@ -167,5 +218,4 @@ def test_no_template(setup_editor):
 
 
 if __name__ == "__main__":
-    import os
     pytest.main(['-x', os.path.basename(__file__), '-vv', '-rw'])