[gnome-builder/wip/libide] libide: be smarter about avoiding diff calculation



commit 90fd68793ea281582d1eaa7b1bcff91ac8df5632
Author: Christian Hergert <christian hergert me>
Date:   Thu Feb 26 17:44:37 2015 -0800

    libide: be smarter about avoiding diff calculation
    
    Try to get the things we know need to be calculated immediately served
    immediately. Try to catch the hard stuff (like manually changing a line
    back to its original contents) in a followup handler a second after the
    most recent edit.
    
    The longer we keep typing, the longer we try to delay the followup pass.
    This is because it is pretty likely the user will perform either a newline
    insertion or deletion (requiring an immediate diff) within the not too
    distant future.
    
    This significantly reduces the number of diffs calculated for me. Should
    result in some power savings.

 libide/git/ide-git-buffer-change-monitor.c |  173 +++++++++++++++++++++++++---
 1 files changed, 158 insertions(+), 15 deletions(-)
---
diff --git a/libide/git/ide-git-buffer-change-monitor.c b/libide/git/ide-git-buffer-change-monitor.c
index c2381b5..d2baee8 100644
--- a/libide/git/ide-git-buffer-change-monitor.c
+++ b/libide/git/ide-git-buffer-change-monitor.c
@@ -49,8 +49,11 @@ struct _IdeGitBufferChangeMonitor
 
   GgitBlob       *cached_blob;
 
+  guint           changed_timeout;
+
   guint           state_dirty : 1;
   guint           in_calculation : 1;
+  guint           delete_range_requires_recalculation : 1;
 };
 
 typedef struct
@@ -225,27 +228,15 @@ ide_git_buffer_change_monitor__calculate_cb (GObject      *object,
 }
 
 static void
