Repository: spyder-ide/spyder

Issue:
Pylint should follow the opened file
_From [contrebasse](https://code.google.com/u/contrebasse/) on 2013-08-26T07:29:21Z_

Spyder Version:  2.3.0dev
Python Version:  2.7.4
Qt Version    :  4.8.4, PyQt4 (API v1) 4.10 on Linux
IPython >=1.0   :  0.13.2 (NOK)
matplotlib >=1.0:  1.2.1 (OK)
pep8 >=0.6      :  1.4 (OK)
pyflakes 0.5.0  :  0.6.1 (NOK)
pylint >=0.25   :  0.26.0 (OK)
rope >=0.9.2    :  0.9.2 (OK)
sphinx >=0.6.6  :  1.1.3 (OK) 

What steps will reproduce the problem?
1. Run a pylint analysis on the current opened file in the editor
2. Change the active file in the editor What is the expected output? What do you see instead? I expect to see the pylint analysis for the new file or a blank analysis if no previous analysis is found. Instead, I still see the analysis for the previous file, which is misleading. 

Please provide any additional information below

. I encountered this problem when trying to improve two modules that work tightly together, and I was constantly switching between the two files.

_Original issue: http://code.google.com/p/spyderlib/issues/detail?id=1536_



Discussion:
_From [ccordoba12](https://code.google.com/u/ccordoba12/) on 2013-09-04T12:09:35Z_

Thanks for the sugesstion.

**Labels:** MS-v2.3  

_From [ccordoba12](https://code.google.com/u/ccordoba12/) on 2013-12-11T12:49:27Z_

**Labels:** -MS-v2.3 MS-v2.3.1  

_From [ccordoba12](https://code.google.com/u/ccordoba12/) on 2014-07-17T09:38:57Z_

**Labels:** -MS-v2.3.1 MS-v2.3.2  

_From [ccordoba12](https://code.google.com/u/ccordoba12/) on 2014-09-21T11:05:14Z_

The plan for this one is to connect the pylint text field to the signal the Editor emits when changing from tab to tab, and put there the file name so pylint is ready to evaluate it.

**Labels:** Easy  

_From [ccordoba12](https://code.google.com/u/ccordoba12/) on 2014-10-31T15:10:19Z_

**Labels:** -MS-v2.3.2 MS-v2.4  


Resolving patch:
diff --git a/spyder/plugins/editor/plugin.py b/spyder/plugins/editor/plugin.py
--- a/spyder/plugins/editor/plugin.py
+++ b/spyder/plugins/editor/plugin.py
@@ -96,6 +96,9 @@ class Editor(SpyderPluginWidget):
     run_in_current_extconsole = Signal(str, str, str, bool, bool)
     open_file_update = Signal(str)
 
+    # This signal is fired for any focus change among all editor stacks
+    sig_editor_focus_changed = Signal()
+
     def __init__(self, parent, ignore_last_opened_files=False):
         SpyderPluginWidget.__init__(self, parent)
 
@@ -1241,6 +1244,7 @@ def register_editorstack(self, editorstack):
                                    lambda: self.sig_update_plugin_title.emit())
         editorstack.editor_focus_changed.connect(self.save_focus_editorstack)
         editorstack.editor_focus_changed.connect(self.main.plugin_focus_changed)
+        editorstack.editor_focus_changed.connect(self.sig_editor_focus_changed)
         editorstack.zoom_in.connect(lambda: self.zoom(1))
         editorstack.zoom_out.connect(lambda: self.zoom(-1))
         editorstack.zoom_reset.connect(lambda: self.zoom(0))
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
@@ -64,9 +64,12 @@ def __init__(self, parent=None):
                                        triggered=self.change_history_depth)
         self.pylint.treewidget.common_actions += (None, history_action)
 
+        # Follow editorstacks tab change
+        self.main.editor.sig_editor_focus_changed.connect(self.set_filename)
+
         # Initialize plugin
         self.initialize_plugin()
-        
+
     #------ SpyderPluginWidget API --------------------------------------------
     def get_plugin_title(self):
         """Return widget title"""
@@ -83,7 +86,7 @@ def get_focus_widget(self):
         this plugin's dockwidget is raised on top-level
         """
         return self.pylint.treewidget
-    
+
     def get_plugin_actions(self):
         """Return a list of actions related to plugin"""
         return self.pylint.treewidget.get_menu_actions()
@@ -99,30 +102,30 @@ def register_plugin(self):
         self.pylint.redirect_stdio.connect(
             self.main.redirect_internalshell_stdio)
         self.main.add_dockwidget(self)
-        
+
         pylint_act = create_action(self, _("Run static code analysis"),
                                    triggered=self.run_pylint)
         pylint_act.setEnabled(is_module_installed('pylint'))
         self.register_shortcut(pylint_act, context="Pylint",
                                name="Run analysis")
-        
+
         self.main.source_menu_actions += [MENU_SEPARATOR, pylint_act]
         self.main.editor.pythonfile_dependent_actions += [pylint_act]
 
     def refresh_plugin(self):
         """Refresh pylint widget"""
         self.pylint.remove_obsolete_items()
-        
+
     def closing_plugin(self, cancelable=False):
         """Perform actions before parent main window is closed"""
         return True
-            
+
     def apply_plugin_settings(self, options):
         """Apply configuration file's plugin settings"""
-        # The history depth option will be applied at 
+        # The history depth option will be applied at
         # next Spyder startup, which is soon enough
         pass
-        
+
     #------ Public API --------------------------------------------------------
     @Slot()
     def change_history_depth(self):
@@ -134,6 +137,15 @@ def change_history_depth(self):
         if valid:
             self.set_option('max_entries', depth)
 
+    def get_filename(self):
+        """Get current filename in combobox."""
+        return self.pylint.get_filename()
+
+    @Slot()
+    def set_filename(self):
+        """Set filename without code analysis."""
+        self.pylint.set_filename(self.main.editor.get_current_filename())
+
     @Slot()
     def run_pylint(self):
         """Run pylint code analysis"""
@@ -142,7 +154,7 @@ def run_pylint(self):
             return
         self.switch_to_plugin()
         self.analyze(self.main.editor.get_current_filename())
-        
+
     def analyze(self, filename):
         """Reimplement analyze method"""
         if self.dockwidget and not self.ismaximized:
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
@@ -231,7 +231,13 @@ def __init__(self, parent, max_entries=100, options_button=None,
         else:
             self.start_button.setEnabled(False)
 
-    def analyze(self, filename):
+    def get_filename(self):
+        """Get current filename in combobox."""
+        return self.filecombo.currentText()
+
+    @Slot(str)
+    def set_filename(self, filename):
+        """Set filename without performing code analysis."""
         filename = to_text_string(filename) # filename is a QString instance
         self.kill_if_running()
         index, _data = self.get_data(filename)
@@ -241,6 +247,16 @@ def analyze(self, filename):
         else:
             self.filecombo.setCurrentIndex(self.filecombo.findText(filename))
         self.filecombo.selected()
+
+    def analyze(self, filename=None):
+        """
+        Perform code analysis for given `filename`.
+
+        If `filename` is None default to current filename in combobox.
+        """
+        if filename is not None:
+            self.set_filename(filename)
+
         if self.filecombo.is_valid():
             self.start()
 


Test patch:
diff --git a/spyder/app/tests/test_mainwindow.py b/spyder/app/tests/test_mainwindow.py
--- a/spyder/app/tests/test_mainwindow.py
+++ b/spyder/app/tests/test_mainwindow.py
@@ -1822,5 +1822,43 @@ def test_custom_layouts(main_window, qtbot):
                                 assert widget.isVisible()
 
 
+# @pytest.mark.slow
+def test_pylint_follows_file(qtbot, tmpdir, main_window):
+    """Test that file editor focus change updates pylint combobox filename."""
+    for plugin in main_window.thirdparty_plugins:
+        if plugin.CONF_SECTION == 'pylint':
+            pylint_plugin = plugin
+            break
+
+    # Show pylint plugin
+    pylint_plugin.dockwidget.show()
+    pylint_plugin.dockwidget.raise_()
+
+    # Create base temporary directory
+    basedir = tmpdir.mkdir('foo')
+
+    # Open some files
+    for idx in range(2):
+        fh = basedir.join('{}.py'.format(idx))
+        fname = str(fh)
+        fh.write('print("Hello world!")')
+        main_window.open_file(fh)
+        qtbot.wait(200)
+        assert fname == pylint_plugin.get_filename()
+
+    # Create a editor split
+    main_window.editor.editorsplitter.split(orientation=Qt.Vertical)
+    qtbot.wait(500)
+
+    # Open other files
+    for idx in range(4):
+        fh = basedir.join('{}.py'.format(idx))
+        fh.write('print("Hello world!")')
+        fname = str(fh)
+        main_window.open_file(fh)
+        qtbot.wait(200)
+        assert fname == pylint_plugin.get_filename()
+
+
 if __name__ == "__main__":
     pytest.main()