Repository: spyder-ide/spyder

Issue:
`re.error` when running some searches in the Find/Replace widget
## Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

ctrl+f 
to replace all `\u00b-2` with `$\mathregular{m^-2}$` 

### Traceback
```python-traceback
Traceback (most recent call last):
  File "D:\Python\lib\sre_parse.py", line 1051, in parse_template
    this = chr(ESCAPES[this][1])
KeyError: '\\m'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "D:\Python\lib\site-packages\spyder\widgets\findreplace.py", line 692, in replace_find_all
    cursor.insertText(re_pattern.sub(replace_text, text))
  File "D:\Python\lib\re.py", line 326, in _subx
    template = _compile_repl(template, pattern)
  File "D:\Python\lib\re.py", line 317, in _compile_repl
    return sre_parse.parse_template(repl, pattern)
  File "D:\Python\lib\sre_parse.py", line 1054, in parse_template
    raise s.error('bad escape %s' % this, len(this))
re.error: bad escape \m at position 1
```

## Versions

* Spyder version: 5.4.1  (conda)
* Python version: 3.10.9 64-bit
* Qt version: 5.15.2
* PyQt5 version: 5.15.7
* Operating System: Windows 10

### Dependencies

```
# Mandatory:
atomicwrites >=1.2.0          :  1.4.0 (OK)
chardet >=2.0.0               :  4.0.0 (OK)
cloudpickle >=0.5.0           :  2.0.0 (OK)
cookiecutter >=1.6.0          :  1.7.3 (OK)
diff_match_patch >=20181111   :  20200713 (OK)
intervaltree >=3.0.2          :  3.1.0 (OK)
IPython >=7.31.1;<9.0.0       :  8.12.0 (OK)
jedi >=0.17.2;<0.19.0         :  0.18.1 (OK)
jellyfish >=0.7               :  0.9.0 (OK)
jsonschema >=3.2.0            :  4.17.3 (OK)
keyring >=17.0.0              :  23.4.0 (OK)
nbconvert >=4.0               :  6.5.4 (OK)
numpydoc >=0.6.0              :  1.5.0 (OK)
paramiko >=2.4.0              :  2.8.1 (OK)
parso >=0.7.0;<0.9.0          :  0.8.3 (OK)
pexpect >=4.4.0               :  4.8.0 (OK)
pickleshare >=0.4             :  0.7.5 (OK)
psutil >=5.3                  :  5.9.0 (OK)
pygments >=2.0                :  2.11.2 (OK)
pylint >=2.5.0;<3.0           :  2.16.2 (OK)
pylint_venv >=2.1.1           :  2.3.0 (OK)
pyls_spyder >=0.4.0           :  0.4.0 (OK)
pylsp >=1.7.0;<1.8.0          :  1.7.1 (OK)
pylsp_black >=1.2.0           :  1.2.1 (OK)
qdarkstyle >=3.0.2;<3.1.0     :  3.0.2 (OK)
qstylizer >=0.2.2             :  0.2.2 (OK)
qtawesome >=1.2.1             :  1.2.2 (OK)
qtconsole >=5.4.0;<5.5.0      :  5.4.2 (OK)
qtpy >=2.1.0                  :  2.2.0 (OK)
rtree >=0.9.7                 :  1.0.1 (OK)
setuptools >=49.6.0           :  65.6.3 (OK)
sphinx >=0.6.6                :  5.0.2 (OK)
spyder_kernels >=2.4.1;<2.5.0 :  2.4.3 (OK)
textdistance >=4.2.0          :  4.2.1 (OK)
three_merge >=0.1.1           :  0.1.1 (OK)
watchdog >=0.10.3             :  2.1.6 (OK)
zmq >=22.1.0                  :  23.2.0 (OK)

# Optional:
cython >=0.21                 :  None (NOK)
matplotlib >=3.0.0            :  3.7.0 (OK)
numpy >=1.7                   :  1.23.5 (OK)
pandas >=1.1.1                :  1.5.3 (OK)
scipy >=0.17.0                :  1.10.0 (OK)
sympy >=0.7.3                 :  1.11.1 (OK)
```



