[gedit-plugins] Bug 702336 - Show the file's git status in the file browser



commit 9a414947265750c2e2245048d5e432381020e3f1
Author: Garrett Regier <garrettregier gmail com>
Date:   Mon Jul 1 06:43:42 2013 -0700

    Bug 702336 - Show the file's git status in the file browser

 plugins/git/git/Makefile.am          |    3 +-
 plugins/git/git/__init__.py          |    3 +-
 plugins/git/git/viewactivatable.py   |   19 ++-
 plugins/git/git/windowactivatable.py |  237 ++++++++++++++++++++++++++++++++++
 4 files changed, 255 insertions(+), 7 deletions(-)
---
diff --git a/plugins/git/git/Makefile.am b/plugins/git/git/Makefile.am
index c3ab9af..da7fe1e 100644
--- a/plugins/git/git/Makefile.am
+++ b/plugins/git/git/Makefile.am
@@ -5,6 +5,7 @@ plugindir = $(GEDIT_PLUGINS_LIBS_DIR)/git
 plugin_PYTHON =                \
        diffrenderer.py         \
        __init__.py             \
-       viewactivatable.py
+       viewactivatable.py      \
+       windowactivatable.py
 
 -include $(top_srcdir)/git.mk
diff --git a/plugins/git/git/__init__.py b/plugins/git/git/__init__.py
index 9ee99c5..bcb2787 100644
--- a/plugins/git/git/__init__.py
+++ b/plugins/git/git/__init__.py
@@ -17,6 +17,7 @@
 #  Foundation, Inc., 59 Temple Place, Suite 330,
 #  Boston, MA 02111-1307, USA.
 
-from .viewactivatable import GitPlugin
+from .viewactivatable import GitViewActivatable
+from .windowactivatable import GitWindowActivatable
 
 # ex:ts=4:et:
diff --git a/plugins/git/git/viewactivatable.py b/plugins/git/git/viewactivatable.py
index 84600f6..2426914 100644
--- a/plugins/git/git/viewactivatable.py
+++ b/plugins/git/git/viewactivatable.py
@@ -19,6 +19,7 @@
 
 from gi.repository import GLib, GObject, Gtk, Gedit, Ggit
 from .diffrenderer import DiffType, DiffRenderer
+from .windowactivatable import GitWindowActivatable
 
 import difflib
 
@@ -31,19 +32,22 @@ class LineContext:
         self.line_type = DiffType.NONE
 
 
-class GitPlugin(GObject.Object, Gedit.ViewActivatable):
+class GitViewActivatable(GObject.Object, Gedit.ViewActivatable):
     view = GObject.property(type=Gedit.View)
 
-    def __init__(self):
-        GObject.Object.__init__(self)
+    status = GObject.property(type=Ggit.StatusFlags,
+                              default=Ggit.StatusFlags.CURRENT)
 
-        Ggit.init()
+    def __init__(self):
+        super().__init__()
 
         self.diff_timeout = 0
         self.file_contents_list = None
         self.file_context = None
 
     def do_activate(self):
+        GitWindowActivatable.register_view_activatable(self)
+
         self.diff_renderer = DiffRenderer()
         self.gutter = self.view.get_gutter(Gtk.TextWindowType.LEFT)
 
@@ -162,6 +166,8 @@ class GitPlugin(GObject.Object, Gedit.ViewActivatable):
 
         # Must be a new file
         if not self.file_contents_list:
+            self.status = Ggit.StatusFlags.WORKING_TREE_NEW
+
             n_lines = self.buffer.get_line_count()
             if len(self.diff_renderer.file_context) == n_lines:
                 return False
@@ -192,7 +198,10 @@ class GitPlugin(GObject.Object, Gedit.ViewActivatable):
 
         except StopIteration:
             # Nothing has changed
-            pass
+            self.status = Ggit.StatusFlags.CURRENT
+
+        else:
+            self.status = Ggit.StatusFlags.WORKING_TREE_MODIFIED
 
         file_context = {}
         for line_data in diff:
