Repository: spyder-ide/spyder

Issue:
Consider a different icon for "no matches" in Find/Replace
<!--- **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! --->

As a followup to PR #20049 , we might want to consider a different icon for "no matches" in the find/replace widget, and perhaps shift the "Bad Expression" icon to match. 

Currently, they use the following (per @ccordoba12 ):

**Warning**
![warning icon](https://user-images.githubusercontent.com/365293/201726370-02fd59e6-dcbf-4236-abad-632078b7e8bc.png)

**Error**
![error icon](https://user-images.githubusercontent.com/365293/201726561-5c51cb95-9762-452b-a75d-c803ce30895c.png)

This matches the icons used for warnings and errors in the Editor linting panel and Code Analysis pane, etc., treating no matches as a warning, and a regex syntax error as an error.

However, we might want to reconsider, as at least to me, the "warning" icon, :warning: i.e. "alert", to me implies there's something wrong with the expression I typed, whereas a red circled :x: is actually closer, if anything, to the symbology I'd expect if no matches were found, with the red X meaning "no" or "none".

No matches being found doesn't necessarily implied something was wrong with what the user typed, it instead might be either a positive or negative outcome for users. Quite often, I use it to check for uses of things I'm changing, have deprecated or I otherwise want to make sure don't exist in a certain file. Or, things I'm just not entirely sure whether they are in there or not, and just want to check.

Instead, to send a clearer and less ambiguous message, as well as avoid any confusion with the usage elsewhere with the "warning" and "error" semantics that this doesn't directly parallel (at least with "warning"), I'd suggest choosing a different icon to indicate "no match", such as a ~~yellow~~ orange or strong grey :no_entry: symbol, i.e. the Material Design icon "Do not disturb", or perhaps the :no_entry_sign: ("Block") icon, which carries more of the implication of nothing being found as opposed to being a warning/problem with the user's entry, as the parallel to the warning symbol elsewhere could imply.

![image](https://user-images.githubusercontent.com/17051931/209415114-58e69e7b-b398-4d25-8c0d-d1d0048c24a4.png)



Discussion:
> I'd suggest choosing a different icon to indicate "no match", such as a yellow or strong grey :no_entry: symbol, i.e. the Material Design icon "Do not disturb", or perhaps the :no_entry_sign: ("Block") icon

I agree with using the first icon but I'm not so sure about the color. I mean, a grey color could imply a "disabled" state, which is not what we want to convey. And we don't use yellows in [our palette](py). We have oranges, greens and blues. So, perhaps we should use an orange?
Oh, yeah orange works—I was just thinking of yellow since that's the normal color of the :warning: icon.

Resolving patch:
diff --git a/spyder/utils/icon_manager.py b/spyder/utils/icon_manager.py
--- a/spyder/utils/icon_manager.py
+++ b/spyder/utils/icon_manager.py
@@ -113,6 +113,7 @@ def __init__(self):
             'drag_dock_widget':        [('mdi.drag-variant',), {'color': self.MAIN_FG_COLOR}],
             'format_letter_case':      [('mdi.format-letter-case',), {'color': self.MAIN_FG_COLOR}],
             'format_letter_matches':   [('mdi.format-letter-matches',), {'color': self.MAIN_FG_COLOR}],
+            'no_matches':              [('mdi.do-not-disturb',), {'color': SpyderPalette.COLOR_WARN_2}],
             'clear_text':              [('mdi.backspace',), {'color': self.MAIN_FG_COLOR}],
             'regex':                   [('mdi.regex',), {'color': self.MAIN_FG_COLOR}],
             'log':                     [('mdi.file-document',), {'color': self.MAIN_FG_COLOR}],
diff --git a/spyder/widgets/comboboxes.py b/spyder/widgets/comboboxes.py
--- a/spyder/widgets/comboboxes.py
+++ b/spyder/widgets/comboboxes.py
@@ -21,7 +21,7 @@
 from qtpy.QtCore import QEvent, Qt, QTimer, QUrl, Signal, QSize
 from qtpy.QtGui import QFont
 from qtpy.QtWidgets import (QAction, QComboBox, QCompleter, QLineEdit,
-                            QSizePolicy, QToolTip)
+                            QSizePolicy, QToolButton, QToolTip)
 
 # Local imports
 from spyder.config.base import _
@@ -177,11 +177,15 @@ def __init__(self, parent, items=None, tip=None,
             self.clear_action, QLineEdit.TrailingPosition
         )
 
+        # Button that corresponds to the clear_action above
+        self.clear_button = self.lineEdit().findChildren(QToolButton)[0]
+
         # Hide clear_action by default because lineEdit is empty when the
         # combobox is created, so it doesn't make sense to show it.
         self.clear_action.setVisible(False)
 
         self.lineEdit().textChanged.connect(self._on_text_changed)
+        self.installEventFilter(self)
 
     def _on_text_changed(self, text):
         """Actions to take when text has changed on the line edit widget."""
@@ -190,6 +194,21 @@ def _on_text_changed(self, text):
         else:
             self.clear_action.setVisible(False)
 
+    def eventFilter(self, widget, event):
+        """
+        Event filter for this combobox.
+
+        Notes
+        -----
+        * Reduce space between clear_action and the right border of lineEdit.
+        """
+        if event.type() == QEvent.Paint:
+            self.clear_button.move(
+                self.lineEdit().width() - 22, self.clear_button.y()
+            )
+
+        return super().eventFilter(widget, event)
+
 
 class EditableComboBox(BaseComboBox):
     """
diff --git a/spyder/widgets/findreplace.py b/spyder/widgets/findreplace.py
--- a/spyder/widgets/findreplace.py
+++ b/spyder/widgets/findreplace.py
@@ -18,7 +18,8 @@
 from qtpy.QtCore import QEvent, QSize, Qt, QTimer, Signal, Slot
 from qtpy.QtGui import QTextCursor
 from qtpy.QtWidgets import (QAction, QGridLayout, QHBoxLayout, QLabel,
-                            QLineEdit, QSizePolicy, QSpacerItem, QWidget)
+                            QLineEdit, QToolButton, QSizePolicy, QSpacerItem,
+                            QWidget)
 
 # Local imports
 from spyder.config.base import _
@@ -104,7 +105,7 @@ def __init__(self, parent, enable_replace=False):
             self.number_matches_text.hide
         )
 
-        self.warning_icon = ima.icon('warning')
+        self.no_matches_icon = ima.icon('no_matches')
         self.error_icon = ima.icon('error')
         self.messages_action = QAction(self)
         self.messages_action.setVisible(False)
@@ -114,6 +115,11 @@ def __init__(self, parent, enable_replace=False):
             lambda: self.messages_action.setVisible(False)
         )
 
+        # Button corresponding to the messages_action above
+        self.messages_button = (
+            self.search_text.lineEdit().findChildren(QToolButton)[1]
+        )
+
         self.replace_on = False
         self.replace_text_button = create_toolbutton(
             self,
@@ -249,11 +255,15 @@ def __init__(self, parent, enable_replace=False):
         self.search_text.installEventFilter(self)
 
     def eventFilter(self, widget, event):
-        """Event filter for search_text widget.
-
-        Emits signals when presing Enter and Shift+Enter.
-        This signals are used for search forward and backward.
-        Also, a crude hack to get tab working in the Find/Replace boxes.
+        """
+        Event filter for search_text widget.
+
+        Notes
+        -----
+        * Emit signals when Enter and Shift+Enter are pressed. These signals
+          are used for search forward and backward.
+        * Add crude hack to get tab working between the find/replace boxes.
+        * Reduce space between the messages_button and the clear one.
         """
 
         # Type check: Prevent error in PySide where 'event' may be of type
@@ -277,7 +287,13 @@ def eventFilter(self, widget, event):
                         self.search_text.currentText())
                 self.focusNextChild()
 
-        return super(FindReplace, self).eventFilter(widget, event)
+        if event.type() == QEvent.Paint:
+            self.messages_button.move(
+                self.search_text.lineEdit().width() - 42,
+                self.messages_button.y()
+            )
+
+        return super().eventFilter(widget, event)
 
     def create_shortcuts(self, parent):
         """Create shortcuts for this widget"""
@@ -745,7 +761,7 @@ def change_number_matches(self, current_match=0, total_matches=0):
         else:
             self.number_matches_text.hide()
             if self.search_text.currentText():
-                self.show_warning()
+                self.show_no_matches()
 
     def update_matches(self):
         """Update total number of matches if text has changed in the editor."""
@@ -758,9 +774,9 @@ def update_matches(self):
             )
             self.change_number_matches(total_matches=number_matches)
 