Discussion:
Hey @n100820a, thanks for reporting. Could you post a screenshot of what exactly you wrote in the search and replace fields (in case you did, that's not clear from your description) that generated this error? Please don't forget to include the buttons that you toggled as well.

That will help us to reproduce your error on our side, which will make easier for us to solve it.
I have experienced a similar error using search and replace. Spyder crashes when searching and replacing all occurrences of the following:
- Code to be searched: `plt.ylabel(r'$\Psi(t) = [\psi, \phi]^\mathrm{T}$')`
- Replacement code: `plt.ylabel(r'$$\Psi(t) = [\psi \; \phi]^\mathrm{T}$')`
Here is the Traceback:
```
Traceback (most recent call last):
  File "sre_parse.py", line 1039, in parse_template
KeyError: '\\P'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Program Files\Spyder\pkgs\spyder\widgets\findreplace.py", line 697, in replace_find_all
    cursor.insertText(re_pattern.sub(replace_text, text))
  File "re.py", line 327, in _subx
  File "re.py", line 318, in _compile_repl
  File "sre_parse.py", line 1042, in parse_template
re.error: bad escape \P at position 14 (line 1, column 15)
```
Thanks @FormalSymmetry for the additional info! It seems we need to escape the text to be replaced when using regular expressions.

We'll take a look at this in our 5.5.0 version, to be released in a couple of months.
Happy to have helped, I appreciate your work! @ccordoba12 

Resolving patch:
diff --git a/spyder/widgets/findreplace.py b/spyder/widgets/findreplace.py
--- a/spyder/widgets/findreplace.py
+++ b/spyder/widgets/findreplace.py
@@ -671,12 +671,19 @@ def replace_find_all(self):
         """Replace and find all matching occurrences"""
         if self.editor is None:
             return
-        replace_text = to_text_string(self.replace_text.currentText())
-        search_text = to_text_string(self.search_text.currentText())
+
+        replace_text = str(self.replace_text.currentText())
+        search_text = str(self.search_text.currentText())
         re_pattern = None
         case = self.case_button.isChecked()
         re_flags = re.MULTILINE if case else re.IGNORECASE | re.MULTILINE
         re_enabled = self.re_button.isChecked()
+
+        # Escape backslashes present in replace_text to avoid an error when
+        # using the regexp pattern below to perform the substitution.
+        # Fixes spyder-ide/spyder#21007.
+        replace_text = replace_text.replace('\\', r'\\')
+
         # Check regexp before proceeding
         if re_enabled:
             try:


Test patch:
diff --git a/spyder/widgets/tests/test_findreplace.py b/spyder/widgets/tests/test_findreplace.py
--- a/spyder/widgets/tests/test_findreplace.py
+++ b/spyder/widgets/tests/test_findreplace.py
@@ -247,5 +247,43 @@ def test_clear_action(findreplace_editor, qtbot):
     assert not findreplace.messages_action.isVisible()
 
 
+def test_replace_all_backslash(findreplace_editor, qtbot):
+    """
+    Test that we can replace all occurrences of a certain text with an
+    expression that contains backslashes.
+
+    This is a regression test for issue spyder-ide/spyder#21007
+    """
+    editor = findreplace_editor.editor
+    findreplace = findreplace_editor.findreplace
+
+    # Replace all instances of | by \
+    editor.set_text("a | b | c")
+    edit = findreplace.search_text.lineEdit()
+    edit.setFocus()
+    qtbot.keyClicks(edit, '|')
+
+    findreplace.replace_text_button.setChecked(True)
+    findreplace.replace_text.setCurrentText('\\')
+    qtbot.wait(100)
+    findreplace.replace_find_all()
+    assert editor.toPlainText() == "a \\ b \\ c"
+
+    # Clear editor and edit
+    editor.selectAll()
+    qtbot.keyClick(edit, Qt.Key_Delete)
+    edit.clear()
+
+    # Replace all instances of \alpha by \beta
+    editor.set_text("\\Psi\n\\alpha\n\\beta\n\\alpha")
+    edit.setFocus()
+    qtbot.keyClicks(edit, "\\alpha")
+
+    findreplace.replace_text.setCurrentText('\\beta')
+    qtbot.wait(100)
+    findreplace.replace_find_all()
+    assert editor.toPlainText() == "\\Psi\n\\beta\n\\beta\n\\beta"
+
+
 if __name__ == "__main__":
     pytest.main([os.path.basename(__file__)])