Repository: spyder-ide/spyder

Issue:
Pressing Enter in the "Replace with" entry of Find/Replace should replace the currently selected text
This comes from this SO post:

http://stackoverflow.com/questions/35662566/how-do-you-use-replace-in-spyder

and here it is an screenshot of the problem

![seleccion_001](https://cloud.githubusercontent.com/assets/365293/22134650/b27dcb6a-de96-11e6-95ea-05e6d0af487f.png)

So when a user has focused the *Replace with* filed and press `Enter`, we should replace *parsed* with *foo* and move to the next occurrence. Currently pressing `Enter` does nothing.

This is very similar to the way *Find* works, i.e. when you press `Enter`, we automatically move users to the next occurrence.

@andfoy, this one is for you :-)


Discussion:

Resolving patch:
diff --git a/spyder/widgets/findreplace.py b/spyder/widgets/findreplace.py
--- a/spyder/widgets/findreplace.py
+++ b/spyder/widgets/findreplace.py
@@ -113,7 +113,8 @@ def __init__(self, parent, enable_replace=False):
         replace_with = QLabel(_("Replace with:"))
         self.replace_text = PatternComboBox(self, adjust_to_minimum=False,
                                             tip=_('Replace string'))
-        
+        self.replace_text.valid.connect(
+                    lambda _: self.replace_find(focus_replace_text=True))
         self.replace_button = create_toolbutton(self,
                                      text=_('Replace/find'),
                                      icon=ima.icon('DialogApplyButton'),
@@ -344,7 +345,7 @@ def find(self, changed=True, forward=True,
             return found
 
     @Slot()
-    def replace_find(self):
+    def replace_find(self, focus_replace_text=False):
         """Replace and find"""
         if (self.editor is not None):
             replace_text = to_text_string(self.replace_text.currentText())
@@ -419,3 +420,5 @@ def replace_find(self):
             self.all_check.setCheckState(Qt.Unchecked)
             if cursor is not None:
                 cursor.endEditBlock()
+            if focus_replace_text:
+                self.replace_text.setFocus()


Test patch:
diff --git a/spyder/widgets/tests/__init__.py b/spyder/widgets/tests/__init__.py
new file mode 100644
diff --git a/spyder/widgets/tests/test_codeeditor_upper_lower.py b/spyder/widgets/tests/test_codeeditor.py
similarity index 100%
rename from spyder/widgets/tests/test_codeeditor_upper_lower.py
rename to spyder/widgets/tests/test_codeeditor.py
diff --git a/spyder/widgets/tests/test_editor.py b/spyder/widgets/tests/test_editor.py
--- a/spyder/widgets/tests/test_editor.py
+++ b/spyder/widgets/tests/test_editor.py
@@ -8,72 +8,136 @@
 Tests for editor.py
 """
 
+# Standard library imports
+try:
+    from unittest.mock import Mock
+except ImportError:
+    from mock import Mock # Python 2
+
 # Third party imports
 import pytest
+from qtpy.QtCore import Qt
 
 # Local imports
 from spyder.utils.fixtures import setup_editor
+from spyder.widgets.editor import EditorStack
+from spyder.widgets.findreplace import FindReplace
+
+# Qt Test Fixtures
+#--------------------------------
+@pytest.fixture
+def base_editor_bot(qtbot):
+    editor_stack = EditorStack(None, [])
+    editor_stack.set_introspector(Mock())
+    editor_stack.set_find_widget(Mock())
+    editor_stack.set_io_actions(Mock(), Mock(), Mock(), Mock())
+    return editor_stack, qtbot
+
+@pytest.fixture
+def editor_bot(base_editor_bot):
+    """
+    Set up EditorStack with CodeEditor containing some Python code.
+    The cursor is at the empty line below the code.
+    Returns tuple with EditorStack and CodeEditor.
+    """
+    editor_stack, qtbot = base_editor_bot
+    text = ('a = 1\n'
+            'print(a)\n'
+            '\n'
+            'x = 2')  # a newline is added at end
+    finfo = editor_stack.new('foo.py', 'utf-8', text)
+    qtbot.addWidget(editor_stack)
+    return editor_stack, finfo.editor, qtbot
+
+@pytest.fixture
+def editor_find_replace_bot(base_editor_bot):
+    editor_stack, qtbot = base_editor_bot
+    text = ('spam bacon\n'
+            'spam sausage\n'
+            'spam egg')
+    finfo = editor_stack.new('spam.py', 'utf-8', text)
+    find_replace = FindReplace(None, enable_replace=True)
+    editor_stack.set_find_widget(find_replace)
+    find_replace.set_editor(finfo.editor)
+    qtbot.addWidget(editor_stack)
+    return editor_stack, finfo.editor, find_replace, qtbot
 
-def test_run_top_line(qtbot):
-    editorStack, editor = setup_editor(qtbot)
+# Tests
+#-------------------------------
+def test_run_top_line(editor_bot):
+    editor_stack, editor, qtbot = editor_bot
     editor.go_to_line(1) # line number is one based
     editor.move_cursor(3)
-    with qtbot.waitSignal(editorStack.exec_in_extconsole) as blocker:
-        editorStack.run_selection()
+    with qtbot.waitSignal(editor_stack.exec_in_extconsole) as blocker:
+        editor_stack.run_selection()
     assert blocker.signal_triggered
     assert blocker.args[0] == 'a = 1'
     # check cursor moves to start of next line; note line number is zero based
-    assert editor.get_cursor_line_column() == (1, 0) 
+    assert editor.get_cursor_line_column() == (1, 0)
 
-def test_run_last_nonempty_line(qtbot):
-    editorStack, editor = setup_editor(qtbot)
+def test_run_last_nonempty_line(editor_bot):
+    editor_stack, editor, qtbot = editor_bot
     editor.go_to_line(4)
-    with qtbot.waitSignal(editorStack.exec_in_extconsole) as blocker:
-        editorStack.run_selection()
+    with qtbot.waitSignal(editor_stack.exec_in_extconsole) as blocker:
+        editor_stack.run_selection()
     assert blocker.signal_triggered
     assert blocker.args[0] == 'x = 2'
     assert editor.get_cursor_line_column() == (4, 0) # check cursor moves down
 
-def test_run_empty_line_in_middle(qtbot):
-    editorStack, editor = setup_editor(qtbot)
+def test_run_empty_line_in_middle(editor_bot):
+    editor_stack, editor, qtbot = editor_bot
     editor.go_to_line(3)
-    with qtbot.assertNotEmitted(editorStack.exec_in_extconsole):
-        editorStack.run_selection()
+    with qtbot.assertNotEmitted(editor_stack.exec_in_extconsole):
+        editor_stack.run_selection()
     assert editor.get_cursor_line_column() == (3, 0) # check cursor moves down
 
+def test_run_last_line_when_empty(editor_bot):
+    editor_stack, editor, qtbot = editor_bot
+    with qtbot.assertNotEmitted(editor_stack.exec_in_extconsole):
+        editor_stack.run_selection()
+    # check cursor doesn't move
+    assert editor.get_cursor_line_column() == (4, 0)
 
-def test_run_last_line_when_empty(qtbot):
-    editorStack, editor = setup_editor(qtbot)
-    with qtbot.assertNotEmitted(editorStack.exec_in_extconsole):
-        editorStack.run_selection()
-    assert editor.get_cursor_line_column() == (4, 0) # check cursor doesn't move
-
-def test_run_last_line_when_nonempty(qtbot):
-    editorStack, editor = setup_editor(qtbot)
+def test_run_last_line_when_nonempty(editor_bot):
+    editor_stack, editor, qtbot = editor_bot
     editor.stdkey_backspace() # delete empty line at end
     old_text = editor.toPlainText()
-    with qtbot.waitSignal(editorStack.exec_in_extconsole) as blocker:
-        editorStack.run_selection()
+    with qtbot.waitSignal(editor_stack.exec_in_extconsole) as blocker:
+        editor_stack.run_selection()
     assert blocker.signal_triggered
     assert blocker.args[0] == 'x = 2'
     expected_new_text = old_text + editor.get_line_separator()
-    assert editor.toPlainText() == expected_new_text # check blank line got added
+    # check blank line got added
+    assert editor.toPlainText() == expected_new_text
     assert editor.get_cursor_line_column() == (4, 0) # check cursor moves down
 
 def test_find_replace_case_sensitive(qtbot):
-    editorStack, editor = setup_editor(qtbot)
-    editorStack.find_widget.case_button.setChecked(True)
+    editor_stack, editor = setup_editor(qtbot)
+    editor_stack.find_widget.case_button.setChecked(True)
     text = ' test \nTEST \nTest \ntesT '
     editor.set_text(text)
-    editorStack.find_widget.search_text.add_text('test')
-    editorStack.find_widget.replace_text.add_text('pass')
-    editorStack.find_widget.replace_find()
-    editorStack.find_widget.replace_find()
-    editorStack.find_widget.replace_find()
-    editorStack.find_widget.replace_find()
+    editor_stack.find_widget.search_text.add_text('test')
+    editor_stack.find_widget.replace_text.add_text('pass')
+    editor_stack.find_widget.replace_find()
+    editor_stack.find_widget.replace_find()
+    editor_stack.find_widget.replace_find()
+    editor_stack.find_widget.replace_find()
     editor_text = editor.toPlainText()
     assert editor_text == ' pass \nTEST \nTest \ntesT '
 
+def test_replace_current_selected_line(editor_find_replace_bot):
+    editor_stack, editor, finder, qtbot = editor_find_replace_bot
+    expected_new_text = ('ham bacon\n'
+                         'spam sausage\n'
+                         'spam egg')
+    old_text = editor.toPlainText()
+    finder.show()
+    finder.show_replace()
+    qtbot.keyClicks(finder.search_text, 'spam')
+    qtbot.keyClicks(finder.replace_text, 'ham')
+    qtbot.keyPress(finder.replace_text, Qt.Key_Return)
+    assert editor.toPlainText()[0:-1] == expected_new_text
+
 
 if __name__ == "__main__":
     pytest.main()