Repository: spyder-ide/spyder

Issue:
File names are repeated in Code analysis file combobox
Instead of registering each file name once, we're doing it several times. This is how the problem looks graphically:

![imagen](https://user-images.githubusercontent.com/365293/83071209-5f8b6000-a032-11ea-8744-c9ebc0dc8f1e.png)

@juanis2112, please wait until @steff456 fix this one before filming the pending gif for our docs.


Discussion:

Resolving patch:
diff --git a/spyder/plugins/pylint/confpage.py b/spyder/plugins/pylint/confpage.py
--- a/spyder/plugins/pylint/confpage.py
+++ b/spyder/plugins/pylint/confpage.py
@@ -16,6 +16,11 @@
 from spyder.plugins.pylint.widgets.pylintgui import PylintWidget
 
 
+MIN_HISTORY_ENTRIES = 5
+MAX_HISTORY_ENTRIES = 100
+DEFAULT_HISTORY_ENTRIES = 30
+
+
 class PylintConfigPage(PluginConfigPage):
     def setup_page(self):
         settings_group = QGroupBox(_("Settings"))
@@ -28,8 +33,8 @@ def setup_page(self):
         hist_label1.setWordWrap(True)
         hist_spin = self.create_spinbox(
             _("History: "),
-            _(" results"), 'max_entries', default=50,
-            min_=10, max_=1000000, step=10)
+            _(" results"), 'max_entries', default=DEFAULT_HISTORY_ENTRIES,
+            min_=MIN_HISTORY_ENTRIES, max_=MAX_HISTORY_ENTRIES, step=1)
 
         results_group = QGroupBox(_("Results"))
         results_label1 = QLabel(_("Results are stored here:"))
diff --git a/spyder/plugins/pylint/plugin.py b/spyder/plugins/pylint/plugin.py
--- a/spyder/plugins/pylint/plugin.py
+++ b/spyder/plugins/pylint/plugin.py
@@ -26,7 +26,10 @@
 from spyder.utils import icon_manager as ima
 from spyder.utils.programs import is_module_installed
 from spyder.utils.qthelpers import create_action, MENU_SEPARATOR
-from spyder.plugins.pylint.confpage import PylintConfigPage
+from spyder.plugins.pylint.confpage import (PylintConfigPage,
+                                            MAX_HISTORY_ENTRIES,
+                                            MIN_HISTORY_ENTRIES,
+                                            DEFAULT_HISTORY_ENTRIES)
 from spyder.plugins.pylint.widgets.pylintgui import PylintWidget
 
 
@@ -49,7 +52,7 @@ class Pylint(SpyderPluginWidget):
     def __init__(self, parent=None):
         SpyderPluginWidget.__init__(self, parent)
 
-        max_entries = self.get_option('max_entries', 50)
+        max_entries = self.get_option('max_entries', DEFAULT_HISTORY_ENTRIES)
         self.pylint = PylintWidget(self, max_entries=max_entries,
                                    options_button=self.options_button,
                                    text_color=MAIN_TEXT_COLOR,
@@ -123,7 +126,7 @@ def apply_plugin_settings(self, options):
         """Apply configuration file's plugin settings"""
         # The history depth option will be applied at
         # next Spyder startup, which is soon enough
-        pass
+        self.pylint.change_history_limit(self.get_option('max_entries'))
 
     #------ Public API --------------------------------------------------------
     @Slot()
@@ -132,9 +135,11 @@ def change_history_depth(self):
         depth, valid = QInputDialog.getInt(self, _('History'),
                                        _('Maximum entries'),
                                        self.get_option('max_entries'),
-                                       10, 10000)
+                                       MIN_HISTORY_ENTRIES,
+                                       MAX_HISTORY_ENTRIES)
         if valid:
             self.set_option('max_entries', depth)
+            self.pylint.change_history_limit(depth)
 
     def get_filename(self):
         """Get current filename in combobox."""
