Repository: spyder-ide/spyder

Issue:
Stopping a search using FindInFiles freezes the app
### Issue Report Checklist

* [X] Searched the [issues page](https://github.com/spyder-ide/spyder/issues?q=is%3Aissue) for similar reports
* [X] Read the relevant sections of the [Spyder Troubleshooting Guide](https://github.com/spyder-ide/spyder/wiki/Troubleshooting-Guide-and-FAQ) and followed its advice
* [X] Reproduced the issue after updating with ``conda update spyder`` (or ``pip``, if not using Anaconda)
* [ ] Could not reproduce inside ``jupyter qtconsole`` (if console-related)
* [X] Tried basic troubleshooting (if a bug/error)
    * [X] Restarted Spyder
    * [X] Reset preferences with ``spyder --reset``
    * [X] Reinstalled the latest version of [Anaconda](https://www.anaconda.com/download/)
    * [X] Tried the other applicable steps from the Troubleshooting Guide
* [X] Completed the **Problem Description**, **Steps to Reproduce** and **Version** sections below


## Problem Description

When you use FindInFiles on a directory with huge files or a lot of files, the stop button does not stop the Find, crashing the IDE.


### What steps reproduce the problem?

1. Open Spyder
2. Set FindInFiles to look on a directory with huge files or with a lot of files
3. Start a search
4. Stop de search

### What is the expected output? What do you see instead?
The expected output is the search being stopped and the result list with partial results.
I'm getting a frozen window and a message from Ubuntu to close or wait for the app.


### Paste Traceback/Error Below (if applicable)
<!--- Copy from error dialog or View > Panes > Internal Console --->

```python-traceback

PASTE TRACEBACK HERE

```

## Versions
<!--- You can get this information from Help > About Spyder...
or (if Spyder won't launch) the "conda list" command
from the Anaconda Prompt/Terminal/command line. --->

* Spyder version: 
* Python version: 
* Qt version: 
* PyQt version: 
* Operating System name/version: 

### Dependencies
<!--- Please go to the menu entry Help > Dependencies,
press the Copy to clipboard button and paste below --->

```
PASTE DEPENDENCIES HERE
```



Discussion:
There are some places where `self.stopped = True` is needed, I'm working on a fix :)

Resolving patch:
diff --git a/spyder/widgets/findinfiles.py b/spyder/widgets/findinfiles.py
--- a/spyder/widgets/findinfiles.py
+++ b/spyder/widgets/findinfiles.py
@@ -139,10 +139,16 @@ def find_files_in_path(self, path):
                     return False
             try:
                 for d in dirs[:]:
+                    with QMutexLocker(self.mutex):
+                        if self.stopped:
+                            return False
                     dirname = os.path.join(path, d)
                     if re.search(self.exclude, dirname + os.sep):
                         dirs.remove(d)
                 for f in files:
+                    with QMutexLocker(self.mutex):
+                        if self.stopped:
+                            return False
                     filename = os.path.join(path, f)
                     if re.search(self.exclude, filename):
                         continue
@@ -159,6 +165,9 @@ def find_string_in_file(self, fname):
         try:
             for lineno, line in enumerate(open(fname, 'rb')):
                 for text, enc in self.texts:
+                    with QMutexLocker(self.mutex):
+                        if self.stopped:
+                            return False
                     line_search = line
                     if not self.case_sensitive:
                         line_search = line_search.lower()
@@ -178,6 +187,9 @@ def find_string_in_file(self, fname):
                     line = line.lower()
                 if self.text_re:
                     for match in re.finditer(text, line):
+                        with QMutexLocker(self.mutex):
+                            if self.stopped:
+                                return False
                         self.total_matches += 1
                         self.sig_file_match.emit((osp.abspath(fname),
                                                   lineno + 1,
@@ -187,6 +199,9 @@ def find_string_in_file(self, fname):
                 else:
                     found = line.find(text)
                     while found > -1:
+                        with QMutexLocker(self.mutex):
+                            if self.stopped:
+                                return False
                         self.total_matches += 1
                         self.sig_file_match.emit((osp.abspath(fname),
                                                   lineno + 1,
@@ -543,15 +558,17 @@ def get_options(self, all=False):
         path = self.path_selection_combo.get_current_searchpath()
 
         # Finding text occurrences
-        if not exclude_re:
-            exclude = fnmatch.translate(exclude)
-        else:
-            try:
+        try:
+            if not exclude_re:
+                items = [fnmatch.translate(item.strip())
+                         for item in exclude.split(",")]
+                exclude = '|'.join(items)
+            else:
                 exclude = re.compile(exclude)
-            except Exception:
-                exclude_edit = self.exclude_pattern.lineEdit()
-                exclude_edit.setStyleSheet(self.REGEX_INVALID)
-                return None
+        except Exception:
+            exclude_edit = self.exclude_pattern.lineEdit()
+            exclude_edit.setStyleSheet(self.REGEX_INVALID)
+            return None
 
         if text_re:
             try:


Test patch:
diff --git a/spyder/widgets/tests/test_findinfiles.py b/spyder/widgets/tests/test_findinfiles.py
--- a/spyder/widgets/tests/test_findinfiles.py
+++ b/spyder/widgets/tests/test_findinfiles.py
@@ -111,7 +111,7 @@ def test_find_in_files_search(qtbot):
     assert expected_results() == matches
 
 
-def test_exclude_extension(qtbot):
+def test_exclude_extension_regex(qtbot):
     find_in_files = setup_findinfiles(qtbot, exclude="\.py$")
     find_in_files.set_search_text("spam")
     find_in_files.find_options.set_directory(osp.join(LOCATION, "data"))
@@ -128,6 +128,43 @@ def test_exclude_extension(qtbot):
     assert files_filtered
 
 
+def test_exclude_extension_string(qtbot):
+    find_in_files = setup_findinfiles(qtbot, exclude="*.py",
+                                      exclude_regexp=False)
+    find_in_files.set_search_text("spam")
+    find_in_files.find_options.set_directory(osp.join(LOCATION, "data"))
+    find_in_files.find()
+    blocker = qtbot.waitSignal(find_in_files.sig_finished)
+    blocker.wait()
+    matches = process_search_results(find_in_files.result_browser.data)
+    files_filtered = True
+    for file in matches:
+        filename, ext = osp.splitext(file)
+        if ext == '.py':
+            files_filtered = False
+            break
+    assert files_filtered
+
+
+def test_exclude_extension_multiple_string(qtbot):
+    find_in_files = setup_findinfiles(qtbot, exclude="*.py, *.cpp",
+                                      exclude_regexp=False)
+    find_in_files.set_search_text("spam")
+    find_in_files.find_options.set_directory(osp.join(LOCATION, "data"))
+    find_in_files.find()
+    blocker = qtbot.waitSignal(find_in_files.sig_finished)
+    blocker.wait()
+    matches = process_search_results(find_in_files.result_browser.data)
+    files_filtered = True
+    for file in matches:
+        filename, ext = osp.splitext(file)
+        if ext in ['.py', '.cpp']:
+            print(ext)
+            files_filtered = False
+            break
+    assert files_filtered
+
+
 def test_case_unsensitive_search(qtbot):
     find_in_files = setup_findinfiles(qtbot, case_sensitive=False)
     find_in_files.set_search_text('ham')