Repository: spyder-ide/spyder

Issue:
Some proposed improvements to the file switcher
**1. Make items sorted in ascending order**

I think it would make more sense to sort the **filtered** items in ascending order instead of descending order in the switcher.

![image](https://user-images.githubusercontent.com/10170372/68684032-ca14fc80-0535-11ea-82a3-63c246c8ed8c.png)


**2. Make the list view looks like is has the focus.**

The focus is given to the line edit of the switcher, while the focus policy of the list view is set to `NoFocus`. This is not a big deal, but this make the selected item in the list looks like it is not active (while in reality, it is implemented in such a way that it has the functionality of an active list view, i.e. navigation with the arrows, selection with enter, etc).

So, I think the switcher should look like it has the focus and is active as a whole. I suggest that we trick the html delegate that is used in the list view into thinking that it is active at all times, even if strictly speaking it is not. 

![image](https://user-images.githubusercontent.com/10170372/68682829-c2ecef00-0533-11ea-985c-fb85899db25d.png)

## 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 4.0.0.dev0 (Commit: a74824da2) 
* Python 3.7.3 64-bit | Qt 5.9.6 | PyQt5 5.9.2 | Windows 10 



Discussion:
>I think it would make more sense to sort the filtered items in ascending order instead of descending order in the switcher.

That is probably a regression from moving to the new switcher. 👍 

>So, I think the switcher should look like it has the focus and is active as a whole. I suggest that we trick the html delegate that is used in the list view into thinking that it is active at all times, even if strictly speaking it is not.

Sounds good too !

Could you open a PR @jnsebgosselin ?
> Could you open a PR @jnsebgosselin ?

Yes, I'm already working on this 👍
I agree with both suggestions. Thanks @jnsebgosselin for helping us with them!

Resolving patch:
diff --git a/spyder/widgets/switcher.py b/spyder/widgets/switcher.py
--- a/spyder/widgets/switcher.py
+++ b/spyder/widgets/switcher.py
@@ -146,6 +146,10 @@ def get_height(self):
         """Return the content height."""
         return self._height
 
+    def get_score(self):
+        """Return the fuzzy matchig score."""
+        return self._score
+
     def set_score(self, value):
         """Set the search text fuzzy match score."""
         self._score = value
@@ -435,10 +439,6 @@ def get_data(self):
         """Return the additional data associated to the item."""
         return self._data
 
-    def get_score(self):
-        """Return the fuzzy matchig score."""
-        return self._score
-
     def set_section(self, value):
         """Set the item section name."""
         self._section = value
@@ -468,12 +468,34 @@ def __init__(self, parent=None):
         self.setFilterCaseSensitivity(Qt.CaseInsensitive)
         self.setSortCaseSensitivity(Qt.CaseInsensitive)
         self.setDynamicSortFilter(True)
+        self.__filter_by_score = False
+
+    def set_filter_by_score(self, value):
+        """
+        Set whether the items should be filtered by their score result.
+
+        Parameters
+        ----------
+        value : bool
+           Indicates whether the items should be filtered by their
+           score result.
+        """
+        self.__filter_by_score = value
+        self.invalidateFilter()
+
+    def filterAcceptsRow(self, source_row, source_parent):
+        """Override Qt method to filter items by their score result."""
+        item = self.sourceModel().item(source_row)
+        if self.__filter_by_score is False or item.is_action_item():
+            return True
+        else:
+            return not item.get_score() == -1
 
     def sortBy(self, attr):
         """Override Qt method."""
         self.__sort_by = attr
         self.invalidate()
-        self.sort(0, Qt.DescendingOrder)
+        self.sort(0, Qt.AscendingOrder)
 
     def lessThan(self, left, right):
         """Override Qt method."""
@@ -522,7 +544,6 @@ def __init__(self, parent, help_text=None, item_styles=ITEM_STYLES,
                  item_separator_styles=ITEM_SEPARATOR_STYLES):
         """Multi purpose switcher."""
         super(Switcher, self).__init__(parent)
-        self._visible_rows = 0
         self._modes = {}
         self._mode_on = ''
         self._item_styles = item_styles
@@ -575,7 +596,6 @@ def _add_item(self, item, last_item=True):
         """Perform common actions when adding items."""
         item.set_width(self._ITEM_WIDTH)
         self.model.appendRow(item)
-        self._visible_rows = self.model.rowCount()
         if last_item:
             # Only set the current row to the first item when the added item is
             # the last one in order to prevent performance issues when
@@ -655,6 +675,7 @@ def setup(self):
         if self.search_text() == '':
             self._mode_on = ''
             self.clear()
+            self.proxy.set_filter_by_score(False)
             self.sig_mode_selected.emit(self._mode_on)
             return
 
@@ -679,30 +700,19 @@ def setup(self):
         scores = get_search_scores(to_text_string(search_text),
                                    titles, template=u"<b>{0}</b>")
 
-        self._visible_rows = self.model.rowCount()
-        for idx, score in enumerate(scores):
-            title, rich_title, score_value = score
+        for idx, (title, rich_title, score_value) in enumerate(scores):
             item = self.model.item(idx)
-
             if not self._is_separator(item) and not item.is_action_item():
                 rich_title = rich_title.replace(" ", "&nbsp;")
                 item.set_rich_title(rich_title)
-
             item.set_score(score_value)
-            proxy_index = self.proxy.mapFromSource(self.model.index(idx, 0))
+        self.proxy.set_filter_by_score(True)
 
-            if not item.is_action_item():
-                self.list.setRowHidden(proxy_index.row(), score_value == -1)
-
-                if score_value == -1:
-                    self._visible_rows -= 1
-
-        if self._visible_rows:
+        self.setup_sections()
+        if self.count():
             self.set_current_row(0)
         else:
             self.set_current_row(-1)
-
-        self.setup_sections()
         self.set_height()
 
     def setup_sections(self):
@@ -740,12 +750,12 @@ def setup_sections(self):
 
     def set_height(self):
         """Set height taking into account the number of items."""
-        if self._visible_rows >= self._MAX_NUM_ITEMS:
+        if self.count() >= self._MAX_NUM_ITEMS:
             switcher_height = self._MAX_HEIGHT
-        elif self._visible_rows != 0 and self.current_item():
+        elif self.count() != 0 and self.current_item():
             current_item = self.current_item()
             item_height = current_item.get_height()
-            list_height = item_height * (self._visible_rows + 3)
+            list_height = item_height * (self.count() + 3)
             edit_height = self.edit.height()
             spacing_height = self.layout().spacing() * 4
             switcher_height = list_height + edit_height + spacing_height
@@ -828,7 +838,7 @@ def _select_row(self, steps):
 
     def count(self):
         """Get the item count in the list widget."""
-        return self._visible_rows
+        return self.proxy.rowCount()
 
     def current_row(self):
         """Return the current selected row in the list widget."""
@@ -843,14 +853,14 @@ def current_item(self):
 
     def set_current_row(self, row):
         """Set the current selected row in the list widget."""
-        index = self.model.index(row, 0)
+        proxy_index = self.proxy.index(row, 0)
         selection_model = self.list.selectionModel()
 
         # https://doc.qt.io/qt-5/qitemselectionmodel.html#SelectionFlag-enum
-        selection_model.setCurrentIndex(index, selection_model.ClearAndSelect)
+        selection_model.setCurrentIndex(
+            proxy_index, selection_model.ClearAndSelect)
 
         # Ensure that the selected item is visible
-        proxy_index = self.proxy.mapFromSource(index)
         self.list.scrollTo(proxy_index, QAbstractItemView.EnsureVisible)
 
     def previous_row(self):


Test patch:
diff --git a/spyder/widgets/tests/test_switcher.py b/spyder/widgets/tests/test_switcher.py
--- a/spyder/widgets/tests/test_switcher.py
+++ b/spyder/widgets/tests/test_switcher.py
@@ -72,33 +72,33 @@ def test_switcher_filter_and_mode(dlg_switcher, qtbot):
     edit = dlg_switcher.edit
 
     # Initially cvs mode with five rows
-    assert dlg_switcher._visible_rows == 5
+    assert dlg_switcher.count() == 5
 
     # Match one row by name
     edit.setText("master")
     qtbot.wait(1000)
-    assert dlg_switcher._visible_rows == 2
+    assert dlg_switcher.count() == 2
 
     # Help mode
     edit.setText("")
     edit.setText("?")
     qtbot.wait(1000)
-    assert dlg_switcher._visible_rows == 5
+    assert dlg_switcher.count() == 5
 
     # Symbol mode
     edit.setText("")
     edit.setText("@")
     qtbot.wait(1000)
-    assert dlg_switcher._visible_rows == 2
+    assert dlg_switcher.count() == 2
 
     # Commands mode
     edit.setText("")
     edit.setText(">")
     qtbot.wait(1000)
-    assert dlg_switcher._visible_rows == 7
+    assert dlg_switcher.count() == 7
 
     # Text mode
     edit.setText("")
     edit.setText(":")
     qtbot.wait(1000)
-    assert dlg_switcher._visible_rows == 1
+    assert dlg_switcher.count() == 1