diff --git a/spyder/plugins/pylint/widgets/pylintgui.py b/spyder/plugins/pylint/widgets/pylintgui.py
--- a/spyder/plugins/pylint/widgets/pylintgui.py
+++ b/spyder/plugins/pylint/widgets/pylintgui.py
@@ -169,6 +169,7 @@ def __init__(self, parent, max_entries=100, options_button=None,
         self.output = None
         self.error_output = None
         self.filename = None
+        self.curr_filenames = []
         self.text_color = text_color
         self.prevrate_color = prevrate_color
 
@@ -183,6 +184,7 @@ def __init__(self, parent, max_entries=100, options_button=None,
                 pass
 
         self.filecombo = PythonModulesComboBox(self)
+        self.filecombo.setInsertPolicy(self.filecombo.InsertAtTop)
 
         self.start_button = create_toolbutton(self, icon=ima.icon('run'),
                                     text=_("Analyze"),
@@ -237,7 +239,7 @@ def __init__(self, parent, max_entries=100, options_button=None,
 
         if self.rdata:
             self.remove_obsolete_items()
-            self.filecombo.addItems(self.get_filenames())
+            self.filecombo.insertItems(0, self.get_filenames())
             self.start_button.setEnabled(self.filecombo.is_valid())
         else:
             self.start_button.setEnabled(False)
@@ -258,13 +260,38 @@ def set_filename(self, filename):
         filename = to_text_string(filename) # filename is a QString instance
         self.kill_if_running()
         index, _data = self.get_data(filename)
-        if index is None:
-            self.filecombo.addItem(filename)
-            self.filecombo.setCurrentIndex(self.filecombo.count()-1)
+        is_parent = self.parent is not None
+
+        if filename not in self.curr_filenames:
+            self.filecombo.insertItem(0, filename)
+            self.curr_filenames.insert(0, filename)
+            self.filecombo.setCurrentIndex(0)
         else:
-            self.filecombo.setCurrentIndex(self.filecombo.findText(filename))
+            index = self.filecombo.findText(filename)
+            self.filecombo.removeItem(index)
+            self.curr_filenames.pop(index)
+            self.filecombo.insertItem(0, filename)
+            self.curr_filenames.insert(0, filename)
+            self.filecombo.setCurrentIndex(0)
+
+        num_elements = self.filecombo.count()
+        if is_parent and (num_elements >
+                          self.parent.get_option('max_entries')):
+            self.filecombo.removeItem(num_elements - 1)
+            self.curr_filenames.pop(num_elements - 1)
         self.filecombo.selected()
 
+    def change_history_limit(self, new_limit):
+        """Change the number of files listed in the history combobox."""
+        if self.filecombo.count() > new_limit:
+            num_elements = self.filecombo.count()
+            diff = num_elements - new_limit
+            for __ in range(diff):
+                num_elements = self.filecombo.count()
+                self.filecombo.removeItem(num_elements - 1)
+                self.curr_filenames.pop(num_elements - 1)
+            self.filecombo.selected()
+
     def analyze(self, filename=None):
         """
         Perform code analysis for given `filename`.
@@ -456,10 +483,7 @@ def finished(self, exit_code, exit_status):
             else:
                 pylint_item = (module, items["line_nb"], items["message"],
                                items["msg_id"], items["message_name"])
-                act_result = results[line[0] + ':']
-                if (self.parent is not None and
-                    len(act_result) < self.parent.get_option('max_entries')):
-                    results[line[0] + ':'].append(pylint_item)
+                results[line[0] + ':'].append(pylint_item)
 
         # Rate
         rate = None


Test patch:
diff --git a/spyder/plugins/pylint/tests/test_pylint.py b/spyder/plugins/pylint/tests/test_pylint.py
--- a/spyder/plugins/pylint/tests/test_pylint.py
+++ b/spyder/plugins/pylint/tests/test_pylint.py
@@ -86,9 +86,26 @@ def pylint_test_script(pylintrc_search_paths):
     with open(script_path, mode="w",
               encoding="utf-8", newline="\n") as script_file:
         script_file.write(PYLINT_TEST_SCRIPT)
+
     return script_path
 
 
+@pytest.fixture
+def pylint_test_scripts(pylintrc_search_paths):
+    def _pylint_test_scripts(filenames):
+        """Write scripts for testing Pylint to a temporary directory."""
+        script_paths = []
+        for filename in filenames:
+            script_path = osp.join(
+                pylintrc_search_paths[SCRIPT_DIR], filename)
+            with open(script_path, mode="w",
+                      encoding="utf-8", newline="\n") as script_file:
+                script_file.write(PYLINT_TEST_SCRIPT)
+            script_paths.append(script_path)
+        return script_paths
+    return _pylint_test_scripts
+
+
 @pytest.fixture(
     params=[
         [], [SCRIPT_DIR], [WORKING_DIR], [PROJECT_DIR], [HOME_DIR],
@@ -185,10 +202,10 @@ def test_pylint_widget_pylintrc(
                 for bad_name in bad_names])
 
 
-def test_pylint_max_history_conf(pylint_test_script, mocker, qtbot):
+def test_pylint_max_history_conf(pylint_test_scripts, mocker):
     """Regression test for checking max_entries configuration.
 
-    For further information see spyder-ide/spyder#12874
+    For further information see spyder-ide/spyder#12884
     """
     # Create the pylint widget for code analysis
     main_window = MainWindowMock()
@@ -196,22 +213,36 @@ def test_pylint_max_history_conf(pylint_test_script, mocker, qtbot):
         return_value=None)
     pylint_sw = Pylint(parent=main_window)
     pylint_widget = PylintWidget(parent=pylint_sw)
+    pylint_widget.filecombo.clear()
+
+    script_0, script_1, script_2 = pylint_test_scripts(
+        ["test_script_{}.py".format(n) for n in range(3)])
 
     # Change the max_entry to 2
     pylint_widget.parent.set_option('max_entries', 2)
+    pylint_widget.change_history_limit(2)
     assert pylint_widget.parent.get_option('max_entries') == 2
 
-    # Analyze the test script with 1 as max_entry
-    pylint_widget.analyze(filename=pylint_test_script)
-    qtbot.waitUntil(
-        lambda: pylint_widget.get_data(pylint_test_script)[1] is not None,
-        timeout=5000)
-    pylint_data = pylint_widget.get_data(filename=pylint_test_script)
-    results = pylint_data[1][-1]
+    # Call to set_filename
+    pylint_widget.set_filename(filename=script_0)
+    assert pylint_widget.filecombo.count() == 1
+
+    # Add to more filenames
+    pylint_widget.set_filename(filename=script_1)
+    pylint_widget.set_filename(filename=script_2)
+
+    assert pylint_widget.filecombo.count() == 2
+
+    assert 'test_script_2.py' in pylint_widget.curr_filenames[0]
+    assert 'test_script_1.py' in pylint_widget.curr_filenames[1]
+
+    # Change the max entry to 1
+    pylint_widget.parent.set_option('max_entries', 1)
+    pylint_widget.change_history_limit(1)
+
+    assert pylint_widget.filecombo.count() == 1
 
-    max_entries = pylint_widget.parent.get_option('max_entries')
-    for key in results:
-        assert len(results[key]) <= max_entries
+    assert 'test_script_2.py' in pylint_widget.curr_filenames[0]
 
 
 if __name__ == "__main__":