[gnome-builder/change-monitor] editor: add utility to track changes in the source buffer.



commit b377be37261ea15cae15b6f3ed3a0a43ffee9994
Author: Christian Hergert <christian hergert me>
Date:   Fri Sep 12 17:21:06 2014 -0700

    editor: add utility to track changes in the source buffer.
    
    This only handles insertions at the moment. Followup commits will add
    support for handling deletions from the buffer.

 src/editor/gb-editor-commands.c               |    7 +
 src/editor/gb-editor-tab-private.h            |    7 +
 src/editor/gb-editor-tab.c                    |   18 +
 src/editor/gb-source-change-gutter-renderer.c |  189 ++++++++++++
 src/editor/gb-source-change-gutter-renderer.h |   55 ++++
 src/editor/gb-source-change-monitor.c         |  410 +++++++++++++++++++++++++
 src/editor/gb-source-change-monitor.h         |   66 ++++
 src/gnome-builder.mk                          |    4 +
 src/resources/ui/gb-editor-tab.ui             |    3 +
 9 files changed, 759 insertions(+), 0 deletions(-)
---
diff --git a/src/editor/gb-editor-commands.c b/src/editor/gb-editor-commands.c
index 27fa6a1..f3e477c 100644
--- a/src/editor/gb-editor-commands.c
+++ b/src/editor/gb-editor-commands.c
@@ -366,6 +366,9 @@ on_load_cb (GtkSourceFileLoader *loader,
       g_clear_error (&error);
     }
 
+  gb_source_change_monitor_reset (tab->priv->change_monitor);
+  gtk_source_gutter_renderer_set_visible (tab->priv->change_renderer, TRUE);
+
   g_object_unref (tab);
 }
 
@@ -386,6 +389,8 @@ gb_editor_tab_open_file (GbEditorTab *tab,
   loader = gtk_source_file_loader_new (GTK_SOURCE_BUFFER (priv->document),
                                        priv->file);
 
+  gtk_source_gutter_renderer_set_visible (priv->change_renderer, FALSE);
+
   gtk_source_file_loader_load_async (loader,
                                      G_PRIORITY_DEFAULT,
                                      NULL, /* TODO: Cancellable */
@@ -633,6 +638,8 @@ gb_editor_commands_new_tab (GbEditorWorkspace *workspace,
                            "position", &page,
                            NULL);
   gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), page);
+
+  gtk_widget_grab_focus (GTK_WIDGET (tab));
 }
 
 static gboolean
diff --git a/src/editor/gb-editor-tab-private.h b/src/editor/gb-editor-tab-private.h
index 318dc83..c1c26b3 100644
--- a/src/editor/gb-editor-tab-private.h
+++ b/src/editor/gb-editor-tab-private.h
@@ -28,6 +28,7 @@
 #include "gb-editor-settings.h"
 #include "gb-markdown-preview.h"
 #include "gb-notebook.h"
+#include "gb-source-change-monitor.h"
 #include "gb-source-search-highlighter.h"
 #include "gb-source-snippet-completion-provider.h"
 #include "gb-source-view.h"
@@ -56,6 +57,12 @@ struct _GbEditorTabPrivate
   GtkSourceSearchContext    *search_context;
 
   /*
+   * Change (add, change, etc) tracking of the editor.
+   */
+  GbSourceChangeMonitor *change_monitor;
+  GtkSourceGutterRenderer *change_renderer;
+
+  /*
    * Tab related settings.
    */
   GbEditorSettings *settings;
diff --git a/src/editor/gb-editor-tab.c b/src/editor/gb-editor-tab.c
index a9ec8b8..6a82a12 100644
--- a/src/editor/gb-editor-tab.c
+++ b/src/editor/gb-editor-tab.c
@@ -24,6 +24,7 @@
 #include "gb-editor-tab-private.h"
 #include "gb-log.h"
 #include "gb-rgba.h"
+#include "gb-source-change-gutter-renderer.h"
 #include "gb-source-snippet.h"
 #include "gb-source-snippets-manager.h"
 #include "gb-source-snippets.h"