diff --git a/plugins/git/git/windowactivatable.py b/plugins/git/git/windowactivatable.py
new file mode 100644
index 0000000..6d69f35
--- /dev/null
+++ b/plugins/git/git/windowactivatable.py
@@ -0,0 +1,237 @@
+# -*- coding: utf-8 -*-
+
+#  Copyright (C) 2013 - Garrett Regier
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330,
+#  Boston, MA 02111-1307, USA.
+
+from gi.repository import GLib, GObject, Gio, Gtk, Gedit, Ggit
+
+import weakref
+
+
+class GitWindowActivatable(GObject.Object, Gedit.WindowActivatable):
+    window = GObject.property(type=Gedit.Window)
+
+    windows = weakref.WeakValueDictionary()
+
+    def __init__(self):
+        super().__init__()
+
+        Ggit.init()
+
+        self.view_activatables = weakref.WeakSet()
+
+        self.files = {}
+        self.monitors = {}
+
+        self.repo = None
+        self.tree = None
+
+    @classmethod
+    def register_view_activatable(cls, view_activatable):
+        window = view_activatable.view.get_toplevel()
+
+        if window not in cls.windows:
+            return None
+
+        window_activatable = cls.windows[window]
+
+        window_activatable.view_activatables.add(view_activatable)
+        view_activatable.connect('notify::status',
+                                 window_activatable.notify_status)
+
+        return window_activatable
+
+    def do_activate(self):
+        # self.window is not set until now
+        self.windows[self.window] = self
+
+        self.bus = self.window.get_message_bus()
+
+        self.files = {}
+        self.file_names = {}
+        self.monitors = {}
+
+        self.window_signals = [
+            self.window.connect('tab-removed', self.tab_removed),
+            self.window.connect('focus-in-event', self.focus_in_event)
+        ]
+
+        self.bus_signals = [
+            self.bus.connect('/plugins/filebrowser', 'root_changed',
+                             self.root_changed, None),
+            self.bus.connect('/plugins/filebrowser', 'inserted',
+                             self.inserted, None),
+            self.bus.connect('/plugins/filebrowser', 'deleted',
+                             self.deleted, None)
+        ]
+
+        self.refresh();
+
+    def do_deactivate(self):
+        self.clear_monitors()
+
+        for sid in self.window_signals:
+            self.window.disconnect(sid)
+
+        for sid in self.bus_signals:
+            self.bus.disconnect(sid)
+
+        self.files = {}
+        self.file_names = {}
+        self.repo = None
+        self.tree = None
+        self.window_signals = []
+        self.bus_signals = []
+
+        self.refresh();
+
+    def refresh(self):
+        if self.bus.is_registered('/plugins/filebrowser', 'refresh'):
+            self.bus.send('/plugins/filebrowser', 'refresh')
+
+    def notify_status(self, view_activatable, psepc):
+        location = view_activatable.view.get_buffer().get_location()
+
+        if location is not None:
+            self.update_location(location)
+
+    def tab_removed(self, window, tab):
+        view = tab.get_view()
+        location = view.get_buffer().get_location()
+
+        if location is None:
+            return
+
+        # Need to remove the view activatable otherwise update_location()
+        # will might use the view's status and not the file's actual status
+        for view_activatable in self.view_activatables:
+            if view_activatable.view == view:
+                self.view_activatables.remove(view_activatable)
+                break
+
+        self.update_location(location)
+
+    def focus_in_event(self, window, event):
+        for view_activatable in self.view_activatables:
+            view_activatable.update()
+
+        for uri in self.files:
+            self.update_location(Gio.File.new_for_uri(uri))
+
+    def root_changed(self, bus, msg, data=None):
+        self.clear_monitors()
+
+        try:
+            repo_file = Ggit.Repository.discover(msg.location)
+            self.repo = Ggit.Repository.open(repo_file)
+            head = self.repo.get_head()
+            commit = self.repo.lookup(head.get_target(), Ggit.Commit.__gtype__)
+            self.tree = commit.get_tree()
+
+        except Exception as e:
+            self.repo = None
+            self.tree = None
+
+        else:
+            self.monitor_directory(msg.location)
+
+    def inserted(self, bus, msg, data=None):
+        self.files[msg.location.get_uri()] = msg.id
+        self.file_names[msg.location.get_uri()] = msg.name
+
+        self.update_location(msg.location)
+
+        if msg.is_directory:
+            self.monitor_directory(msg.location)
+
+    def deleted(self, bus, msg, data=None):
+        # File browser's deleted signal is broken
+        return
+
+        uri = msg.location.get_uri()
+
+        del self.files[uri]
+        del self.file_names[uri]
+
+        if uri in self.monitors:
+            self.monitors[uri].cancel()
+            del self.monitors[uri]
+
+    def update_location(self, location):
+        if self.repo is None:
+            return
+
+        if location.get_uri() not in self.files:
+            return
+
+        status = None
+
+        # First get the status from the open documents
+        for view_activatable in self.view_activatables:
+            doc_location = view_activatable.view.get_buffer().get_location()
+
+            if doc_location is not None and doc_location.equal(location):
+                status = view_activatable.status
+                break
+
+        try:
+            git_status = self.repo.file_status(location)
+
+        except Exception:
+            pass
+
+        else:
+            # Don't use the view activatable's
+            # status if the file is ignored
+            if status is None or git_status & Ggit.StatusFlags.IGNORED:
+                status = git_status
+
+        markup = GLib.markup_escape_text(self.file_names[location.get_uri()]) 
+
+        if status is not None:
+            if status & Ggit.StatusFlags.INDEX_NEW or \
+                    status & Ggit.StatusFlags.WORKING_TREE_NEW:
+                markup = '<span foreground="green">%s</span> [New]' % (markup)
+
+            elif status & Ggit.StatusFlags.INDEX_MODIFIED or \
+                    status & Ggit.StatusFlags.WORKING_TREE_MODIFIED:
+                markup = '<span foreground="blue">%s</span> [Modified]' % (markup)
+
+        self.bus.send('/plugins/filebrowser', 'set_markup',
+                      id=self.files[location.get_uri()], markup=markup)
+
+    def clear_monitors(self):
+        for uri in self.monitors:
+            self.monitors[uri].cancel()
+
+        self.monitors = {}
+
+    def monitor_directory(self, location):
+        monitor = location.monitor(Gio.FileMonitorFlags.NONE, None)
+        self.monitors[location.get_uri()] = monitor
+
+        monitor.connect('changed', self.monitor_changed)
+
+    def monitor_changed(self, monitor, file, other_file, event_type):
+        # Only monitor for changes as the file browser will monitor
+        # file created and deleted files and emit inserted and deleted
+        if event_type == Gio.FileMonitorEvent.CHANGED:
+            for f in (file, other_file):
+                if f in self.files:
+                    self.update_location(f)
+
+# ex:ts=4:et:


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]