Repository: spyder-ide/spyder

Issue:
IndexError for some shortcuts in the Editor
## Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

pless shortcut key
ctrl + alt + ->
ctrl + alt + <-

### Traceback
```python-traceback
  File "C:\Program Files\Anaconda3\lib\site-packages\spyder\plugins\editor.py", line 2469, in go_to_next_cursor_position
    self.__move_cursor_position(1)
  File "C:\Program Files\Anaconda3\lib\site-packages\spyder\plugins\editor.py", line 2440, in __move_cursor_position
    filename, _position = self.cursor_pos_history[self.cursor_pos_index]
IndexError: list index out of range
WARNING:spyder.widgets.github.backend:failed to send bug report on github. response={'code': 403, 'json': {'message': 'At least one email address must be verified to do that.', 'documentation_url': 'https://help.github.com/articles/adding-an-email-address-to-your-github-account'}}
```

## Versions

* Spyder version: 4.0.0b1 
* Python version: 3.5.2
* Qt version: 5.6.2
* PyQt5 version: 5.6
* Operating System: Windows 7

### Dependencies

```
pyflakes >=0.6.0  :  1.3.0 (OK)
pycodestyle >=2.3 :  2.4.0 (OK)
pygments >=2.0    :  2.1.3 (OK)
sphinx >=0.6.6    :  1.4.6 (OK)
rope >=0.9.4      :  0.9.4-1 (OK)
jedi >=0.11.0     :  0.13.1 (OK)
nbconvert >=4.0   :  4.2.0 (OK)
pandas >=0.13.1   :  0.18.1 (OK)
numpy >=1.7       :  1.11.1 (OK)
sympy >=0.7.3     :  1.0 (OK)
cython >=0.21     :  0.24.1 (OK)
qtconsole >=4.2.0 :  4.2.1 (OK)
IPython >=4.0     :  5.1.0 (OK)
matplotlib >=2.0.0:  1.5.3 (NOK)
pylint >=0.25     :  1.5.4 (OK)
```



Discussion:
@jnsebgosselin, is this still happening in master?
Yes. Additional notes are shown below.
0: run spyder
1: create new file
2: pless ctrl + alt + ->
3: pless ctrl + alt + <-
4: open the issue reporter
> @jnsebgosselin, is this still happening in master?

@ccordoba12 yes

@ryo-123 Thank you very much for your report. I'll take a look at it shortly.

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
@@ -2251,21 +2251,24 @@ def go_to_last_edit_location(self):
                     editor.set_cursor_position(position)
 
     def __move_cursor_position(self, index_move):
+        """
+        Move the cursor position forward or backward in the cursor
+        position history by the specified index increment.
+        """
         if self.cursor_pos_index is None:
             return
         filename, _position = self.cursor_pos_history[self.cursor_pos_index]
-        self.cursor_pos_history[self.cursor_pos_index] = ( filename,
-                            self.get_current_editor().get_position('cursor') )
+        self.cursor_pos_history[self.cursor_pos_index] = (
+            filename, self.get_current_editor().get_position('cursor'))
         self.__ignore_cursor_position = True
         old_index = self.cursor_pos_index
-        self.cursor_pos_index = min([
-                                     len(self.cursor_pos_history)-1,
-                                     max([0, self.cursor_pos_index+index_move])
-                                     ])
+        self.cursor_pos_index = min(len(self.cursor_pos_history) - 1,
+                                    max(0, self.cursor_pos_index + index_move))
         filename, position = self.cursor_pos_history[self.cursor_pos_index]
-        if not osp.isfile(filename):
+        filenames = self.get_current_editorstack().get_filenames()
+        if not osp.isfile(filename) and filename not in filenames:
             self.cursor_pos_history.pop(self.cursor_pos_index)
-            if self.cursor_pos_index < old_index:
+            if self.cursor_pos_index <= old_index:
                 old_index -= 1
             self.cursor_pos_index = old_index
         else:


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
@@ -59,7 +59,7 @@ def test_setup_open_files(editor_plugin_open_files, last_focused_filename,
 def test_setup_open_files_cleanprefs(editor_plugin_open_files):
     """Test that Editor successfully opens files if layout is not defined.
 
-    Regression test for #8458 .
+    Regression test for spyder-ide/spyder#8458.
     """
     editor_factory = editor_plugin_open_files
     editor, expected_filenames, expected_current_filename = (
@@ -189,5 +189,71 @@ def test_closing_editor_plugin_stops_autosave_timer(editor_plugin):
     assert not editor.autosave.timer.isActive()
 
 
+def test_go_to_prev_next_cursor_position(editor_plugin, python_files):
+    """
+    Test the previous and next cursor position feature of the Editor.
+
+    Regression test for spyder-ide/spyder#8000.
+    """
+    filenames, tmpdir = python_files
+    editorstack = editor_plugin.get_current_editorstack()
+
+    expected_cursor_pos_history = []
+    assert editor_plugin.cursor_pos_history == expected_cursor_pos_history
+
+    # Load the Python test files (4).
+    editor_plugin.load(filenames)
+    # Open a new file.
+    editor_plugin.new()
+    # Go to the third file.
+    editorstack.set_stack_index(2)
+    # Move the cursor within the third file. Note that this new position is
+    # not added to the cursor position history.
+    editorstack.get_current_editor().set_cursor_position(5)
+
+    # Note that we use the filenames from the editor to define the expected
+    # results because those returned by the python_files fixture are
+    # normalized, so this would cause issues when assessing the results.
+    filenames = editor_plugin.get_filenames()
+    expected_cursor_pos_history = [
+        (filenames[0], 0),
+        (filenames[4], len(editorstack.data[4].get_source_code())),
+        (filenames[2], 0)
+        ]
+    assert editor_plugin.cursor_pos_history == expected_cursor_pos_history
+
+    # Navigate to previous and next cursor positions.
+
+    # The last entry in the cursor position history is overriden by the
+    # current cursor position when going to previous or next cursor position,
+    # so we need to update the last item of the expected_cursor_pos_history.
+    expected_cursor_pos_history[-1] = (filenames[2], 5)
+
+    cursor_index_moves = [-1, 1, 1, -1, -1, -1, 1, -1]
+    expected_cursor_pos_indexes = [1, 2, 2, 1, 0, 0, 1, 0]
+    for move, index in zip(cursor_index_moves, expected_cursor_pos_indexes):
+        if move == -1:
+            editor_plugin.go_to_previous_cursor_position()
+        elif move == 1:
+            editor_plugin.go_to_next_cursor_position()
+
+        assert editor_plugin.cursor_pos_index == index
+        assert (editor_plugin.get_current_filename(),
+                editor_plugin.get_current_editor().get_position('cursor')
+                ) == expected_cursor_pos_history[index]
+    assert editor_plugin.cursor_pos_history == expected_cursor_pos_history
+
+    # So we are now expected to be at index 0 in the cursor position history.
+    # From there, we go to the fourth file.
+    editorstack.set_stack_index(3)
+
+    # We expect that our last action caused the cursor position history to
+    # be stripped from the current cursor position index and that the
+    # new cursor position is added at the end of the cursor position history.
+    expected_cursor_pos_history = expected_cursor_pos_history[:1]
+    expected_cursor_pos_history.append((filenames[3], 0))
+    assert editor_plugin.cursor_pos_history == expected_cursor_pos_history
+
+
 if __name__ == "__main__":
     pytest.main(['-x', osp.basename(__file__), '-vv', '-rw'])