Repository: spyder-ide/spyder

Issue:
Moving tabs in the Editor is slow
## Problem Description
@goanpeca

PR #9048 introduced some undesired lag when reordering tabs in the Editor.

More specifically, the problem comes from the connection made [here](https://github.com/spyder-ide/spyder/commit/0325489bd19763938eaf792e7299a7c6b184c530#diff-36807ba8f009af540ab6f82a4dc31802R1094):

```
editorstack.current_file_changed.connect(self.vcs_status.update_vcs)
```

### What steps reproduce the problem?

1. Move tabs in the Editor.

## Versions
* Spyder version: Spyder 4.0.0.dev0 (Commit: 64a9d6ad9) 
* Python version: Python 3.7.1 64-bit
* Qt version: Qt 5.9.6
* PyQt version: PyQt5 5.9.2
* Operating System name/version: Windows 10 



Discussion:
I think it is also making the opening of files on startup and the creation of split Editorstacks slower.
Thanks! I will take a look at it
Here is an idea that seems to solve the issue for me at least :

```python
class GitWorker(QObject):
    """A worker to get Git active branch, state, branches (plus tags)."""
    sig_git_finished = Signal(list, str, list)

    def __init__(self):
        super(GitWorker, self).__init__(parent=None)
        self.fpath = None

    def get_git_refs(self):
        """Get Git active branch, state, branches (plus tags)."""
        branches = []
        branch = ''
        files_modified = []
        try:
            branches, branch, files_modified = get_git_refs(self.fpath)
        except Exception:
            # Important note: we have to handle unexpected exceptions by
            # ourselves because they won't be catched by the main thread
            # (known QThread limitation/bug)
            traceback.print_exc()
        self.sig_git_finished.emit(branches, branch, files_modified)


class VCSStatus(StatusBarWidget):
    """Status bar widget for system vcs."""
    TIP = _("Git branch")

    def __init__(self, parent, statusbar):
        super(VCSStatus, self).__init__(parent, statusbar,
                                        icon=ima.icon('code_fork'))
        self.git_worker = GitWorker()
        self.git_thread = QThread()
        self.git_worker.moveToThread(self.git_thread)
        self.git_worker.sig_git_finished.connect(self.process_git_data)
        self.git_thread.started.connect(self.git_worker.get_git_refs)
        self._job_waiting = None

    def update_vcs_state(self, idx, fname, fname2):
        """Update vcs status."""
        self.update_vcs(fname, None)

    def update_vcs(self, fname, index, force=False):
        """Update vcs status."""
        fpath = os.path.dirname(fname)
        if fpath != self.git_worker.fpath or force:
            if self.git_thread.isRunning():
                self._job_waiting = (fname, index)
            else:
                self.git_worker.fpath = fpath
                self.git_thread.start()
                self._job_waiting = None

    @Slot(list, str, list)
    def process_git_data(self, branches, branch, files_modified):
        """Receive data from git and update gui."""
        self.git_thread.quit()

        text = branch if branch else ''
        if len(files_modified):
            text = text + ' [{}]'.format(len(files_modified))
        self.setVisible(bool(branch))
        self.set_value(text)

        self.git_thread.wait()
        if self._job_waiting:
            self.update_vcs(*self._job_waiting)

    def change_branch(self):
        """Change current branch."""
        pass
```
@goanpeca I would be glad to help you with this if you want. I find this bug very annoying, so I wouln't mind putting some time helping you to fix this.
Hi @jnsebgosselin yes please work on this. But do not create a worker from scratch.

Try to use this

https://github.com/spyder-ide/spyder/blob/master/spyder/utils/workers.py

Something like:

```python
class VCSStatus(StatusBarWidget):
    """Status bar widget for system vcs."""
    TIP = _("Git branch")

    def __init__(self, parent, statusbar):
        super(VCSStatus, self).__init__(parent, statusbar,
                                        icon=ima.icon('code_fork'))
        self.wm = WorkerManager(max_threads=1)

   def update_vcs(self):
        worker = self.wm.create_python_worker(self.func_to_run_in_thread, *args, **kwargs)
        worker.sig_finished.connect(self.func_to_handle_after_finshed) 
        worker.some_variable_to_carry =  'hello'
        worker.start()

   def func_to_handle_after_finshed(self, worker, output, error):
        pass
        print(worker.some_variable_to_carry)
```

Thanks!

The idea is to make the code short and reusable for workers. Also if you find something that could be improved int the worker logic please also make changes there :-) !

This scheme handles errors and are reported on `error` parameter. The output of the method run in the thread is the `output` and the worker is also returned in case something needs to be done with the worker. Like carrying variables arround
Excellent, thank you very much @goanpeca !

Resolving patch:
diff --git a/spyder/plugins/editor/widgets/status.py b/spyder/plugins/editor/widgets/status.py
--- a/spyder/plugins/editor/widgets/status.py
+++ b/spyder/plugins/editor/widgets/status.py
@@ -7,12 +7,13 @@
 """Status bar widgets."""
 
 # Standard library imports
-import os
+import os.path as osp
 
 # Local imports
 from spyder.config.base import _
 from spyder.py3compat import to_text_string
 from spyder.utils import icon_manager as ima
+from spyder.utils.workers import WorkerManager
 from spyder.utils.vcs import get_git_refs
 from spyder.widgets.status import StatusBarWidget
 
@@ -65,23 +66,51 @@ class VCSStatus(StatusBarWidget):
     def __init__(self, parent, statusbar):
         super(VCSStatus, self).__init__(parent, statusbar,
                                         icon=ima.icon('code_fork'))
+        self._worker_manager = WorkerManager(max_threads=1)
+        self._git_is_working = None
+        self._git_job_queue = None
+        self._last_git_job = None
 
     def update_vcs_state(self, idx, fname, fname2):
         """Update vcs status."""
         self.update_vcs(fname, None)
 
-    def update_vcs(self, fname, index):
+    def update_vcs(self, fname, index, force=False):
         """Update vcs status."""
-        fpath = os.path.dirname(fname)
-        branches, branch, files_modified = get_git_refs(fpath)
-        text = branch if branch else ''
+        if self._last_git_job == (fname, index) and not force:
+            self._git_job_queue = None
+            return
+
+        if self._git_is_working:
+            self._git_job_queue = (fname, index)
+        else:
+            self._worker_manager.terminate_all()
+            worker = self._worker_manager.create_python_worker(
+                self.get_git_refs, fname)
+            worker.sig_finished.connect(self.process_git_data)
+            self._last_git_job = (fname, index)
+            self._git_job_queue = None
+            self._git_is_working = True
+            worker.start()
+
+    def get_git_refs(self, fname):
+        """Get Git active branch, state, branches (plus tags)."""
+        return get_git_refs(osp.dirname(fname))
+
+    def process_git_data(self, worker, output, error):
+        """Receive data from git and update gui."""
+        branches, branch, files_modified = output
 
+        text = branch if branch else ''
         if len(files_modified):
             text = text + ' [{}]'.format(len(files_modified))
-
         self.setVisible(bool(branch))
         self.set_value(text)
 
+        self._git_is_working = False
+        if self._git_job_queue:
+            self.update_vcs(*self._git_job_queue)
+
     def change_branch(self):
         """Change current branch."""
         pass


Test patch: