[gnome-builder] buffer-manager: add API to apply a series of IdeProjectEdits



commit 7d6038470757c947889d55bcaf7f1042e8deebd0
Author: Christian Hergert <chergert redhat com>
Date:   Tue Oct 25 23:02:17 2016 -0700

    buffer-manager: add API to apply a series of IdeProjectEdits
    
    This simplifies the process of performing a group of edits for a refactor
    such as renaming a symbol across a project.

 libide/buffers/ide-buffer-manager.c |  312 ++++++++++++++++++++++++++++++++++-
 libide/buffers/ide-buffer-manager.h |    8 +
 2 files changed, 312 insertions(+), 8 deletions(-)
---
diff --git a/libide/buffers/ide-buffer-manager.c b/libide/buffers/ide-buffer-manager.c
index 5ff1b5b..fb98ce1 100644
--- a/libide/buffers/ide-buffer-manager.c
+++ b/libide/buffers/ide-buffer-manager.c
@@ -31,11 +31,14 @@
 #include "buffers/ide-buffer.h"
 #include "buffers/ide-unsaved-files.h"
 #include "diagnostics/ide-source-location.h"
+#include "diagnostics/ide-source-range.h"
 #include "files/ide-file-settings.h"
 #include "files/ide-file.h"
 #include "history/ide-back-forward-item.h"
 #include "history/ide-back-forward-list-private.h"
 #include "history/ide-back-forward-list.h"
+#include "projects/ide-project-edit.h"
+#include "projects/ide-project-edit-private.h"
 #include "util/ide-doc-seq.h"
 #include "util/ide-progress.h"
 #include "vcs/ide-vcs.h"
@@ -83,6 +86,14 @@ typedef struct
   IdeProgress *progress;
 } SaveState;
 
+typedef struct
+{
+  GPtrArray  *edits;
+  GHashTable *buffers;
+  guint       count;
+  guint       failed : 1;
+} EditState;
+
 static void list_model_iface_init (GListModelInterface *iface);
 
 G_DEFINE_TYPE_EXTENDED (IdeBufferManager, ide_buffer_manager, IDE_TYPE_OBJECT, 0,
@@ -124,6 +135,19 @@ static GParamSpec *properties [LAST_PROP];
 static guint signals [LAST_SIGNAL];
 
 static void
+edit_state_free (gpointer data)
+{
+  EditState *state = data;
+
+  if (state != NULL)
+    {
+      g_clear_pointer (&state->edits, g_ptr_array_unref);
+      g_clear_pointer (&state->buffers, g_hash_table_unref);
+      g_slice_free (EditState, state);
+    }
+}
+
+static void
 save_state_free (gpointer data)
 {
   SaveState *state = data;
@@ -152,6 +176,13 @@ load_state_free (gpointer data)
     }
 }
 
+static void
+unref_if_non_null (gpointer data)
+{
+  if (data != NULL)
+    g_object_unref (data);
+}
+
 /**
  * ide_buffer_manager_get_auto_save_timeout:
  *
@@ -732,14 +763,14 @@ ide_buffer_manager__load_file_read_cb (GObject      *object,
  * See ide_buffer_manager_load_file_finish() for how to complete this asynchronous request.
  */
 void
