Repository: spyder-ide/spyder

Issue:
The "Next word" and " Previous word" shortcuts do not work as expected
<!--- **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! --->

### Issue Report Checklist

* [x] Searched the [issues page](https://github.com/spyder-ide/spyder/issues?q=is%3Aissue) for similar reports
* [x] Completed the **Problem Description**, **Steps to Reproduce** and **Version** sections below

## Problem Description

The `Next word` and `Previous word` shortcut call the `End of word` and `Start of word` actions instead of `Next word` and `Previous word`.

This error occurs in the 3.x and master branch and is is due to an error [here](https://github.com/spyder-ide/spyder/blob/3.x/spyder/widgets/sourcecode/codeeditor.py#L599) in the code for the 3.x and [here](https://github.com/spyder-ide/spyder/blob/master/spyder/plugins/editor/widgets/codeeditor.py#L546) for master:

The `StartOfWord` and `EndOfWord` variable name should be replaced by `PreviousWord` and `NextWord`

Also, a test should be added to catch this error. This is problematic however because, as explained in #7853, both shortcuts use the `Meta` key by default in the key sequence. So this would have to be changed also as part of fixing this.


Discussion:
I would replace the default key sequence for these 2 shortcuts like this :

Next word: `Meta+Right` -> `Ctrl+Right`
Previous word: `Meta+Left` -> `Ctrl+Left`

Which are the default key sequence for all OSes but macOS:

http://doc.qt.io/qt-5/qkeysequence.html#standard-shortcuts
![image](https://user-images.githubusercontent.com/10170372/45301720-7ea13180-b4df-11e8-99c0-701e2420eb86.png)

Ok, thanks a lot. Please work on this for Spyder 3.
@ccordoba12 
I think we should do 2 PR to fix this if this is possible. 

In a PR based against 3.x, we should only replace the `StartOfWord` and `EndOfWord` variable name by `PreviousWord` and `NextWord`. Due to the situation with builtin shortcuts in 3.x, I'm not sure it is worth doing more than that.

In another PR based against master, I think we should:
1. replace the `StartOfWord` and `EndOfWord` variable name by `PreviousWord` and `NextWord`
2. change the default key sequence for the 2 shortcuts.
3. add a test
> I think we should do 2 PR to fix this if this is possible

Agreed with your plan :+1: 

Resolving patch:
diff --git a/spyder/config/main.py b/spyder/config/main.py
--- a/spyder/config/main.py
+++ b/spyder/config/main.py
@@ -390,8 +390,8 @@
               'editor/next line': "Meta+N",
               'editor/previous char': "Meta+B",
               'editor/next char': "Meta+F",
-              'editor/previous word': "Meta+Left",
-              'editor/next word': "Meta+Right",
+              'editor/previous word': "Ctrl+Left",
+              'editor/next word': "Ctrl+Right",
               'editor/kill to line end': "Meta+K",
               'editor/kill to line start': "Meta+U",
               'editor/yank': 'Meta+Y',
@@ -748,7 +748,7 @@
 #    or if you want to *rename* options, then you need to do a MAJOR update in
 #    version, e.g. from 3.0.0 to 4.0.0
 # 3. You don't need to touch this value if you're just adding a new option
-CONF_VERSION = '46.0.0'
+CONF_VERSION = '46.1.0'
 
 # Main configuration instance
 try:
diff --git a/spyder/plugins/editor/widgets/codeeditor.py b/spyder/plugins/editor/widgets/codeeditor.py
--- a/spyder/plugins/editor/widgets/codeeditor.py
+++ b/spyder/plugins/editor/widgets/codeeditor.py
@@ -543,8 +543,9 @@ def create_shortcut_callbacks(self):
             'editor/next line': self.create_cursor_callback('Down'),
             'editor/previous char': self.create_cursor_callback('Left'),
             'editor/next char': self.create_cursor_callback('Right'),
-            'editor/previous word': self.create_cursor_callback('StartOfWord'),
-            'editor/next word': self.create_cursor_callback('EndOfWord'),
+            'editor/previous word':
+                self.create_cursor_callback('PreviousWord'),
+            'editor/next word': self.create_cursor_callback('NextWord'),
             'editor/kill to line end': self.kill_line_end,
             'editor/kill to line start': self.kill_line_start,
             'editor/yank': self._kill_ring.yank,
@@ -616,7 +617,7 @@ def set_as_clone(self, editor):
         self.eol_chars = editor.eol_chars
         self._apply_highlighter_color_scheme()
 
-    #-----Widget setup and options
+    # ---- Widget setup and options
     def toggle_wrap_mode(self, enable):
         """Enable/disable wrap mode"""
         self.set_wrap_mode('word' if enable else None)


Test patch:
diff --git a/spyder/plugins/editor/widgets/tests/test_shortcuts.py b/spyder/plugins/editor/widgets/tests/test_shortcuts.py
--- a/spyder/plugins/editor/widgets/tests/test_shortcuts.py
+++ b/spyder/plugins/editor/widgets/tests/test_shortcuts.py
@@ -71,6 +71,8 @@ def test_default_keybinding_values():
     assert get_shortcut('editor', 'transform to lowercase') == 'Ctrl+U'
     assert get_shortcut('editor', 'transform to uppercase') == 'Ctrl+Shift+U'
     assert get_shortcut('editor', 'go to line') == 'Ctrl+L'
+    assert get_shortcut('editor', 'next word') == 'Ctrl+Right'
+    assert get_shortcut('editor', 'previous word') == 'Ctrl+Left'
 
 
 def test_start_and_end_of_document_shortcuts(editor_bot):
@@ -224,6 +226,32 @@ def test_transform_to_uppercase_shortcut(editor_bot):
     assert editor.toPlainText() == 'LINE1\nLINE2\nLINE3\nLINE4\n'
 
 
+def test_next_and_previous_word_shortcuts(editor_bot):
+    """
+    Test that the next word and previous word shortcuts are working as
+    expected with the default Spyder keybindings.
+    """
+    editorstack, qtbot = editor_bot
+    editor = editorstack.get_current_editor()
+
+    # Go to the next word 3 times.
+    assert editor.get_cursor_line_column() == (0, 0)
+    qtbot.keyClick(editor, Qt.Key_Right, modifier=Qt.ControlModifier)
+    assert editor.get_cursor_line_column() == (0, 5)
+    qtbot.keyClick(editor, Qt.Key_Right, modifier=Qt.ControlModifier)
+    assert editor.get_cursor_line_column() == (1, 0)
+    qtbot.keyClick(editor, Qt.Key_Right, modifier=Qt.ControlModifier)
+    assert editor.get_cursor_line_column() == (1, 5)
+
+    # Go to the previous word 3 times.
+    qtbot.keyClick(editor, Qt.Key_Left, modifier=Qt.ControlModifier)
+    assert editor.get_cursor_line_column() == (1, 0)
+    qtbot.keyClick(editor, Qt.Key_Left, modifier=Qt.ControlModifier)
+    assert editor.get_cursor_line_column() == (0, 5)
+    qtbot.keyClick(editor, Qt.Key_Left, modifier=Qt.ControlModifier)
+    assert editor.get_cursor_line_column() == (0, 0)
+
+
 @pytest.mark.skipif(sys.platform == 'darwin', reason="Not valid in macOS")
 def test_builtin_shift_del_and_ins(editor_bot):
     """