-ide_git_buffer_change_monitor__buffer_changed_cb (IdeGitBufferChangeMonitor *self,
-                                                  IdeBuffer                 *buffer)
+ide_git_buffer_change_monitor_recalculate (IdeGitBufferChangeMonitor *self)
 {
   g_assert (IDE_IS_GIT_BUFFER_CHANGE_MONITOR (self));
-  g_assert (IDE_IS_BUFFER (buffer));
 
   self->state_dirty = TRUE;
 
-  /* We recalculate state upon completion of current request */
   if (self->in_calculation)
     return;
 
-  /*
-   * TODO:
-   *
-   * we shouldn't do this here:
-   *
-   * instead we should hook to insert/delete-range signals and determine if there was a multiline
-   * change. also, if the line has not changed, we need to recalculate. but we can avoid the
-   * check if the line has already changed and no \n were added/removed.
-   */
   ide_git_buffer_change_monitor_calculate_async (self,
                                                  NULL,
                                                  ide_git_buffer_change_monitor__calculate_cb,
@@ -253,6 +244,134 @@ ide_git_buffer_change_monitor__buffer_changed_cb (IdeGitBufferChangeMonitor *sel
 }
 
 static void
+ide_git_buffer_change_monitor__buffer_delete_range_after_cb (IdeGitBufferChangeMonitor *self,
+                                                             GtkTextIter               *begin,
+                                                             GtkTextIter               *end,
+                                                             IdeBuffer                 *buffer)
+{
+  g_assert (IDE_IS_GIT_BUFFER_CHANGE_MONITOR (self));
+  g_assert (begin);
+  g_assert (end);
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  if (self->delete_range_requires_recalculation)
+    {
+      self->delete_range_requires_recalculation = FALSE;
+      ide_git_buffer_change_monitor_recalculate (self);
+    }
+}
+
+static void
+ide_git_buffer_change_monitor__buffer_delete_range_cb (IdeGitBufferChangeMonitor *self,
+                                                       GtkTextIter               *begin,
+                                                       GtkTextIter               *end,
+                                                       IdeBuffer                 *buffer)
+{
+  IdeBufferLineChange change;
+
+  g_assert (IDE_IS_GIT_BUFFER_CHANGE_MONITOR (self));
+  g_assert (begin);
+  g_assert (end);
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  /*
+   * We need to recalculate the diff when text is deleted if:
+   *
+   * 1) The range includes a newline.
+   * 2) The current line change is set to NONE.
+   *
+   * Technically we need to do it on every change to be more correct, but that wastes a lot of
+   * power. So instead, we'll be a bit lazy about it here and pick up the other changes on a much
+   * more conservative timeout, generated by ide_git_buffer_change_monitor__buffer_changed_cb().
+   */
+
+  if (gtk_text_iter_get_line (begin) != gtk_text_iter_get_line (end))
+    goto recalculate;
+
+  change = ide_git_buffer_change_monitor_get_change (IDE_BUFFER_CHANGE_MONITOR (self), begin);
+  if (change == IDE_BUFFER_LINE_CHANGE_NONE)
+    goto recalculate;
+
+  return;
+
+recalculate:
+  /*
+   * We need to wait for the delete to occur, so mark it as necessary and let
+   * ide_git_buffer_change_monitor__buffer_delete_range_after_cb perform the operation.
+   */
+  self->delete_range_requires_recalculation = TRUE;
+}
+
+static void
+ide_git_buffer_change_monitor__buffer_insert_text_after_cb (IdeGitBufferChangeMonitor *self,
+                                                            GtkTextIter               *location,
+                                                            gchar                     *text,
+                                                            gint                       len,
+                                                            IdeBuffer                 *buffer)
+{
+  IdeBufferLineChange change;
+
+  g_assert (IDE_IS_GIT_BUFFER_CHANGE_MONITOR (self));
+  g_assert (location);
+  g_assert (text);
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  /*
+   * We need to recalculate the diff when text is inserted if:
+   *
+   * 1) A newline is included in the text.
+   * 2) The line currently has flags of NONE.
+   *
+   * Technically we need to do it on every change to be more correct, but that wastes a lot of
+   * power. So instead, we'll be a bit lazy about it here and pick up the other changes on a much
+   * more conservative timeout, generated by ide_git_buffer_change_monitor__buffer_changed_cb().
+   */
+
+  if (strchr (text, '\n') != NULL)
+    goto recalculate;
+
+  change = ide_git_buffer_change_monitor_get_change (IDE_BUFFER_CHANGE_MONITOR (self), location);
+  if (change == IDE_BUFFER_LINE_CHANGE_NONE)
+    goto recalculate;
+
+  return;
+
+recalculate:
+  ide_git_buffer_change_monitor_recalculate (self);
+}
+
+static gboolean
+ide_git_buffer_change_monitor__changed_timeout_cb (gpointer user_data)
+{
+  IdeGitBufferChangeMonitor *self = user_data;
+
+  ide_git_buffer_change_monitor_recalculate (self);
+  self->changed_timeout = 0;
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+ide_git_buffer_change_monitor__buffer_changed_after_cb (IdeGitBufferChangeMonitor *self,
+                                                        IdeBuffer                 *buffer)
+{
+  g_assert (IDE_IS_BUFFER_CHANGE_MONITOR (self));
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  self->state_dirty = TRUE;
+
+  if (self->in_calculation)
+    return;
+
+  if (self->changed_timeout)
+    g_source_remove (self->changed_timeout);
+
+  self->changed_timeout = g_timeout_add_seconds (1,
+                                                 ide_git_buffer_change_monitor__changed_timeout_cb,
+                                                 self);
+}
+
+static void
 ide_git_buffer_change_monitor_set_buffer (IdeBufferChangeMonitor *monitor,
                                           IdeBuffer              *buffer)
 {
@@ -265,10 +384,28 @@ ide_git_buffer_change_monitor_set_buffer (IdeBufferChangeMonitor *monitor,
   self->buffer = g_object_ref (buffer);
 
   g_signal_connect_object (self->buffer,
-                           "changed",
-                           G_CALLBACK (ide_git_buffer_change_monitor__buffer_changed_cb),
+                           "insert-text",
+                           G_CALLBACK (ide_git_buffer_change_monitor__buffer_insert_text_after_cb),
+                           self,
+                           G_CONNECT_SWAPPED | G_CONNECT_AFTER);
+
+  g_signal_connect_object (self->buffer,
+                           "delete-range",
+                           G_CALLBACK (ide_git_buffer_change_monitor__buffer_delete_range_cb),
                            self,
                            G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->buffer,
+                           "delete-range",
+                           G_CALLBACK (ide_git_buffer_change_monitor__buffer_delete_range_after_cb),
+                           self,
+                           G_CONNECT_SWAPPED | G_CONNECT_AFTER);
+
+  g_signal_connect_object (self->buffer,
+                           "changed",
+                           G_CALLBACK (ide_git_buffer_change_monitor__buffer_changed_after_cb),
+                           self,
+                           G_CONNECT_SWAPPED | G_CONNECT_AFTER);
 }
 
 static gint
@@ -484,6 +621,12 @@ ide_git_buffer_change_monitor_dispose (GObject *object)
 {
   IdeGitBufferChangeMonitor *self = (IdeGitBufferChangeMonitor *)object;
 
+  if (self->changed_timeout)
+    {
+      g_source_remove (self->changed_timeout);
+      self->changed_timeout = 0;
+    }
+
   g_clear_object (&self->cached_blob);
   g_clear_object (&self->buffer);
   g_clear_object (&self->repository);


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