-    def show_warning(self):
-        """Show a warning message with an icon when no matches can be found."""
-        self._show_icon_message('warning')
+    def show_no_matches(self):
+        """Show a no matches message with an icon."""
+        self._show_icon_message('no_matches')
 
     def show_error(self, error_msg):
         """Show a regexp error message with an icon."""
@@ -774,13 +790,13 @@ def _show_icon_message(self, kind, extra_info=None):
         Parameters
         ----------
         kind: str
-            The kind of message. It can be 'warning' or 'error'.
+            The kind of message. It can be 'no_matches' or 'error'.
         extra_info:
             Extra info to add to the icon's tooltip.
         """
-        if kind == 'warning':
+        if kind == 'no_matches':
             tooltip = self.TOOLTIP['no_matches']
-            icon = self.warning_icon
+            icon = self.no_matches_icon
         else:
             tooltip = self.TOOLTIP['regexp_error']
             icon = self.error_icon


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
@@ -117,7 +117,7 @@ def test_messages_action(findreplace_editor, qtbot):
     qtbot.keyClicks(edit, 'foo')
     assert not findreplace.number_matches_text.isVisible()
     assert findreplace.messages_action.icon().cacheKey() == \
-           findreplace.warning_icon.cacheKey()
+           findreplace.no_matches_icon.cacheKey()
     assert findreplace.messages_action.toolTip() == \
            findreplace.TOOLTIP['no_matches']