@@ -1027,6 +1028,21 @@ gb_editor_tab_constructed (GObject *object)
                                "language", G_BINDING_SYNC_CREATE,
                                transform_file_to_language, NULL, tab, NULL);
 
+  {
+    GtkSourceGutter *gutter;
+
+    gutter = gtk_source_view_get_gutter (GTK_SOURCE_VIEW (priv->source_view),
+                                         GTK_TEXT_WINDOW_LEFT);
+    priv->change_renderer =
+        g_object_new (GB_TYPE_SOURCE_CHANGE_GUTTER_RENDERER,
+                      "change-monitor", priv->change_monitor,
+                      "size", 3,
+                      "visible", TRUE,
+                      "xpad", 3,
+                      NULL);
+    gtk_source_gutter_insert (gutter, priv->change_renderer, 0);
+  }
+
   gb_editor_tab_cursor_moved (tab, priv->document);
 
   EXIT;
@@ -1208,6 +1224,7 @@ gb_editor_tab_class_init (GbEditorTabClass *klass)
 
   gtk_widget_class_bind_template_child_private (widget_class, GbEditorTab, floating_bar);
   gtk_widget_class_bind_template_child_private (widget_class, GbEditorTab, document);
+  gtk_widget_class_bind_template_child_private (widget_class, GbEditorTab, change_monitor);
   gtk_widget_class_bind_template_child_private (widget_class, GbEditorTab, file);
   gtk_widget_class_bind_template_child_private (widget_class, GbEditorTab, go_down_button);
   gtk_widget_class_bind_template_child_private (widget_class, GbEditorTab, go_up_button);
@@ -1224,6 +1241,7 @@ gb_editor_tab_class_init (GbEditorTabClass *klass)
   gtk_widget_class_bind_template_child_private (widget_class, GbEditorTab, source_view);
 
   g_type_ensure (GB_TYPE_EDITOR_DOCUMENT);
+  g_type_ensure (GB_TYPE_SOURCE_CHANGE_MONITOR);
   g_type_ensure (GB_TYPE_SOURCE_VIEW);
   g_type_ensure (GB_TYPE_SOURCE_SNIPPET_COMPLETION_PROVIDER);
   g_type_ensure (GB_TYPE_SOURCE_SEARCH_HIGHLIGHTER);
