Repository: spyder-ide/spyder

Issue:
Find and replace: Greedy regular expressions
Spyder version: 2.3.9
OS: Ubuntu 14
Python 2.7

Find and replace doesn't work for non greedy regular expressions. This regular expression:

```
        \{\$documento\['(.+?)'\]\}
```

doesnt recognize this text:

```
{$documento['DOC_CODIGO']}','{$documento['DOC_DOCID_EDP']}','{$documento['DOC_ESTADO']}','{$documento['DOC_BORRADO']}','{$documento['DOC_SAP_CODMATERIAL']}'
```

But this other regular expressiones does (without the question mark):

```
        \{\$documento\['(\w+)'\]\}
```



Discussion:
This shouldn't that hard to fix. Could you come up with pull request to do it?

Hi,
I am trying to find the problem. I suppose that the problem is here:
widgets/findreplace.py

I am trying to understand the code with prints. But the problem is that the print statement is redirected. I am trying to print to stdout with this trick:

```
import sys
sys.__stdout__.write("echo")
```

But I get this error:

```
QTextCursor::setPosition: Position '305' out of range
```

Where can I see the the string passed to print statement? And how can I print to stdout?

I found a trick to print to stdout:

```
from spyderlib.baseconfig import STDOUT
STDOUT.write("echo")
```

Where can I see all the strings printed with the print statement?

I don't understand why this trick doesn't work:

```
import sys
sys.__stdout__.write("echo")
```

I suppose that another option is to use pdb for debugging.

Those are printed in the internal console. This pane is not opened by default.

@Nodd is right. @aplicacionamedida, please go to

```
View > Panes > Internal console
```

to open it :-)

By the way, thanks for helping us with this.

The problem is here (file `widgets/findreplace.py`)

```
    def replace_find(self):
        """Replace and find"""       
        if (self.editor is not None):

            replace_text = to_text_string(self.replace_text.currentText())
            search_text = to_text_string(self.search_text.currentText())
            pattern = search_text if self.re_button.isChecked() else None
            case = self.case_button.isChecked()
            first = True
            cursor = None
            while True:
                if first:
                    # First found
                    seltxt = to_text_string(self.editor.get_selected_text())
                    cmptxt1 = search_text if case else search_text.lower()
                    cmptxt2 = seltxt if case else seltxt.lower()

                    if self.editor.has_selected_text() and cmptxt1 == cmptxt2:
                        # Text was already found, do nothing
                        pass
                    else:
                        if not self.find(changed=False, forward=True,
                                         rehighlight=False):
                           break
```

self.editor is a CodeEditor instance. `self.editor.get_selected_text()` returns the regex pattern instead of the selected text.

At the same time this logic is wrong:

```
                    seltxt = to_text_string(self.editor.get_selected_text())
                    cmptxt1 = search_text if case else search_text.lower()
                    cmptxt2 = seltxt if case else seltxt.lower()

                    if self.editor.has_selected_text() and cmptxt1 == cmptxt2:
                        # Text was already found, do nothing
                        pass
                    else:
                        if not self.find(changed=False, forward=True,
                                         rehighlight=False):
                           break
```

Maybe it's better to enter to that logic only if pattern is `None`. I suppose that `self.find` is only for simple substitutions (not for regex substitutions).

```
                    if pattern is None:
                        seltxt = to_text_string(self.editor.get_selected_text())
                        cmptxt1 = search_text if case else search_text.lower()
                        cmptxt2 = seltxt if case else seltxt.lower()

                        if self.editor.has_selected_text() and cmptxt1 == cmptxt2:
                            # Text was already found, do nothing
                            pass
                        else:
                            if not self.find(changed=False, forward=True,
                                             rehighlight=False):
                               break
```

@aplicacionamedida, thanks a lot for finding the problem and proposing a solution.

Could you create a pull request for this fix? If you don't know how to use git, you could just edit the file through the Github web interface, and create a pull request from it :-)

hi @ccordoba12 , I dont have too much experience using git. How can I make a pull request? .

I have been researching a little bit more. The method self.editor.get_selected_text() calls

```
BaseEditMixin.get_selected_text
```

defined here:

```
widgets/mixins.py
```

because `self.editor` is an instance of `CodeEditor` . And `CodeEditor` inherit only from `TextEditBaseWidget`. And `TextEditBaseWidget` inherit from `QPlainTextEdit` and `BaseEditMixin`.

The source code of the method `BaseEditMixin.get_selected_text` is this:

```
    def get_selected_text(self):
        """
        Return text selected by current text cursor, converted in unicode

        Replace the unicode line separator character \u2029 by 
        the line separator characters returned by get_line_separator
        """
        return to_text_string(self.textCursor().selectedText()).replace(u("\u2029"),
                                                     self.get_line_separator()

```

This method `BaseEditMixin.get_selected_text()` calls `self.textCursor`. `self.textCursor` is a mehod defined here in the Qt class `QPlainTextEdit`. This method returns a bad selected text in the example provided.

In this file `widgets/findreplace.py` in the method `FindReplace.find()`, I think that there is a little bit of logic duplicated.

Maybe it's better to encapsulate all the code for finding and replace here:

```
BaseEditMixin.replace()
```

in the file:

```
widgets/mixins.py
```

@aplicacionamedida, here it's our guide to use git and create a pull request:

https://github.com/spyder-ide/spyder/wiki/Dev:-Github-Workflow

@rlaverde, please create a PR based on @aplicacionamedida work.


Resolving patch:
diff --git a/spyder/widgets/mixins.py b/spyder/widgets/mixins.py
--- a/spyder/widgets/mixins.py
+++ b/spyder/widgets/mixins.py
@@ -19,9 +19,15 @@
 import textwrap
 
 # Third party imports
-from qtpy.QtCore import QPoint, QRegExp, Qt
+from qtpy.QtCore import QPoint, Qt
 from qtpy.QtGui import QCursor, QTextCursor, QTextDocument
 from qtpy.QtWidgets import QApplication, QToolTip
+from qtpy import PYQT5
+
+if PYQT5:
+    from qtpy.QtCore import QRegularExpression
+else:
+    from qtpy.QtCore import QRegExp
 
 # Local imports
 from spyder.config.base import _
@@ -480,9 +486,17 @@ def find_text(self, text, changed=True, forward=True, case=False,
             moves += [QTextCursor.End]
         if not regexp:
             text = re.escape(to_text_string(text))
-        pattern = QRegExp(r"\b%s\b" % text if words else text,
-                          Qt.CaseSensitive if case else Qt.CaseInsensitive,
-                          QRegExp.RegExp2)
+        if PYQT5:
+            pattern = QRegularExpression(r"\b{}\b".format(text) if words else
+                                         text)
+            if case:
+                pattern.setPatternOptions(
+                    QRegularExpression.CaseInsensitiveOption)
+        else:
+            pattern = QRegExp(r"\b{}\b".format(text)
+                              if words else text, Qt.CaseSensitive if case else
+                              Qt.CaseInsensitive, QRegExp.RegExp2)
+
         for move in moves:
             cursor.movePosition(move)
             if regexp and '\\n' in text:


Test patch: