[gnome-builder/wip/gtk4-port] libide/code: add IdeBuffer commit hooks



commit 6945b627b14ccaf047b5699b9d47ff91cebd170d
Author: Christian Hergert <chergert redhat com>
Date:   Mon Apr 11 15:31:00 2022 -0700

    libide/code: add IdeBuffer commit hooks
    
    This is intended to help us manage operations with text "commit" operations
    where the underlying buffer is changed. It doesn't give much information,
    but then again, it's intended to be rather fast and correct in the sense
    of requiring consumers to not do further modifications from their
    function callbacks.
    
    This can be used by spellcheck to update the region b+tree in concert with
    the text buffer.

 src/libide/code/ide-buffer.c | 149 ++++++++++++++++++++++++++++++++++++++++++-
 src/libide/code/ide-buffer.h |  16 +++++
 2 files changed, 162 insertions(+), 3 deletions(-)
---
diff --git a/src/libide/code/ide-buffer.c b/src/libide/code/ide-buffer.c
index efceb67a7..6f5d51bef 100644
--- a/src/libide/code/ide-buffer.c
+++ b/src/libide/code/ide-buffer.c
@@ -95,6 +95,9 @@ struct _IdeBuffer
   int                     hold;
   guint                   release_in_idle;
 
+  GArray                 *commit_funcs;
+  guint                   next_commit_handler;
+
   /* Bit-fields */
   IdeBufferState          state : 3;
   guint                   can_restore_cursor : 1;
@@ -105,6 +108,17 @@ struct _IdeBuffer
   guint                   highlight_diagnostics : 1;
 };
 
+typedef struct
+{
+  IdeBufferCommitFunc before_insert_text;
+  IdeBufferCommitFunc after_insert_text;
+  IdeBufferCommitFunc before_delete_range;
+  IdeBufferCommitFunc after_delete_range;
+  gpointer user_data;
+  GDestroyNotify user_data_destroy;
+  guint handler_id;
+} CommitHooks;
+
 typedef struct
 {
   IdeNotification *notif;
@@ -272,6 +286,15 @@ lookup_symbol_data_free (LookUpSymbolData *data)
   g_slice_free (LookUpSymbolData, data);
 }
 
+static void
+clear_commit_func (gpointer data)
+{
+  CommitHooks *hooks = data;
+
+  if (hooks->user_data_destroy)
+    hooks->user_data_destroy (hooks->user_data);
+}
+
 IdeBuffer *
 _ide_buffer_new (IdeBufferManager *buffer_manager,
                  GFile            *file,
@@ -505,6 +528,7 @@ ide_buffer_dispose (GObject *object)
   g_clear_pointer (&self->content, g_bytes_unref);
   g_clear_object (&self->diagnostics);
   ide_clear_and_destroy_object (&self->file_settings);
+  g_clear_pointer (&self->commit_funcs, g_array_unref);
 
   G_OBJECT_CLASS (ide_buffer_parent_class)->dispose (object);
 }
