Repository: spyder-ide/spyder

Issue:
Error when passing a regexp to the findreplace widget
## Description

**What steps will reproduce the problem?**

1. 
2. 
3. 

**What is the expected output? What do you see instead?**


**Please provide any additional information below**

  File "/Users/ymurakumo/anaconda3/lib/python3.6/site-packages/spyder/widgets/findreplace.py", line 557, in replace_find_selection
    replacement = re_pattern.sub(replace_text, selected_text)
  File "/Users/ymurakumo/anaconda3/lib/python3.6/re.py", line 326, in _subx
    template = _compile_repl(template, pattern)
  File "/Users/ymurakumo/anaconda3/lib/python3.6/re.py", line 317, in _compile_repl
    return sre_parse.parse_template(repl, pattern)
  File "/Users/ymurakumo/anaconda3/lib/python3.6/sre_parse.py", line 879, in parse_template
    s = Tokenizer(source)
  File "/Users/ymurakumo/anaconda3/lib/python3.6/sre_parse.py", line 231, in __init__
    self.__next()
  File "/Users/ymurakumo/anaconda3/lib/python3.6/sre_parse.py", line 245, in __next
    self.string, len(self.string) - 1) from None
sre_constants.error: bad escape (end of pattern) at position 0


## Version and main components

* Spyder Version: 3.2.6 
* Python Version: 3.6.3
* Qt Versions: 5.6.2, PyQt5 5.6 on Darwin

## Dependencies
```
pyflakes >=0.6.0 :  1.6.0 (OK)
pycodestyle >=2.3:  2.3.1 (OK)
pygments >=2.0   :  2.2.0 (OK)
pandas >=0.13.1  :  0.22.0 (OK)
numpy >=1.7      :  1.14.0 (OK)
sphinx >=0.6.6   :  1.6.6 (OK)
rope >=0.9.4     :  0.10.7 (OK)
jedi >=0.9.0     :  0.11.1 (OK)
psutil >=0.3     :  5.4.3 (OK)
nbconvert >=4.0  :  5.3.1 (OK)
sympy >=0.7.3    :  1.1.1 (OK)
cython >=0.21    :  0.27.3 (OK)
qtconsole >=4.2.0:  4.3.1 (OK)
IPython >=4.0    :  6.2.1 (OK)
pylint >=0.25    :  1.8.2 (OK)

```



Discussion:
Thanks for reporting. We'll try to fix this in our next release.
Thanks. I'm looking forward to fix this bug on next Spyder version and update one.
So its easy to reproduce, can you provide an example of a regex which causes this error? Thanks.
Leaving this to do after 3.3 because we don't a clear to reproduce it.

Resolving patch:
diff --git a/spyder/widgets/findreplace.py b/spyder/widgets/findreplace.py
--- a/spyder/widgets/findreplace.py
+++ b/spyder/widgets/findreplace.py
@@ -444,11 +444,18 @@ def replace_find(self, focus_replace_text=False, replace_all=False):
             replace_text = to_text_string(self.replace_text.currentText())
             search_text = to_text_string(self.search_text.currentText())
             re_pattern = None
+
+            # Check regexp before proceeding
             if self.re_button.isChecked():
                 try:
                     re_pattern = re.compile(search_text)
+                    # Check if replace_text can be substituted in re_pattern
+                    # Fixes issue #7177
+                    re_pattern.sub(replace_text, '')
                 except re.error:
-                    return  # do nothing with an invalid regexp
+                    # Do nothing with an invalid regexp
+                    return
+
             case = self.case_button.isChecked()
             first = True
             cursor = None
@@ -544,10 +551,16 @@ def replace_find_selection(self, focus_replace_text=False):
                 replace_text = re.escape(replace_text)
             if words:  # match whole words only
                 pattern = r'\b{pattern}\b'.format(pattern=pattern)
+
+            # Check regexp before proceeding
             try:
                 re_pattern = re.compile(pattern, flags=re_flags)
+                # Check if replace_text can be substituted in re_pattern
+                # Fixes issue #7177
+                re_pattern.sub(replace_text, '')
             except re.error as e:
-                return  # do nothing with an invalid regexp
+                # Do nothing with an invalid regexp
+                return
 
             selected_text = to_text_string(self.editor.get_selected_text())
             replacement = re_pattern.sub(replace_text, selected_text)


Test patch:
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
@@ -342,10 +342,25 @@ def test_replace_invalid_regex(editor_find_replace_bot):
     old_text = editor.toPlainText()
     finder.show()
     finder.show_replace()
+
+    # Test with invalid search_text and valid replace_text
     qtbot.keyClicks(finder.search_text, '\\')
     qtbot.keyClicks(finder.replace_text, 'anything')
+
     if not finder.re_button.isChecked():
         qtbot.mouseClick(finder.re_button, Qt.LeftButton)
+
+    qtbot.mouseClick(finder.replace_button, Qt.LeftButton)
+    assert editor.toPlainText() == old_text
+    qtbot.mouseClick(finder.replace_sel_button, Qt.LeftButton)
+    assert editor.toPlainText() == old_text
+    qtbot.mouseClick(finder.replace_all_button, Qt.LeftButton)
+    assert editor.toPlainText() == old_text
+
+    # Test with valid search_text and invalid replace_text
+    qtbot.keyClicks(finder.search_text, 'anything')
+    qtbot.keyClicks(finder.replace_text, '\\')
+
     qtbot.mouseClick(finder.replace_button, Qt.LeftButton)
     assert editor.toPlainText() == old_text
     qtbot.mouseClick(finder.replace_sel_button, Qt.LeftButton)