diff --git a/src/editor/gb-source-change-gutter-renderer.c b/src/editor/gb-source-change-gutter-renderer.c
new file mode 100644
index 0000000..0c8597d
--- /dev/null
+++ b/src/editor/gb-source-change-gutter-renderer.c
@@ -0,0 +1,189 @@
+/* gb-source-change-gutter-renderer.c
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "gb-source-change-gutter-renderer.h"
+#include "gb-source-change-monitor.h"
+
+struct _GbSourceChangeGutterRendererPrivate
+{
+  GbSourceChangeMonitor *change_monitor;
+};
+
+enum
+{
+  PROP_0,
+  PROP_CHANGE_MONITOR,
+  LAST_PROP
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GbSourceChangeGutterRenderer,
+                            gb_source_change_gutter_renderer,
+                            GTK_SOURCE_TYPE_GUTTER_RENDERER)
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+GbSourceChangeMonitor *
+gb_source_change_gutter_renderer_get_change_monitor (GbSourceChangeGutterRenderer *renderer)
+{
+  g_return_val_if_fail (GB_IS_SOURCE_CHANGE_GUTTER_RENDERER (renderer), NULL);
+
+  return renderer->priv->change_monitor;
+}
+
+static void
+gb_source_change_gutter_renderer_set_change_monitor (GbSourceChangeGutterRenderer *renderer,
+                                                     GbSourceChangeMonitor        *monitor)
+{
+  GbSourceChangeGutterRendererPrivate *priv;
+
+  g_return_if_fail (GB_IS_SOURCE_CHANGE_GUTTER_RENDERER (renderer));
+  g_return_if_fail (!monitor || GB_IS_SOURCE_CHANGE_MONITOR (monitor));
+
+  priv = renderer->priv;
+
+  if (priv->change_monitor)
+    {
+      g_object_remove_weak_pointer (G_OBJECT (monitor),
+                                    (gpointer *)&priv->change_monitor);
+      priv->change_monitor = NULL;
+    }
+
+  if (monitor)
+    {
+      priv->change_monitor = monitor;
+      g_object_add_weak_pointer (G_OBJECT (monitor),
+                                 (gpointer *)&priv->change_monitor);
+    }
+}
+
+static void
+gb_source_change_gutter_renderer_draw (GtkSourceGutterRenderer      *renderer,
+                                       cairo_t                      *cr,
+                                       GdkRectangle                 *bg_area,
+                                       GdkRectangle                 *cell_area,
+                                       GtkTextIter                  *begin,
+                                       GtkTextIter                  *end,
+                                       GtkSourceGutterRendererState  state)
+{
+  GbSourceChangeGutterRendererPrivate *priv;
+  GbSourceChangeFlags flags;
+  GdkRGBA rgba;
+  guint lineno;
+
+  g_return_if_fail (GB_IS_SOURCE_CHANGE_GUTTER_RENDERER (renderer));
+  g_return_if_fail (cr);
+  g_return_if_fail (bg_area);
+  g_return_if_fail (cell_area);
+  g_return_if_fail (begin);
+  g_return_if_fail (end);
+
+  priv = GB_SOURCE_CHANGE_GUTTER_RENDERER (renderer)->priv;
+
+  lineno = gtk_text_iter_get_line (begin);
+  flags = gb_source_change_monitor_get_line (priv->change_monitor, lineno);
+
+  if (!flags)
+    return;
+
+  if ((flags & GB_SOURCE_CHANGE_ADDED) != 0)
+    gdk_rgba_parse (&rgba, "#8ae234");
+
+  if ((flags & GB_SOURCE_CHANGE_CHANGED) != 0)
+    gdk_rgba_parse (&rgba, "#fcaf3e");
+
+  gdk_cairo_rectangle (cr, cell_area);
+  gdk_cairo_set_source_rgba (cr, &rgba);
+  cairo_fill (cr);
+}
+
+static void
+gb_source_change_gutter_renderer_dispose (GObject *object)
+{
+  GbSourceChangeGutterRenderer *renderer = (GbSourceChangeGutterRenderer *)object;
+
+  gb_source_change_gutter_renderer_set_change_monitor (renderer, NULL);
+
+  G_OBJECT_CLASS (gb_source_change_gutter_renderer_parent_class)->dispose (object);
+}
+
+static void
+gb_source_change_gutter_renderer_get_property (GObject    *object,
+                                               guint       prop_id,
+                                               GValue     *value,
+                                               GParamSpec *pspec)
+{
+  GbSourceChangeGutterRenderer *renderer = GB_SOURCE_CHANGE_GUTTER_RENDERER (object);
+
+  switch (prop_id) {
+  case PROP_CHANGE_MONITOR:
+    g_value_set_object (value, renderer->priv->change_monitor);
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+gb_source_change_gutter_renderer_set_property (GObject      *object,
+                                               guint         prop_id,
+                                               const GValue *value,
+                                               GParamSpec   *pspec)
+{
+  GbSourceChangeGutterRenderer *renderer = GB_SOURCE_CHANGE_GUTTER_RENDERER (object);
+
+  switch (prop_id) {
+  case PROP_CHANGE_MONITOR:
+    gb_source_change_gutter_renderer_set_change_monitor (
+        renderer, g_value_get_object (value));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+gb_source_change_gutter_renderer_class_init (GbSourceChangeGutterRendererClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkSourceGutterRendererClass *renderer_class = GTK_SOURCE_GUTTER_RENDERER_CLASS (klass);
+
+  object_class->dispose = gb_source_change_gutter_renderer_dispose;
+  object_class->get_property = gb_source_change_gutter_renderer_get_property;
+  object_class->set_property = gb_source_change_gutter_renderer_set_property;
+
+  renderer_class->draw = gb_source_change_gutter_renderer_draw;
+
+  gParamSpecs [PROP_CHANGE_MONITOR] =
+    g_param_spec_object ("change-monitor",
+                         _("Change Monitor"),
+                         _("The change monitor for the gutter renderer."),
+                         GB_TYPE_SOURCE_CHANGE_MONITOR,
+                         (G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT_ONLY |
+                          G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_CHANGE_MONITOR,
+                                   gParamSpecs [PROP_CHANGE_MONITOR]);
+}
+
+static void
+gb_source_change_gutter_renderer_init (GbSourceChangeGutterRenderer *renderer)
+{
+  renderer->priv = gb_source_change_gutter_renderer_get_instance_private (renderer);
+}
diff --git a/src/editor/gb-source-change-gutter-renderer.h b/src/editor/gb-source-change-gutter-renderer.h
new file mode 100644
index 0000000..0aaa077
--- /dev/null
+++ b/src/editor/gb-source-change-gutter-renderer.h
@@ -0,0 +1,55 @@
+/* gb-source-change-gutter-renderer.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GB_SOURCE_CHANGE_GUTTER_RENDERER_H
+#define GB_SOURCE_CHANGE_GUTTER_RENDERER_H
+
+#include <gtksourceview/gtksource.h>
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_SOURCE_CHANGE_GUTTER_RENDERER            (gb_source_change_gutter_renderer_get_type())
+#define GB_SOURCE_CHANGE_GUTTER_RENDERER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GB_TYPE_SOURCE_CHANGE_GUTTER_RENDERER, GbSourceChangeGutterRenderer))
+#define GB_SOURCE_CHANGE_GUTTER_RENDERER_CONST(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GB_TYPE_SOURCE_CHANGE_GUTTER_RENDERER, GbSourceChangeGutterRenderer const))
+#define GB_SOURCE_CHANGE_GUTTER_RENDERER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  
GB_TYPE_SOURCE_CHANGE_GUTTER_RENDERER, GbSourceChangeGutterRendererClass))
+#define GB_IS_SOURCE_CHANGE_GUTTER_RENDERER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GB_TYPE_SOURCE_CHANGE_GUTTER_RENDERER))
+#define GB_IS_SOURCE_CHANGE_GUTTER_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  
GB_TYPE_SOURCE_CHANGE_GUTTER_RENDERER))
+#define GB_SOURCE_CHANGE_GUTTER_RENDERER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  
GB_TYPE_SOURCE_CHANGE_GUTTER_RENDERER, GbSourceChangeGutterRendererClass))
+
+typedef struct _GbSourceChangeGutterRenderer        GbSourceChangeGutterRenderer;
+typedef struct _GbSourceChangeGutterRendererClass   GbSourceChangeGutterRendererClass;
+typedef struct _GbSourceChangeGutterRendererPrivate GbSourceChangeGutterRendererPrivate;
+
+struct _GbSourceChangeGutterRenderer
+{
+  GtkSourceGutterRenderer parent;
+
+  /*< private >*/
+  GbSourceChangeGutterRendererPrivate *priv;
+};
+
+struct _GbSourceChangeGutterRendererClass
+{
+  GtkSourceGutterRendererClass parent_class;
+};
+
+GType gb_source_change_gutter_renderer_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* GB_SOURCE_CHANGE_GUTTER_RENDERER_H */
diff --git a/src/editor/gb-source-change-monitor.c b/src/editor/gb-source-change-monitor.c
new file mode 100644
index 0000000..d5089ee
--- /dev/null
+++ b/src/editor/gb-source-change-monitor.c
@@ -0,0 +1,410 @@
+/* gb-source-change-monitor.c
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "change-monitor"
+
+#include <glib/gi18n.h>
+
+#include "gb-log.h"
+#include "gb-source-change-monitor.h"
+
+struct _GbSourceChangeMonitorPrivate
+{
+  GtkTextBuffer *buffer;
+  GArray        *state;
+
+  guint delete_range_before_handler;
+  guint delete_range_after_handler;
+  guint insert_text_before_handler;
+  guint insert_text_after_handler;
+};
+
+enum
+{
+  PROP_0,
+  PROP_BUFFER,
+  LAST_PROP
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GbSourceChangeMonitor,
+                            gb_source_change_monitor,
+                            G_TYPE_OBJECT)
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+
+static void
+gb_source_change_monitor_insert (GbSourceChangeMonitor *monitor,
+                                 guint                  line,
+                                 GbSourceChangeFlags    flags)
+{
+  guint8 byte = (guint8)flags;
+
+  g_return_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor));
+
+  if (line == monitor->priv->state->len)
+    g_array_append_val (monitor->priv->state, byte);
+  else
+    g_array_insert_val (monitor->priv->state, line, byte);
+}
+
+static void
+gb_source_change_monitor_set_line (GbSourceChangeMonitor *monitor,
+                                   guint                  line,
+                                   GbSourceChangeFlags    flags)
+{
+  GbSourceChangeFlags old_flags;
+
+  g_return_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor));
+  g_return_if_fail (line < monitor->priv->state->len);
+
+  old_flags = g_array_index (monitor->priv->state, guint8, line);
+
+  /*
+   * Don't allow ourselves to go from "added" to "changed" unless we
+   * previously did not have a dirty bit.
+   */
+  if (((old_flags & GB_SOURCE_CHANGE_ADDED) != 0) &&
+      ((old_flags & GB_SOURCE_CHANGE_DIRTY) != 0))
+    {
+      flags &= ~GB_SOURCE_CHANGE_CHANGED;
+      flags |= GB_SOURCE_CHANGE_ADDED;
+    }
+
+  g_array_index (monitor->priv->state, guint8, line) = (guint8)flags;
+}
+
+static void
+gb_source_change_monitor_ensure_bounds (GbSourceChangeMonitor *monitor)
+{
+  GbSourceChangeMonitorPrivate *priv;
+  GtkTextIter begin;
+  GtkTextIter end;
+  guint line;
+
+  g_return_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor));
+
+  priv = monitor->priv;
+
+  gtk_text_buffer_get_bounds (priv->buffer, &begin, &end);
+
+  line = gtk_text_iter_get_line (&end);
+
+  if (line + 1 > priv->state->len)
+    g_array_set_size (priv->state, line + 1);
+}
+
+static void
+on_delete_range_before_cb (GbSourceChangeMonitor *monitor,
+                           GtkTextIter           *begin,
+                           GtkTextIter           *end,
+                           GtkTextBuffer         *buffer)
+{
+  g_return_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor));
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+}
+
+static void
+on_delete_range_after_cb (GbSourceChangeMonitor *monitor,
+                          GtkTextIter           *begin,
+                          GtkTextIter           *end,
+                          GtkTextBuffer         *buffer)
+{
+  g_return_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor));
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+}
+
+static void
+on_insert_text_before_cb (GbSourceChangeMonitor *monitor,
+                          GtkTextIter           *location,
+                          gchar                 *text,
+                          gint                   len,
+                          GtkTextBuffer         *buffer)
+{
+  g_return_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor));
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+}
+
+static void
+on_insert_text_after_cb (GbSourceChangeMonitor *monitor,
+                         GtkTextIter           *location,
+                         gchar                 *text,
+                         gint                   len,
+                         GtkTextBuffer         *buffer)
+{
+  GbSourceChangeFlags flags = 0;
+  guint line;
+
+  g_return_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor));
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+
+  line = gtk_text_iter_get_line (location);
+
+  if (g_strcmp0 (text, "\n") == 0)
+    {
+      flags = (GB_SOURCE_CHANGE_ADDED | GB_SOURCE_CHANGE_DIRTY);
+      gb_source_change_monitor_insert (monitor, line, flags);
+    }
+  else if (strchr (text, '\n') == NULL)
+    {
+      flags = (GB_SOURCE_CHANGE_CHANGED | GB_SOURCE_CHANGE_DIRTY);
+      gb_source_change_monitor_set_line (monitor, line, flags);
+    }
+  else
+    {
+      GtkTextIter end;
+      GtkTextIter iter;
+      guint last_line = line;
+
+      len = g_utf8_strlen (text, len);
+
+      gtk_text_iter_assign (&iter, location);
+      gtk_text_iter_assign (&end, location);
+      gtk_text_iter_backward_chars (&iter, len);
+
+      while (gtk_text_iter_compare (&iter, &end) <= 0)
+        {
+          line = gtk_text_iter_get_line (&iter);
+
+          if (line != last_line)
+            {
+              flags = (GB_SOURCE_CHANGE_ADDED | GB_SOURCE_CHANGE_DIRTY);
+              gb_source_change_monitor_insert (monitor, line, flags);
+              last_line = line;
+            }
+
+          if (!gtk_text_iter_forward_char (&iter))
+            break;
+        }
+    }
+
+  gb_source_change_monitor_ensure_bounds (monitor);
+}
+
+GbSourceChangeFlags
+gb_source_change_monitor_get_line (GbSourceChangeMonitor *monitor,
+                                   guint                  lineno)
+{
+  g_return_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor));
+
+  if (lineno < monitor->priv->state->len)
+    return monitor->priv->state->data [lineno];
+
+  g_warning ("No such line: %u", lineno);
+
+  return 0;
+}
+
+void
+gb_source_change_monitor_reset (GbSourceChangeMonitor *monitor)
+{
+  GbSourceChangeMonitorPrivate *priv;
+  GtkTextIter begin;
+  GtkTextIter end;
+  guint line;
+
+  g_return_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor));
+
+  priv = monitor->priv;
+
+  if (priv->buffer)
+    {
+      gtk_text_buffer_get_bounds (priv->buffer, &begin, &end);
+      line = gtk_text_iter_get_line (&end);
+      g_array_set_size (priv->state, line + 1);
+      memset (priv->state->data, 0, priv->state->len);
+    }
+}
+
+GtkTextBuffer *
+gb_source_change_monitor_get_buffer (GbSourceChangeMonitor *monitor)
+{
+  g_return_val_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor), NULL);
+
+  return monitor->priv->buffer;
+}
+
+static void
+gb_source_change_monitor_set_buffer (GbSourceChangeMonitor *monitor,
+                                     GtkTextBuffer         *buffer,
+                                     gboolean               notify)
+{
+  GbSourceChangeMonitorPrivate *priv;
+
+  ENTRY;
+
+  g_return_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor));
+  g_return_if_fail (!buffer || GB_IS_SOURCE_CHANGE_MONITOR (monitor));
+
+  priv = monitor->priv;
+
+  if (priv->buffer)
+    {
+      g_signal_handler_disconnect (priv->buffer,
+                                   priv->delete_range_before_handler);
+      g_signal_handler_disconnect (priv->buffer,
+                                   priv->delete_range_after_handler);
+      g_signal_handler_disconnect (priv->buffer,
+                                   priv->insert_text_before_handler);
+      g_signal_handler_disconnect (priv->buffer,
+                                   priv->insert_text_after_handler);
+      priv->delete_range_before_handler = 0;
+      priv->delete_range_after_handler = 0;
+      priv->insert_text_before_handler = 0;
+      priv->insert_text_after_handler = 0;
+      g_clear_object (&priv->buffer);
+    }
+
+  if (buffer)
+    {
+      priv->buffer = g_object_ref (buffer);
+      priv->delete_range_before_handler =
+        g_signal_connect_object (priv->buffer,
+                                 "delete-range",
+                                 G_CALLBACK (on_delete_range_before_cb),
+                                 monitor,
+                                 G_CONNECT_SWAPPED);
+      priv->delete_range_after_handler =
+        g_signal_connect_object (priv->buffer,
+                                 "delete-range",
+                                 G_CALLBACK (on_delete_range_after_cb),
+                                 monitor,
+                                 (G_CONNECT_SWAPPED | G_CONNECT_AFTER));
+      priv->insert_text_before_handler =
+        g_signal_connect_object (priv->buffer,
+                                 "insert-text",
+                                 G_CALLBACK (on_insert_text_before_cb),
+                                 monitor,
+                                 G_CONNECT_SWAPPED);
+      priv->insert_text_after_handler =
+        g_signal_connect_object (priv->buffer,
+                                 "insert-text",
+                                 G_CALLBACK (on_insert_text_after_cb),
+                                 monitor,
+                                 (G_CONNECT_SWAPPED | G_CONNECT_AFTER));
+
+      gb_source_change_monitor_ensure_bounds (monitor);
+    }
+
+  if (notify)
+    g_object_notify_by_pspec (G_OBJECT (monitor), gParamSpecs [PROP_BUFFER]);
+
+  EXIT;
+}
+
+static void
+gb_source_change_monitor_dispose (GObject *object)
+{
+  ENTRY;
+
+  gb_source_change_monitor_set_buffer (GB_SOURCE_CHANGE_MONITOR (object),
+                                       NULL, FALSE);
+
+  G_OBJECT_CLASS (gb_source_change_monitor_parent_class)->dispose (object);
+
+  EXIT;
+}
+
+static void
+gb_source_change_monitor_finalize (GObject *object)
+{
+  GbSourceChangeMonitorPrivate *priv;
+
+  ENTRY;
+
+  priv = GB_SOURCE_CHANGE_MONITOR (object)->priv;
+
+  g_clear_pointer (&priv->state, g_array_unref);
+
+  G_OBJECT_CLASS (gb_source_change_monitor_parent_class)->finalize (object);
+
+  EXIT;
+}
+
+static void
+gb_source_change_monitor_get_property (GObject    *object,
+                                       guint       prop_id,
+                                       GValue     *value,
+                                       GParamSpec *pspec)
+{
+  GbSourceChangeMonitor *monitor = GB_SOURCE_CHANGE_MONITOR (object);
+
+  switch (prop_id) {
+  case PROP_BUFFER:
+    g_value_set_object (value,
+                        gb_source_change_monitor_get_buffer (monitor));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+gb_source_change_monitor_set_property (GObject      *object,
+                                       guint         prop_id,
+                                       const GValue *value,
+                                       GParamSpec   *pspec)
+{
+  GbSourceChangeMonitor *monitor = GB_SOURCE_CHANGE_MONITOR (object);
+
+  switch (prop_id) {
+  case PROP_BUFFER:
+    gb_source_change_monitor_set_buffer (monitor,
+                                         g_value_get_object (value),
+                                         TRUE);
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+gb_source_change_monitor_class_init (GbSourceChangeMonitorClass *klass)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (klass);
+  object_class->dispose = gb_source_change_monitor_dispose;
+  object_class->finalize = gb_source_change_monitor_finalize;
+  object_class->get_property = gb_source_change_monitor_get_property;
+  object_class->set_property = gb_source_change_monitor_set_property;
+
+  gParamSpecs [PROP_BUFFER] =
+    g_param_spec_object ("buffer",
+                         _("Buffer"),
+                         _("The text buffer to monitor."),
+                         GTK_TYPE_TEXT_BUFFER,
+                         (G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT_ONLY |
+                          G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_BUFFER,
+                                   gParamSpecs [PROP_BUFFER]);
+}
+
+static void
+gb_source_change_monitor_init (GbSourceChangeMonitor *monitor)
+{
+  ENTRY;
+
+  monitor->priv = gb_source_change_monitor_get_instance_private (monitor);
+
+  monitor->priv->state = g_array_new (FALSE, TRUE, sizeof (guint8));
+  g_array_set_size (monitor->priv->state, 1);
+
+  EXIT;
+}
diff --git a/src/editor/gb-source-change-monitor.h b/src/editor/gb-source-change-monitor.h
new file mode 100644
index 0000000..91d310a
--- /dev/null
+++ b/src/editor/gb-source-change-monitor.h
@@ -0,0 +1,66 @@
+/* gb-source-change-monitor.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GB_SOURCE_CHANGE_MONITOR_H
+#define GB_SOURCE_CHANGE_MONITOR_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_SOURCE_CHANGE_MONITOR            (gb_source_change_monitor_get_type())
+#define GB_SOURCE_CHANGE_MONITOR(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GB_TYPE_SOURCE_CHANGE_MONITOR, GbSourceChangeMonitor))
+#define GB_SOURCE_CHANGE_MONITOR_CONST(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GB_TYPE_SOURCE_CHANGE_MONITOR, GbSourceChangeMonitor const))
+#define GB_SOURCE_CHANGE_MONITOR_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  
GB_TYPE_SOURCE_CHANGE_MONITOR, GbSourceChangeMonitorClass))
+#define GB_IS_SOURCE_CHANGE_MONITOR(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GB_TYPE_SOURCE_CHANGE_MONITOR))
+#define GB_IS_SOURCE_CHANGE_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  
GB_TYPE_SOURCE_CHANGE_MONITOR))
+#define GB_SOURCE_CHANGE_MONITOR_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  
GB_TYPE_SOURCE_CHANGE_MONITOR, GbSourceChangeMonitorClass))
+
+typedef struct _GbSourceChangeMonitor        GbSourceChangeMonitor;
+typedef struct _GbSourceChangeMonitorClass   GbSourceChangeMonitorClass;
+typedef struct _GbSourceChangeMonitorPrivate GbSourceChangeMonitorPrivate;
+
+typedef enum
+{
+  GB_SOURCE_CHANGE_DIRTY   = 1 << 0,
+  GB_SOURCE_CHANGE_ADDED   = 1 << 1,
+  GB_SOURCE_CHANGE_CHANGED = 1 << 2,
+} GbSourceChangeFlags;
+
+struct _GbSourceChangeMonitor
+{
+  GObject parent;
+
+  /*< private >*/
+  GbSourceChangeMonitorPrivate *priv;
+};
+
+struct _GbSourceChangeMonitorClass
+{
+  GObjectClass parent_class;
+};
+
+GType                  gb_source_change_monitor_get_type (void) G_GNUC_CONST;
+GbSourceChangeMonitor *gb_source_change_monitor_new      (GtkTextBuffer         *buffer);
+GbSourceChangeFlags    gb_source_change_monitor_get_line (GbSourceChangeMonitor *monitor,
+                                                          guint                  lineno);
+void                   gb_source_change_monitor_reset    (GbSourceChangeMonitor *monitor);
+
+G_END_DECLS
+
+#endif /* GB_SOURCE_CHANGE_MONITOR_H */
diff --git a/src/gnome-builder.mk b/src/gnome-builder.mk
index a6d9bd0..332d6b7 100644
--- a/src/gnome-builder.mk
+++ b/src/gnome-builder.mk
@@ -28,8 +28,12 @@ gnome_builder_SOURCES = \
        src/editor/gb-editor-workspace.c \
        src/editor/gb-editor-workspace.h \
        src/editor/gb-editor-workspace-private.h \
+       src/editor/gb-source-change-monitor.c \
+       src/editor/gb-source-change-monitor.h \
        src/editor/gb-source-formatter.c \
        src/editor/gb-source-formatter.h \
+       src/editor/gb-source-change-gutter-renderer.c \
+       src/editor/gb-source-change-gutter-renderer.h \
        src/editor/gb-source-search-highlighter.h \
        src/editor/gb-source-search-highlighter.c \
        src/markdown/gs-markdown.c \
diff --git a/src/resources/ui/gb-editor-tab.ui b/src/resources/ui/gb-editor-tab.ui
index aaa3ca3..3ea644e 100644
--- a/src/resources/ui/gb-editor-tab.ui
+++ b/src/resources/ui/gb-editor-tab.ui
@@ -124,6 +124,9 @@
   </object>
   <object class="GbEditorDocument" id="document">
   </object>
+  <object class="GbSourceChangeMonitor" id="change_monitor">
+    <property name="buffer">document</property>
+  </object>
   <object class="GbSourceSnippetCompletionProvider" id="snippets_provider">
     <property name="source-view">source_view</property>
   </object>


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