@@ -1030,13 +1054,16 @@ ide_buffer_class_init (IdeBufferClass *klass)
 static void
 ide_buffer_init (IdeBuffer *self)
 {
+  g_assert (IDE_IS_MAIN_THREAD ());
+
   self->in_flight_symbol_at_location_pos = -1;
   self->source_file = gtk_source_file_new ();
   self->can_restore_cursor = TRUE;
   self->highlight_diagnostics = TRUE;
   self->enable_addins = TRUE;
 
-  g_assert (IDE_IS_MAIN_THREAD ());
+  self->commit_funcs = g_array_new (FALSE, FALSE, sizeof (CommitHooks));
+  g_array_set_clear_func (self->commit_funcs, clear_commit_func);
 
   g_signal_connect (self,
                     "notify::language",
@@ -1991,10 +2018,14 @@ ide_buffer_delete_range (GtkTextBuffer *buffer,
                          GtkTextIter   *begin,
                          GtkTextIter   *end)
 {
+  IdeBuffer *self = (IdeBuffer *)buffer;
+  guint position;
+  guint length;
+
   IDE_ENTRY;
 
   g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (IDE_IS_BUFFER (buffer));
+  g_assert (IDE_IS_BUFFER (self));
   g_assert (begin != NULL);
   g_assert (end != NULL);
 
@@ -2014,8 +2045,27 @@ ide_buffer_delete_range (GtkTextBuffer *buffer,
   }
 #endif
 
+  position = gtk_text_iter_get_offset (begin);
+  length = gtk_text_iter_get_offset (end) - position;
+
+  for (guint i = 0; i < self->commit_funcs->len; i++)
+    {
+      const CommitHooks *hooks = &g_array_index (self->commit_funcs, CommitHooks, i);
+
+      if (hooks->before_delete_range != NULL)
+        hooks->before_delete_range (self, position, length, hooks->user_data);
+    }
+
   GTK_TEXT_BUFFER_CLASS (ide_buffer_parent_class)->delete_range (buffer, begin, end);
 
+  for (guint i = 0; i < self->commit_funcs->len; i++)
+    {
+      const CommitHooks *hooks = &g_array_index (self->commit_funcs, CommitHooks, i);
+
+      if (hooks->after_delete_range != NULL)
+        hooks->after_delete_range (self, position, length, hooks->user_data);
+    }
+
   IDE_EXIT;
 }
 
@@ -2025,12 +2075,15 @@ ide_buffer_insert_text (GtkTextBuffer *buffer,
                         const gchar   *text,
                         gint           len)
 {
+  IdeBuffer *self = (IdeBuffer *)buffer;
   gboolean recheck_language = FALSE;
+  guint position;
+  guint length;
 
   IDE_ENTRY;
 
   g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (IDE_IS_BUFFER (buffer));
+  g_assert (IDE_IS_BUFFER (self));
   g_assert (location != NULL);
   g_assert (text != NULL);
 
@@ -2044,8 +2097,27 @@ ide_buffer_insert_text (GtkTextBuffer *buffer,
       ((text [0] == '\n') || ((len > 1) && (strchr (text, '\n') != NULL))))
     recheck_language = TRUE;
 
+  position = gtk_text_iter_get_offset (location);
+  length = g_utf8_strlen (text, len);
+
+  for (guint i = 0; i < self->commit_funcs->len; i++)
+    {
+      const CommitHooks *hooks = &g_array_index (self->commit_funcs, CommitHooks, i);
+
+      if (hooks->before_insert_text != NULL)
+        hooks->before_insert_text (self, position, length, hooks->user_data);
+    }
+
   GTK_TEXT_BUFFER_CLASS (ide_buffer_parent_class)->insert_text (buffer, location, text, len);
 
+  for (guint i = 0; i < self->commit_funcs->len; i++)
+    {
+      const CommitHooks *hooks = &g_array_index (self->commit_funcs, CommitHooks, i);
+
+      if (hooks->after_insert_text != NULL)
+        hooks->after_insert_text (self, position, length, hooks->user_data);
+    }
+
   if G_UNLIKELY (recheck_language)
     ide_buffer_guess_language (IDE_BUFFER (buffer));
 
@@ -4141,3 +4213,74 @@ _ide_buffer_is_file (IdeBuffer *self,
   return g_file_equal (nolink_file, ide_buffer_get_file (self)) ||
          g_file_equal (nolink_file, self->readlink_file);
 }
+
+/**
+ * ide_buffer_add_commit_funcs:
+ * @self: a #IdeBuffer
+ * @before_insert_text: (nullable) (scope async): function for before inserting text
+ * @after_insert_text: (nullable) (scope async): function for after inserting text
+ * @before_delete_range: (nullable) (scope async): function for before deleting a range
+ * @after_delete_range: (nullable) (scope async): function for after deleting a range
+ * @user_data: closure data
+ * @user_data_destroy: destroy notify for @user_data
+ *
+ * Adds function callbacks to handle important changes to text
+ * internally within the GtkTextBuffer. You can use these instead
+ * of signals like #GtkTextBuffer::insert-text or
+ * #GtkTextBuffer::delete-range when you want to be sure you're
+ * getting unprocessed changes right before they are commited to
+ * underlying GTK data structures.
+ *
+ * However, this has the requirement that you do not change this
+ * content in any way, only access the information that these events
+ * occurred.
+ *
+ * Returns: a handler-id which can be used with
+ *   ide_buffer_remove_commit_funcs().
+ */
+guint
+ide_buffer_add_commit_funcs (IdeBuffer           *self,
+                             IdeBufferCommitFunc  before_insert_text,
+                             IdeBufferCommitFunc  after_insert_text,
+                             IdeBufferCommitFunc  before_delete_range,
+                             IdeBufferCommitFunc  after_delete_range,
+                             gpointer             user_data,
+                             GDestroyNotify       user_data_destroy)
+{
+  CommitHooks hooks;
+
+  g_return_val_if_fail (IDE_IS_BUFFER (self), 0);
+
+  hooks.before_insert_text = before_insert_text;
+  hooks.after_insert_text = after_insert_text;
+  hooks.after_delete_range = after_delete_range;
+  hooks.before_delete_range = before_delete_range;
+  hooks.user_data = user_data;
+  hooks.user_data_destroy = user_data_destroy;
+  hooks.handler_id = ++self->next_commit_handler;
+
+  g_array_append_val (self->commit_funcs, hooks);
+
+  return hooks.handler_id;
+}
+
+void
+ide_buffer_remove_commit_funcs (IdeBuffer *self,
+                                guint      commit_funcs_handler)
+{
+  g_return_if_fail (IDE_IS_BUFFER (self));
+  g_return_if_fail (commit_funcs_handler > 0);
+
+  for (guint i = 0; i < self->commit_funcs->len; i++)
+    {
+      const CommitHooks *hooks = &g_array_index (self->commit_funcs, CommitHooks, i);
+
+      if (hooks->handler_id == commit_funcs_handler)
+        {
+          g_array_remove_index (self->commit_funcs, i);
+          return;
+        }
+    }
+
+  g_warning ("Failed to locate commit handler %u", commit_funcs_handler);
+}
diff --git a/src/libide/code/ide-buffer.h b/src/libide/code/ide-buffer.h
index 566009d7a..0510f067a 100644
--- a/src/libide/code/ide-buffer.h
+++ b/src/libide/code/ide-buffer.h
@@ -48,6 +48,11 @@ typedef enum
   IDE_BUFFER_STATE_FAILED,
 } IdeBufferState;
 
+typedef void (*IdeBufferCommitFunc) (IdeBuffer *buffer,
+                                     guint      position,
+                                     guint      length,
+                                     gpointer   user_data);
+
 IDE_AVAILABLE_IN_ALL
 G_DECLARE_FINAL_TYPE (IdeBuffer, ide_buffer, IDE, BUFFER, GtkSourceBuffer)
 
@@ -182,5 +187,16 @@ void                    ide_buffer_set_highlight_diagnostics     (IdeBuffer
 IDE_AVAILABLE_IN_ALL
 void                    ide_buffer_set_style_scheme_name         (IdeBuffer               *self,
                                                                   const gchar             
*style_scheme_name);
+IDE_AVAILABLE_IN_ALL
+guint                   ide_buffer_add_commit_funcs              (IdeBuffer               *self,
+                                                                  IdeBufferCommitFunc      
before_insert_text,
+                                                                  IdeBufferCommitFunc      after_insert_text,
+                                                                  IdeBufferCommitFunc      
before_delete_range,
+                                                                  IdeBufferCommitFunc      
after_delete_range,
+                                                                  gpointer                 user_data,
+                                                                  GDestroyNotify           
user_data_destroy);
+IDE_AVAILABLE_IN_ALL
+void                    ide_buffer_remove_commit_funcs           (IdeBuffer               *self,
+                                                                  guint                    
commit_funcs_handler);
 
 G_END_DECLS


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