Repository: spyder-ide/spyder

Issue:
Move find in files plugin to python3
<!--- **PLEASE READ:** When submitting here, please ensure you've completed the following checklist and checked the boxes to confirm. Issue reports without it may be closed. Thanks! --->

### Issue Report Checklist

* [ ] Searched the [issues page](https://github.com/spyder-ide/spyder/issues?q=is%3Aissue) for similar reports
* [ ] Read the relevant sections of the [Spyder Troubleshooting Guide](https://github.com/spyder-ide/spyder/wiki/Troubleshooting-Guide-and-FAQ) and followed its advice
* [ ] Reproduced the issue after updating with ``conda update spyder`` (or ``pip``, if not using Anaconda)
* [ ] Could not reproduce inside ``jupyter qtconsole`` (if console-related)
* [ ] Tried basic troubleshooting (if a bug/error)
    * [ ] Restarted Spyder
    * [ ] Reset preferences with ``spyder --reset``
    * [ ] Reinstalled the latest version of [Anaconda](https://www.anaconda.com/download/)
    * [ ] Tried the other applicable steps from the Troubleshooting Guide
* [ ] Completed the **Problem Description**, **Steps to Reproduce** and **Version** sections below


## Problem Description

Take into account the following for the migration:

 Update super() use
 Remove the py3compat module
 Update any code (and remove if necessary) that uses if PY2 etc...
 Check division operator
 Remove future imports
 Check tests
 Check for Py2 of Py3 codes




Discussion:

Resolving patch:
diff --git a/spyder/plugins/findinfiles/plugin.py b/spyder/plugins/findinfiles/plugin.py
--- a/spyder/plugins/findinfiles/plugin.py
+++ b/spyder/plugins/findinfiles/plugin.py
@@ -37,7 +37,7 @@ class FindInFiles(SpyderPluginWidget):
 
     def __init__(self, parent=None):
         """Initialization."""
-        SpyderPluginWidget.__init__(self, parent)
+        super().__init__(parent)
 
         supported_encodings = self.get_option('supported_encodings')
         self.search_text_samples = self.get_option('search_text_samples')
@@ -81,7 +81,7 @@ def toggle(self, state):
         """Toggle widget visibility"""
         if self.dockwidget:
             self.dockwidget.setVisible(state)
-    
+
     def refreshdir(self):
         """Refresh search directory"""
         self.findinfiles.find_options.set_directory(
@@ -131,7 +131,7 @@ def set_max_results(self, value):
         self.set_option('max_results', value)
         self.findinfiles.set_max_results(value)
 
-    #------ SpyderPluginMixin API ---------------------------------------------
+    # ------ SpyderPluginMixin API --------------------------------------------
     def switch_to_plugin(self):
         """
         Switch to plugin.
@@ -139,14 +139,14 @@ def switch_to_plugin(self):
         This method is called when pressing the plugin shortcut.
         """
         if self.dockwidget:
-            super(FindInFiles, self).switch_to_plugin()
+            super().switch_to_plugin()
         self.findinfiles_callback()  # Necessary at least with PyQt5 on Windows
 
-    #------ SpyderPluginWidget API --------------------------------------------
+    # ------ SpyderPluginWidget API -------------------------------------------
     def get_plugin_title(self):
         """Return widget title"""
         return _("Find")
-    
+
     def get_focus_widget(self):
         """
         Return the widget to give focus to when
diff --git a/spyder/plugins/findinfiles/widgets.py b/spyder/plugins/findinfiles/widgets.py
--- a/spyder/plugins/findinfiles/widgets.py
+++ b/spyder/plugins/findinfiles/widgets.py
@@ -12,7 +12,6 @@
 # pylint: disable=R0201
 
 # Standard library imports
-from __future__ import with_statement, print_function
 import fnmatch
 import os
 import os.path as osp
@@ -34,7 +33,6 @@
 # Local imports
 from spyder.config.base import _
 from spyder.config.main import EXCLUDE_PATTERNS
-from spyder.py3compat import to_text_string, PY2
 from spyder.utils import icon_manager as ima
 from spyder.utils.encoding import is_text_file, to_unicode_from_fs
 from spyder.widgets.comboboxes import PatternComboBox
@@ -80,7 +78,7 @@ class SearchThread(QThread):
     max_power = 9   # 2**9 = 512
 
     def __init__(self, parent, search_text, text_color=None):
-        QThread.__init__(self, parent)
+        super().__init__(parent)
         self.mutex = QMutex()
         self.stopped = None
         self.search_text = search_text
@@ -290,32 +288,29 @@ def truncate_result(self, line, start, end):
         """
         Shorten text on line to display the match within `max_line_length`.
         """
-        ellipsis = u'...'
+        ellipsis = '...'
         max_line_length = 80
         max_num_char_fragment = 40
 
         html_escape_table = {
-            u"&": u"&amp;",
-            u'"': u"&quot;",
-            u"'": u"&apos;",
-            u">": u"&gt;",
-            u"<": u"&lt;",
+            "&": "&amp;",
+            '"': "&quot;",
+            "'": "&apos;",
+            ">": "&gt;",
+            "<": "&lt;",
         }
 
         def html_escape(text):
             """Produce entities within text."""
-            return u"".join(html_escape_table.get(c, c) for c in text)
+            return "".join(html_escape_table.get(c, c) for c in text)
 
-        if PY2:
-            line = to_text_string(line, encoding='utf8')
-        else:
-            line = to_text_string(line)
+        line = str(line)
         left, match, right = line[:start], line[start:end], line[end:]
 
         if len(line) > max_line_length:
             offset = (len(line) - len(match)) // 2
 
-            left = left.split(u' ')
+            left = left.split(' ')
             num_left_words = len(left)
 
             if num_left_words == 1:
@@ -324,7 +319,7 @@ def html_escape(text):
                     left = ellipsis + left[-offset:]
                 left = [left]
 
-            right = right.split(u' ')
+            right = right.split(' ')
             num_right_words = len(right)
 
             if num_right_words == 1:
@@ -342,8 +337,8 @@ def html_escape(text):
             if len(right) < num_right_words:
                 right = right + [ellipsis]
 
-            left = u' '.join(left)
-            right = u' '.join(right)
+            left = ' '.join(left)
+            right = ' '.join(right)
 
             if len(left) > max_num_char_fragment:
                 left = ellipsis + left[-30:]
@@ -351,7 +346,7 @@ def html_escape(text):
             if len(right) > max_num_char_fragment:
                 right = right[:30] + ellipsis
 
-        line_match_format = (u'<span style="color:{0}">{{0}}'
+        line_match_format = ('<span style="color:{0}">{{0}}'
                              '<b>{{1}}</b>{{2}}</span>')
         line_match_format = line_match_format.format(self.text_color)
 
@@ -371,7 +366,7 @@ class SearchInComboBox(QComboBox):
     widget.
     """
     def __init__(self, external_path_history=[], parent=None):
-        super(SearchInComboBox, self).__init__(parent)
+        super().__init__(parent)
         self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
         self.setToolTip(_('Search directory'))
         self.setEditable(False)
@@ -431,7 +426,7 @@ def add_external_path(self, path):
 
     def get_external_paths(self):
         """Returns a list of the external paths listed in the combobox."""
-        return [to_text_string(self.itemText(i))
+        return [str(self.itemText(i))
                 for i in range(EXTERNAL_PATHS, self.count())]
 
     def clear_external_paths(self):
@@ -487,7 +482,7 @@ def path_selection_changed(self):
                 self.clear_external_paths()
             self.setCurrentIndex(CWD)
         elif idx >= EXTERNAL_PATHS:
-            self.external_path = to_text_string(self.itemText(idx))
+            self.external_path = str(self.itemText(idx))
 
     @Slot()
     def select_directory(self):
@@ -562,7 +557,7 @@ def __init__(self, parent, search_text, search_text_regexp,
                  supported_encodings, more_options,
                  case_sensitive, external_path_history, search_in_index,
                  options_button=None):
-        QWidget.__init__(self, parent)
+        super().__init__(parent)
         self._running = False
         if not isinstance(search_text, (list, tuple)):
             search_text = [search_text]
@@ -717,9 +712,9 @@ def get_options(self, to_save=False):
         # Return current options for them to be saved when closing
         # Spyder.
         if to_save:
-            search_text = [to_text_string(self.search_text.itemText(index))
+            search_text = [str(self.search_text.itemText(index))
                            for index in range(self.search_text.count())]
-            exclude = [to_text_string(self.exclude_pattern.itemText(index))
+            exclude = [str(self.exclude_pattern.itemText(index))
                        for index in range(self.exclude_pattern.count())]
             exclude_idx = self.exclude_pattern.currentIndex()
             path_history = self.path_selection_combo.get_external_paths()
@@ -737,7 +732,7 @@ def get_options(self, to_save=False):
         self.search_text.setToolTip("")
         self.exclude_pattern.setToolTip("")
 
-        utext = to_text_string(self.search_text.currentText())
+        utext = str(self.search_text.currentText())
         if not utext:
             return
 
@@ -751,7 +746,7 @@ def get_options(self, to_save=False):
                 except UnicodeDecodeError:
                     pass
 
-        exclude = to_text_string(self.exclude_pattern.currentText())
+        exclude = str(self.exclude_pattern.currentText())
 
         if not case_sensitive:
             texts = [(text[0].lower(), text[1]) for text in texts]
@@ -771,7 +766,7 @@ def get_options(self, to_save=False):
             if error_msg:
                 exclude_edit = self.exclude_pattern.lineEdit()
                 exclude_edit.setStyleSheet(self.REGEX_INVALID)
-                tooltip = self.REGEX_ERROR + u': ' + to_text_string(error_msg)
+                tooltip = self.REGEX_ERROR + ': ' + str(error_msg)
                 self.exclude_pattern.setToolTip(tooltip)
                 return None
             else:
@@ -782,7 +777,7 @@ def get_options(self, to_save=False):
             error_msg = regexp_error_msg(texts[0][0])
             if error_msg:
                 self.search_text.lineEdit().setStyleSheet(self.REGEX_INVALID)
-                tooltip = self.REGEX_ERROR + u': ' + to_text_string(error_msg)
+                tooltip = self.REGEX_ERROR + ': ' + str(error_msg)
                 self.search_text.setToolTip(tooltip)
                 return None
             else:
@@ -836,15 +831,14 @@ def __init__(self, parent, lineno, colno, match, font,
         self.match = match
         self.text_color = text_color
         self.font = font
-        QTreeWidgetItem.__init__(self, parent, [self.__repr__()],
-                                 QTreeWidgetItem.Type)
+        super().__init__(parent, [self.__repr__()], QTreeWidgetItem.Type)
 
     def __repr__(self):
-        match = to_text_string(self.match).rstrip()
-        _str = to_text_string("<!-- LineMatchItem -->"
-                              "<p style=\"color:'{4}';\"><b>{1}</b> ({2}): "
-                              "<span style='font-family:{0};"
-                              "font-size:75%;'>{3}</span></p>")
+        match = str(self.match).rstrip()
+        _str = ("<!-- LineMatchItem -->"
+                "<p style=\"color:'{4}';\"><b>{1}</b> ({2}): "
+                "<span style='font-family:{0};"
+                "font-size:75%;'>{3}</span></p>")
         return _str.format(self.font.family(), self.lineno, self.colno, match,
                            self.text_color)
 
@@ -868,15 +862,15 @@ def __init__(self, parent, filename, sorting, text_color=None):
         self.sorting = sorting
         self.filename = osp.basename(filename)
 
-        title_format = to_text_string('<!-- FileMatchItem -->'
-                                      '<b style="color:{2}">{0}</b>'
-                                      '&nbsp;&nbsp;&nbsp;'
-                                      '<small style="color:{2}"><em>{1}</em>'
-                                      '</small>')
+        title_format = ('<!-- FileMatchItem -->'
+                        '<b style="color:{2}">{0}</b>'
+                        '&nbsp;&nbsp;&nbsp;'
+                        '<small style="color:{2}"><em>{1}</em>'
+                        '</small>')
         title = (title_format.format(osp.basename(filename),
                                      osp.dirname(filename),
                                      text_color))
-        QTreeWidgetItem.__init__(self, parent, [title], QTreeWidgetItem.Type)
+        super().__init__(parent, [title], QTreeWidgetItem.Type)
 
         self.setToolTip(0, filename)
 
@@ -896,7 +890,7 @@ def __ge__(self, x):
 class ItemDelegate(QStyledItemDelegate):
 
     def __init__(self, parent):
-        QStyledItemDelegate.__init__(self, parent)
+        super().__init__(parent)
         self._margin = None
 
     def paint(self, painter, option, index):
@@ -942,7 +936,7 @@ class ResultsBrowser(OneColumnTree):
     sig_max_results_reached = Signal()
 
     def __init__(self, parent, text_color=None, max_results=1000):
-        OneColumnTree.__init__(self, parent)
+        super().__init__(parent)
         self.search_text = None
         self.results = None
         self.max_results = max_results
@@ -1042,7 +1036,7 @@ class FileProgressBar(QWidget):
     """Simple progress spinner with a label."""
 
     def __init__(self, parent):
-        QWidget.__init__(self, parent)
+        super().__init__(parent)
 
         self.status_text = QLabel(self)
         self.spinner = create_waitspinner(parent=self)
@@ -1055,9 +1049,9 @@ def __init__(self, parent):
     def set_label_path(self, path, folder=False):
         text = truncate_path(path)
         if not folder:
-            status_str = _(u' Scanning: {0}').format(text)
+            status_str = _(' Scanning: {0}').format(text)
         else:
-            status_str = _(u' Searching for files in folder:'
+            status_str = _(' Searching for files in folder:'
                            ' {0}').format(text)
         self.status_text.setText(status_str)
 
@@ -1097,7 +1091,7 @@ def __init__(self,
                  options_button=None,
                  text_color=None,
                  max_results=1000):
-        QWidget.__init__(self, parent)
+        super().__init__(parent)
 
         self.search_thread = None
         self.text_color = text_color


Test patch:
diff --git a/spyder/plugins/findinfiles/tests/test_plugin.py b/spyder/plugins/findinfiles/tests/test_plugin.py
--- a/spyder/plugins/findinfiles/tests/test_plugin.py
+++ b/spyder/plugins/findinfiles/tests/test_plugin.py
@@ -8,7 +8,6 @@
 """Test scripts for `findinfiles` plugin."""
 
 # Standard library imports
-import re
 import os
 import os.path as osp
 
@@ -20,7 +19,7 @@
 from spyder.plugins.findinfiles.widgets import SELECT_OTHER
 
 LOCATION = osp.realpath(osp.join(os.getcwd(), osp.dirname(__file__)))
-NONASCII_DIR = osp.join(LOCATION, u"èáïü Øαôå 字分误")
+NONASCII_DIR = osp.join(LOCATION, "èáïü Øαôå 字分误")
 if not osp.exists(NONASCII_DIR):
     os.makedirs(NONASCII_DIR)
 
@@ -40,7 +39,8 @@ def test_closing_plugin(findinfiles, qtbot, mocker):
     Test that the external paths listed in the combobox are saved and loaded
     correctly from the spyder config file.
     """
-    path_selection_combo = findinfiles.findinfiles.find_options.path_selection_combo
+    path_selection_combo = (
+        findinfiles.findinfiles.find_options.path_selection_combo)
     path_selection_combo.clear_external_paths()
     assert path_selection_combo.get_external_paths() == []
 
@@ -63,10 +63,10 @@ def test_closing_plugin(findinfiles, qtbot, mocker):
     # Close the plugin and assert that the external_path_history
     # has been saved and loaded as expected.
     findinfiles.close()
-    path_selection_combo = findinfiles.findinfiles.find_options.path_selection_combo
+    path_selection_combo = (
+        findinfiles.findinfiles.find_options.path_selection_combo)
     assert path_selection_combo.get_external_paths() == expected_results
 
 
-
 if __name__ == "__main__":
     pytest.main(['-x', osp.basename(__file__), '-v', '-rw'])
diff --git a/spyder/plugins/findinfiles/tests/test_widgets.py b/spyder/plugins/findinfiles/tests/test_widgets.py
--- a/spyder/plugins/findinfiles/tests/test_widgets.py
+++ b/spyder/plugins/findinfiles/tests/test_widgets.py
@@ -18,15 +18,15 @@
 from qtpy.QtCore import Qt
 
 # Local imports
-from spyder.plugins.findinfiles import widgets
-from spyder.plugins.findinfiles.widgets import (FindInFilesWidget, SearchInComboBox,
-                                                EXTERNAL_PATHS, SELECT_OTHER, CWD,
-                                                CLEAR_LIST, PROJECT, FILE_PATH,
-                                                QMessageBox, SearchThread)
-from spyder.py3compat import PY2
+from spyder.plugins.findinfiles.widgets import (FindInFilesWidget,
+                                                SearchInComboBox,
+                                                EXTERNAL_PATHS, SELECT_OTHER,
+                                                CWD, CLEAR_LIST, PROJECT,
+                                                FILE_PATH, QMessageBox,
+                                                SearchThread)
 
 LOCATION = osp.realpath(osp.join(os.getcwd(), osp.dirname(__file__)))
-NONASCII_DIR = osp.join(LOCATION, u"èáïü Øαôå 字分误")
+NONASCII_DIR = osp.join(LOCATION, "èáïü Øαôå 字分误")
 if not osp.exists(NONASCII_DIR):
     os.makedirs(NONASCII_DIR)
 
@@ -219,12 +219,9 @@ def test_truncate_result_with_different_input(findinfiles, qtbot, line_input):
     slice_start = 1
     slice_end = 2
 
-    if PY2:
-        line_input_expected = line_input.decode('utf-8')
-    else:
-        line_input_expected = line_input
+    line_input_expected = line_input
 
-    expected_result = u'<span style="color:None">%s<b>%s</b>%s</span>' % (
+    expected_result = '<span style="color:None">%s<b>%s</b>%s</span>' % (
         line_input_expected[:slice_start],
         line_input_expected[slice_start:slice_end],
         line_input_expected[slice_end:])
@@ -482,10 +479,11 @@ def test_set_project_path(findinfiles, qtbot):
 
 
 @pytest.mark.parametrize('findinfiles',
-                         [{'external_path_history': [LOCATION,
-                                                     osp.dirname(LOCATION),
-                                                     osp.dirname(osp.dirname(LOCATION)),
-                                                     NONASCII_DIR]}],
+                         [{'external_path_history': [
+                             LOCATION,
+                             osp.dirname(LOCATION),
+                             osp.dirname(osp.dirname(LOCATION)),
+                             NONASCII_DIR]}],
                          indirect=True)
 def test_current_search_path(findinfiles, qtbot):
     """