-ide_buffer_manager_load_file_async (IdeBufferManager     *self,
-                                    IdeFile              *file,
-                                    gboolean              force_reload,
-                                    IdeWorkbenchOpenFlags flags,
-                                    IdeProgress         **progress,
-                                    GCancellable         *cancellable,
-                                    GAsyncReadyCallback   callback,
-                                    gpointer              user_data)
+ide_buffer_manager_load_file_async (IdeBufferManager       *self,
+                                    IdeFile                *file,
+                                    gboolean                force_reload,
+                                    IdeWorkbenchOpenFlags   flags,
+                                    IdeProgress           **progress,
+                                    GCancellable           *cancellable,
+                                    GAsyncReadyCallback     callback,
+                                    gpointer                user_data)
 {
   g_autoptr(GTask) task = NULL;
   IdeContext *context;
@@ -1869,3 +1900,268 @@ ide_buffer_manager_save_all_finish (IdeBufferManager  *self,
 
   return g_task_propagate_boolean (G_TASK (result), error);
 }
+
+static void
+ide_buffer_manager_do_apply_edits (IdeBufferManager *self,
+                                   GHashTable       *buffers,
+                                   GPtrArray        *edits)
+{
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_BUFFER_MANAGER (self));
+  g_assert (buffers != NULL);
+  g_assert (edits != NULL);
+
+  /* Allow each project edit to stage its GtkTextMarks */
+  for (guint i = 0; i < edits->len; i++)
+    {
+      IdeProjectEdit *edit = g_ptr_array_index (edits, i);
+      IdeSourceLocation *location;
+      IdeSourceRange *range;
+      IdeBuffer *buffer;
+      IdeFile *file;
+
+      if (NULL == (range = ide_project_edit_get_range (edit)) ||
+          NULL == (location = ide_source_range_get_begin (range)) ||
+          NULL == (file = ide_source_location_get_file (location)) ||
+          NULL == (buffer = g_hash_table_lookup (buffers, file)))
+        {
+          g_warning ("Impluasible failure to access buffer");
+          continue;
+        }
+
+      gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));
+
+      _ide_project_edit_prepare (edit, buffer);
+    }
+
+  /* Now actually perform the replacement between the text marks */
+  for (guint i = 0; i < edits->len; i++)
+    {
+      IdeProjectEdit *edit = g_ptr_array_index (edits, i);
+      IdeSourceLocation *location;
+      IdeSourceRange *range;
+      IdeBuffer *buffer;
+      IdeFile *file;
+
+      if (NULL == (range = ide_project_edit_get_range (edit)) ||
+          NULL == (location = ide_source_range_get_begin (range)) ||
+          NULL == (file = ide_source_location_get_file (location)) ||
+          NULL == (buffer = g_hash_table_lookup (buffers, file)))
+        {
+          g_warning ("Impluasible failure to access buffer");
+          continue;
+        }
+
+      _ide_project_edit_apply (edit, buffer);
+    }
+
+  /* Complete all of our undo groups */
+  for (guint i = 0; i < edits->len; i++)
+    {
+      IdeProjectEdit *edit = g_ptr_array_index (edits, i);
+      IdeSourceLocation *location;
+      IdeSourceRange *range;
+      IdeBuffer *buffer;
+      IdeFile *file;
+
+      if (NULL == (range = ide_project_edit_get_range (edit)) ||
+          NULL == (location = ide_source_range_get_begin (range)) ||
+          NULL == (file = ide_source_location_get_file (location)) ||
+          NULL == (buffer = g_hash_table_lookup (buffers, file)))
+        {
+          g_warning ("Impluasible failure to access buffer");
+          continue;
+        }
+
+      gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
+    }
+
+  IDE_EXIT;
+}
+
+static void
+ide_buffer_manager_apply_edits_save_cb (GObject      *object,
+                                        GAsyncResult *result,
+                                        gpointer      user_data)
+{
+  IdeBufferManager *self = (IdeBufferManager *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_BUFFER_MANAGER (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  if (!ide_buffer_manager_save_all_finish (self, result, &error))
+    g_task_return_error (task, g_steal_pointer (&error));
+  else
+    g_task_return_boolean (task, TRUE);
+
+  IDE_EXIT;
+}
+
+static void
+ide_buffer_manager_apply_edits_buffer_loaded (GObject      *object,
+                                              GAsyncResult *result,
+                                              gpointer      user_data)
+{
+  IdeBufferManager *self = (IdeBufferManager *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(IdeBuffer) buffer = NULL;
+  EditState *state;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_BUFFER_MANAGER (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  state = g_task_get_task_data (task);
+  state->count--;
+
+  /* Get our buffer, if we failed, we won't proceed with edits */
+  if (NULL == (buffer = ide_buffer_manager_load_file_finish (self, result, &error)))
+    {
+      if (state->failed == FALSE)
+        {
+          state->failed = TRUE;
+          g_task_return_error (task, g_steal_pointer (&error));
+          return;
+        }
+    }
+
+  /* Nothing to do if we already failed */
+  if (state->failed)
+    IDE_EXIT;
+
+  /* If this is the last buffer to load, then we can go apply the edits. */
+  if (state->count == 0)
+    {
+      GCancellable *cancellable = g_task_get_cancellable (task);
+
+      ide_buffer_manager_do_apply_edits (self, state->buffers, state->edits);
+
+      ide_buffer_manager_save_all_async (self,
+                                         cancellable,
+                                         ide_buffer_manager_apply_edits_save_cb,
+                                         g_steal_pointer (&task));
+    }
+
+  IDE_EXIT;
+}
+
+/**
+ * ide_buffer_manager_apply_edits_async:
+ * @self: An #IdeBufferManager
+ * @edits: (transfer container) (element-type Ide.ProjectEdit): An #GPtrArray of #IdeProjectEdit
+ * @cancellable: (allow-none): A #GCancellable or %NULL
+ * @callback: the callback to complete the request
+ * @user_data: user data for @callback
+ *
+ * Asynchronously requests that all of @edits are applied to the buffers
+ * in the project. If the buffer has not been loaded for a particular edit,
+ * it will be loaded.
+ */
+void
+ide_buffer_manager_apply_edits_async (IdeBufferManager    *self,
+                                      GPtrArray           *edits,
+                                      GCancellable        *cancellable,
+                                      GAsyncReadyCallback  callback,
+                                      gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  EditState *state;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_BUFFER_MANAGER (self));
+  g_return_if_fail (edits != NULL);
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_buffer_manager_apply_edits_async);
+
+  state = g_slice_new0 (EditState);
+  state->buffers = g_hash_table_new_full ((GHashFunc)ide_file_hash,
+                                          (GEqualFunc)ide_file_equal,
+                                          g_object_unref,
+                                          unref_if_non_null);
+  state->edits = g_steal_pointer (&edits);
+  state->count = 1;
+
+  g_task_set_task_data (task, state, edit_state_free);
+
+  for (guint i = 0; i < state->edits->len; i++)
+    {
+      IdeProjectEdit *edit = g_ptr_array_index (state->edits, i);
+      IdeSourceLocation *location;
+      IdeSourceRange *range;
+      IdeBuffer *buffer;
+      IdeFile *file;
+
+      if (NULL == (range = ide_project_edit_get_range (edit)) ||
+          NULL == (location = ide_source_range_get_begin (range)) ||
+          NULL == (file = ide_source_location_get_file (location)))
+        continue;
+
+      if (g_hash_table_contains (state->buffers, file))
+        continue;
+
+      buffer = ide_buffer_manager_find_buffer (self, ide_file_get_file (file));
+
+      if (buffer != NULL)
+        {
+          g_hash_table_insert (state->buffers, g_object_ref (file), g_object_ref (buffer));
+          continue;
+        }
+
+      g_hash_table_insert (state->buffers, g_object_ref (file), NULL);
+
+      state->count++;
+
+      ide_buffer_manager_load_file_async (self,
+                                          file,
+                                          FALSE,
+                                          IDE_WORKBENCH_OPEN_FLAGS_BACKGROUND,
+                                          NULL,
+                                          cancellable,
+                                          ide_buffer_manager_apply_edits_buffer_loaded,
+                                          g_object_ref (task));
+    }
+
+  state->count--;
+
+  IDE_TRACE_MSG ("Waiting for %d buffers to load", state->count);
+
+  if (state->count == 0)
+    {
+      ide_buffer_manager_do_apply_edits (self, state->buffers, state->edits);
+      ide_buffer_manager_save_all_async (self,
+                                         cancellable,
+                                         ide_buffer_manager_apply_edits_save_cb,
+                                         g_steal_pointer (&task));
+    }
+
+  IDE_EXIT;
+}
+
+gboolean
+ide_buffer_manager_apply_edits_finish (IdeBufferManager  *self,
+                                       GAsyncResult      *result,
+                                       GError           **error)
+{
+  gboolean ret;
+
+  IDE_ENTRY;
+
+  g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  ret = g_task_propagate_boolean (G_TASK (result), error);
+
+  IDE_RETURN (ret);
+}
diff --git a/libide/buffers/ide-buffer-manager.h b/libide/buffers/ide-buffer-manager.h
index cc257b7..2ab9d6d 100644
--- a/libide/buffers/ide-buffer-manager.h
+++ b/libide/buffers/ide-buffer-manager.h
@@ -76,6 +76,14 @@ IdeBuffer                *ide_buffer_manager_find_buffer         (IdeBufferManag
 gsize                     ide_buffer_manager_get_max_file_size   (IdeBufferManager     *self);
 void                      ide_buffer_manager_set_max_file_size   (IdeBufferManager     *self,
                                                                   gsize                 max_file_size);
+void                      ide_buffer_manager_apply_edits_async   (IdeBufferManager     *self,
+                                                                  GPtrArray            *edits,
+                                                                  GCancellable         *cancellable,
+                                                                  GAsyncReadyCallback   callback,
+                                                                  gpointer              user_data);
+gboolean                  ide_buffer_manager_apply_edits_finish  (IdeBufferManager     *self,
+                                                                  GAsyncResult         *result,
+                                                                  GError              **error);
 
 G_END_DECLS
 


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