[gnome-builder] libide-code: port IdeBuffer to GTK 4



commit 1f9c60a47a9398c46906753e8c5766f9bc830ddb
Author: Christian Hergert <chergert redhat com>
Date:   Mon Jul 11 16:17:32 2022 -0700

    libide-code: port IdeBuffer to GTK 4
    
     * Allow setting charset for a buffer to allow for alternative encoding
       when saving.
     * Allow setting newline type for buffer.
     * Introduce "commit funcs" to hook into buffer tracking components.
     * Update availability macros.
     * Cleanup buffer flags naming and remove unused features.

 src/libide/code/ide-buffer-addin.c          |  20 -
 src/libide/code/ide-buffer-addin.h          |  24 +-
 src/libide/code/ide-buffer-change-monitor.c |   4 -
 src/libide/code/ide-buffer-change-monitor.h |  12 +-
 src/libide/code/ide-buffer-manager.c        |  53 +--
 src/libide/code/ide-buffer-manager.h        |  56 ++-
 src/libide/code/ide-buffer.c                | 600 ++++++++++++++++------------
 src/libide/code/ide-buffer.h                | 122 +++---
 8 files changed, 470 insertions(+), 421 deletions(-)
---
diff --git a/src/libide/code/ide-buffer-addin.c b/src/libide/code/ide-buffer-addin.c
index d9b78b887..756f8db6f 100644
--- a/src/libide/code/ide-buffer-addin.c
+++ b/src/libide/code/ide-buffer-addin.c
@@ -43,8 +43,6 @@
  * may be used by views to show the misspelled words. This is preferrable
  * to adding a spellchecker in each view because it allows for multiple
  * views to share one spellcheker on the underlying buffer.
- *
- * Since: 3.32
  */
 
 G_DEFINE_INTERFACE (IdeBufferAddin, ide_buffer_addin, G_TYPE_OBJECT)
@@ -85,8 +83,6 @@ ide_buffer_addin_default_init (IdeBufferAddinInterface *iface)
  *
  * This calls the load virtual function of #IdeBufferAddin to request
  * that the addin load itself.
- *
- * Since: 3.32
  */
 void
 ide_buffer_addin_load (IdeBufferAddin *self,
@@ -110,8 +106,6 @@ ide_buffer_addin_load (IdeBufferAddin *self,
  *
  * The addin should cancel any in-flight operations and attempt to drop
  * references to the buffer or any other machinery as soon as possible.
- *
- * Since: 3.32
  */
 void
 ide_buffer_addin_unload (IdeBufferAddin *self,
@@ -135,8 +129,6 @@ ide_buffer_addin_unload (IdeBufferAddin *self,
  *
  * It is not guaranteed that this function will be called for addins that were
  * loaded after the buffer already loaded a file.
- *
- * Since: 3.32
  */
 void
 ide_buffer_addin_file_loaded (IdeBufferAddin *self,
@@ -160,8 +152,6 @@ ide_buffer_addin_file_loaded (IdeBufferAddin *self,
  *
  * This function gives a chance for plugins to modify the buffer right before
  * writing to disk.
- *
- * Since: 3.32
  */
 void
 ide_buffer_addin_save_file (IdeBufferAddin *self,
@@ -184,8 +174,6 @@ ide_buffer_addin_save_file (IdeBufferAddin *self,
  * @file: a #GFile
  *
  * This function is called for an addin after a file has been saved to disk.
- *
- * Since: 3.32
  */
 void
 ide_buffer_addin_file_saved (IdeBufferAddin *self,
@@ -209,8 +197,6 @@ ide_buffer_addin_file_saved (IdeBufferAddin *self,
  *
  * This vfunc is called when the source language in the buffer changes. This
  * will only be delivered to addins that support multiple languages.
- *
- * Since: 3.32
  */
 void
 ide_buffer_addin_language_set (IdeBufferAddin *self,
@@ -233,8 +219,6 @@ ide_buffer_addin_language_set (IdeBufferAddin *self,
  * changes provided by the user. It is a convenient way to know when you
  * should perform more background work without having to coalesce work
  * yourself.
- *
- * Since: 3.32
  */
 void
 ide_buffer_addin_change_settled (IdeBufferAddin *self,
@@ -254,8 +238,6 @@ ide_buffer_addin_change_settled (IdeBufferAddin *self,
  *
  * This function is called when the #GtkSourceStyleScheme of the #IdeBuffer
  * has changed.
- *
- * Since: 3.32
  */
 void
 ide_buffer_addin_style_scheme_changed (IdeBufferAddin *self,
@@ -277,8 +259,6 @@ ide_buffer_addin_style_scheme_changed (IdeBufferAddin *self,
  * that provides the addin.
  *
  * Returns: (transfer none) (nullable): An #IdeBufferAddin or %NULL
- *
- * Since: 3.32
  */
 IdeBufferAddin *
 ide_buffer_addin_find_by_module_name (IdeBuffer   *buffer,
diff --git a/src/libide/code/ide-buffer-addin.h b/src/libide/code/ide-buffer-addin.h
index df84afe42..e0e779f6f 100644
--- a/src/libide/code/ide-buffer-addin.h
+++ b/src/libide/code/ide-buffer-addin.h
@@ -32,7 +32,7 @@ G_BEGIN_DECLS
 
 #define IDE_TYPE_BUFFER_ADDIN (ide_buffer_addin_get_type())
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 G_DECLARE_INTERFACE (IdeBufferAddin, ide_buffer_addin, IDE, BUFFER_ADDIN, GObject)
 
 struct _IdeBufferAddinInterface
@@ -68,44 +68,44 @@ struct _IdeBufferAddinInterface
                                     GError              **error);
 };
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void            ide_buffer_addin_load                 (IdeBufferAddin       *self,
                                                        IdeBuffer            *buffer);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void            ide_buffer_addin_unload               (IdeBufferAddin       *self,
                                                        IdeBuffer            *buffer);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void            ide_buffer_addin_file_loaded          (IdeBufferAddin       *self,
                                                        IdeBuffer            *buffer,
                                                        GFile                *file);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void            ide_buffer_addin_save_file            (IdeBufferAddin       *self,
                                                        IdeBuffer            *buffer,
                                                        GFile                *file);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void            ide_buffer_addin_file_saved           (IdeBufferAddin       *self,
                                                        IdeBuffer            *buffer,
                                                        GFile                *file);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void            ide_buffer_addin_language_set         (IdeBufferAddin       *self,
                                                        IdeBuffer            *buffer,
                                                        const gchar          *language_id);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void            ide_buffer_addin_change_settled       (IdeBufferAddin       *self,
                                                        IdeBuffer            *buffer);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void            ide_buffer_addin_style_scheme_changed (IdeBufferAddin       *self,
                                                        IdeBuffer            *buffer);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void            ide_buffer_addin_settle_async         (IdeBufferAddin       *self,
                                                        GCancellable         *cancellable,
                                                        GAsyncReadyCallback   callback,
                                                        gpointer              user_data);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean        ide_buffer_addin_settle_finish        (IdeBufferAddin       *self,
                                                        GAsyncResult         *result,
                                                        GError              **error);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeBufferAddin *ide_buffer_addin_find_by_module_name  (IdeBuffer            *buffer,
                                                        const gchar          *module_name);
 
diff --git a/src/libide/code/ide-buffer-change-monitor.c b/src/libide/code/ide-buffer-change-monitor.c
index b812bfb65..ae5b7759e 100644
--- a/src/libide/code/ide-buffer-change-monitor.c
+++ b/src/libide/code/ide-buffer-change-monitor.c
@@ -194,8 +194,6 @@ ide_buffer_change_monitor_reload (IdeBufferChangeMonitor *self)
  *
  * Calls @callback for every line between @line_begin and @line_end that have
  * an addition, deletion, or change.
- *
- * Since: 3.32
  */
 void
 ide_buffer_change_monitor_foreach_change (IdeBufferChangeMonitor            *self,
@@ -219,8 +217,6 @@ ide_buffer_change_monitor_foreach_change (IdeBufferChangeMonitor            *sel
  * Gets the #IdeBufferChangeMonitor:buffer property.
  *
  * Returns: (transfer none): an #IdeBuffer
- *
- * Since: 3.32
  */
 IdeBuffer *
 ide_buffer_change_monitor_get_buffer (IdeBufferChangeMonitor *self)
diff --git a/src/libide/code/ide-buffer-change-monitor.h b/src/libide/code/ide-buffer-change-monitor.h
index da6ed9b48..8febcb95d 100644
--- a/src/libide/code/ide-buffer-change-monitor.h
+++ b/src/libide/code/ide-buffer-change-monitor.h
@@ -33,7 +33,7 @@ G_BEGIN_DECLS
 
 #define IDE_TYPE_BUFFER_CHANGE_MONITOR (ide_buffer_change_monitor_get_type())
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 G_DECLARE_DERIVABLE_TYPE (IdeBufferChangeMonitor, ide_buffer_change_monitor, IDE, BUFFER_CHANGE_MONITOR, 
IdeObject)
 
 typedef enum
@@ -68,20 +68,20 @@ struct _IdeBufferChangeMonitorClass
   gpointer _reserved[8];
 };
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeBuffer           *ide_buffer_change_monitor_get_buffer     (IdeBufferChangeMonitor            *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void                 ide_buffer_change_monitor_emit_changed   (IdeBufferChangeMonitor            *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void                 ide_buffer_change_monitor_foreach_change (IdeBufferChangeMonitor            *self,
                                                                guint                              line_begin,
                                                                guint                              line_end,
                                                                IdeBufferChangeMonitorForeachFunc  callback,
                                                                gpointer                           user_data);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeBufferLineChange  ide_buffer_change_monitor_get_change     (IdeBufferChangeMonitor            *self,
                                                                guint                              line);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void                 ide_buffer_change_monitor_reload         (IdeBufferChangeMonitor            *self);
 
 G_END_DECLS
diff --git a/src/libide/code/ide-buffer-manager.c b/src/libide/code/ide-buffer-manager.c
index 2f2935d7a..525af6522 100644
--- a/src/libide/code/ide-buffer-manager.c
+++ b/src/libide/code/ide-buffer-manager.c
@@ -73,7 +73,7 @@ typedef struct
 static void list_model_iface_init (GListModelInterface *iface);
 
 G_DEFINE_FINAL_TYPE_WITH_CODE (IdeBufferManager, ide_buffer_manager, IDE_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+                               G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
 
 enum {
   PROP_0,
@@ -253,8 +253,6 @@ ide_buffer_manager_class_init (IdeBufferManagerClass *klass)
    * Builder will attempt to load. Larger files will fail to load to help
    * ensure that Builder's buffer manager does not attempt to load files that
    * will slow the buffer management beyond usefulness.
-   *
-   * Since: 3.32
    */
   properties [PROP_MAX_FILE_SIZE] =
     g_param_spec_int64 ("max-file-size",
@@ -271,11 +269,8 @@ ide_buffer_manager_class_init (IdeBufferManagerClass *klass)
    * IdeBufferManager:load-buffer:
    * @self: an #IdeBufferManager
    * @buffer: an #IdeBuffer
-   * @create_new_view: if the buffer requires a view to be created
    *
    * The "load-buffer" signal is emitted before a buffer is (re)loaded.
-   *
-   * Since: 3.32
    */
   signals [LOAD_BUFFER] =
     g_signal_new ("load-buffer",
@@ -285,7 +280,7 @@ ide_buffer_manager_class_init (IdeBufferManagerClass *klass)
                   NULL,
                   NULL,
                   NULL,
-                  G_TYPE_NONE, 2, IDE_TYPE_BUFFER, G_TYPE_BOOLEAN);
+                  G_TYPE_NONE, 1, IDE_TYPE_BUFFER);
 
   /**
    * IdeBufferManager::buffer-loaded:
@@ -294,8 +289,6 @@ ide_buffer_manager_class_init (IdeBufferManagerClass *klass)
    *
    * The "buffer-loaded" signal is emitted when an #IdeBuffer has loaded
    * a file from storage.
-   *
-   * Since: 3.32
    */
   signals [BUFFER_LOADED] =
     g_signal_new ("buffer-loaded",
@@ -317,8 +310,6 @@ ide_buffer_manager_class_init (IdeBufferManagerClass *klass)
    *
    * The "buffer-saved" signal is emitted when an #IdeBuffer has been saved
    * to storage.
-   *
-   * Since: 3.32
    */
   signals [BUFFER_SAVED] =
     g_signal_new ("buffer-saved",
@@ -340,8 +331,6 @@ ide_buffer_manager_class_init (IdeBufferManagerClass *klass)
    *
    * The "buffer-unloaded" signal is emitted when an #IdeBuffer has been
    * unloaded from the buffer manager.
-   *
-   * Since: 3.32
    */
   signals [BUFFER_UNLOADED] =
     g_signal_new ("buffer-unloaded",
@@ -402,8 +391,6 @@ ide_buffer_manager_next_temp_file (IdeBufferManager *self)
  * Gets the #IdeBufferManager for the #IdeContext.
  *
  * Returns: (transfer none): an #IdeBufferManager
- *
- * Since: 3.32
  */
 IdeBufferManager *
 ide_buffer_manager_from_context (IdeContext *context)
@@ -427,8 +414,6 @@ ide_buffer_manager_from_context (IdeContext *context)
  * of @file.
  *
  * Returns: %TRUE if a buffer exists for @file
- *
- * Since: 3.32
  */
 gboolean
 ide_buffer_manager_has_file (IdeBufferManager *self,
@@ -469,8 +454,6 @@ ide_buffer_manager_find_buffer_cb (IdeObject  *object,
  * Locates the #IdeBuffer that matches #GFile, if any.
  *
  * Returns: (transfer full): an #IdeBuffer or %NULL
- *
- * Since: 3.32
  */
 IdeBuffer *
 ide_buffer_manager_find_buffer (IdeBufferManager *self,
@@ -500,8 +483,6 @@ ide_buffer_manager_find_buffer (IdeBufferManager *self,
  * various subsystems.
  *
  * Returns: the max file size in bytes or -1 for unlimited
- *
- * Since: 3.32
  */
 gssize
 ide_buffer_manager_get_max_file_size (IdeBufferManager *self)
@@ -520,8 +501,6 @@ ide_buffer_manager_get_max_file_size (IdeBufferManager *self)
  * Sets the max file size that will be allowed to be loaded from disk.
  * This is useful to protect Builder from files that would overload the
  * various subsystems.
- *
- * Since: 3.32
  */
 void
 ide_buffer_manager_set_max_file_size (IdeBufferManager *self,
@@ -598,8 +577,6 @@ ide_buffer_manager_load_file_cb (GObject      *object,
  *
  * If @notif is non-NULL, it will be updated with status information while
  * loading the document.
- *
- * Since: 3.32
  */
 void
 ide_buffer_manager_load_file_async (IdeBufferManager     *self,
@@ -614,8 +591,6 @@ ide_buffer_manager_load_file_async (IdeBufferManager     *self,
   g_autoptr(IdeTask) task = NULL;
   g_autoptr(GFile) temp_file = NULL;
   IdeBuffer *existing;
-  gboolean create_new_view = FALSE;
-  gboolean is_new = FALSE;
 
   IDE_ENTRY;
 
@@ -665,7 +640,6 @@ ide_buffer_manager_load_file_async (IdeBufferManager     *self,
       buffer = ide_buffer_manager_create_buffer (self, file,
                                                  (flags & IDE_BUFFER_OPEN_FLAGS_DISABLE_ADDINS) == 0,
                                                  temp_file != NULL);
-      is_new = TRUE;
     }
 
   /* Save this task for later in case we get in a second request to open
@@ -673,14 +647,10 @@ ide_buffer_manager_load_file_async (IdeBufferManager     *self,
    */
   g_hash_table_insert (self->loading_tasks, g_file_dup (file), g_object_ref (task));
 
-  /* We might have listeners tracking new buffers. Apply some rules to
-   * determine if we need the UI to create a new view for the buffer.
-   */
+  /* Notify any listeners of new buffers */
   g_assert (buffer != NULL);
   g_assert (IDE_IS_BUFFER (buffer));
-  create_new_view = !(flags & IDE_BUFFER_OPEN_FLAGS_NO_VIEW) &&
-                     (is_new || (flags & IDE_BUFFER_OPEN_FLAGS_BACKGROUND) == 0);
-  g_signal_emit (self, signals [LOAD_BUFFER], 0, buffer, create_new_view);
+  g_signal_emit (self, signals [LOAD_BUFFER], 0, buffer);
 
   /* Now we can load the buffer asynchronously */
   _ide_buffer_load_file_async (buffer,
@@ -701,8 +671,6 @@ ide_buffer_manager_load_file_async (IdeBufferManager     *self,
  * Completes an asynchronous request to ide_buffer_manager_laod_file_async().
  *
  * Returns: (transfer full): an #IdeBuffer
- *
- * Since: 3.32
  */
 IdeBuffer *
 ide_buffer_manager_load_file_finish (IdeBufferManager  *self,
@@ -826,8 +794,6 @@ ide_buffer_manager_save_all_foreach_cb (IdeObject *object,
  * buffers to disk.
  *
  * @callback will be executed after all the buffers have been saved.
- *
- * Since: 3.32
  */
 void
 ide_buffer_manager_save_all_async (IdeBufferManager    *self,
@@ -870,8 +836,6 @@ ide_buffer_manager_save_all_async (IdeBufferManager    *self,
  * Completes an asynchronous request to save all buffers.
  *
  * Returns: %TRUE if all the buffers were saved successfully
- *
- * Since: 3.32
  */
 gboolean
 ide_buffer_manager_save_all_finish (IdeBufferManager  *self,
@@ -1110,8 +1074,6 @@ ide_buffer_manager_apply_edits_completed_cb (IdeBufferManager *self,
  *
  * @callback should call ide_buffer_manager_apply_edits_finish() to get the
  * result of this operation.
- *
- * Since: 3.32
  */
 void
 ide_buffer_manager_apply_edits_async (IdeBufferManager    *self,
@@ -1184,11 +1146,6 @@ ide_buffer_manager_apply_edits_async (IdeBufferManager    *self,
        */
       ide_buffer_manager_load_file_async (self,
                                           file,
-                                          IDE_BUFFER_OPEN_FLAGS_NO_VIEW |
-                                          /* We are only temporary loading the buffer to replace text
-                                           * there, so let's avoid any heavy useless work that the
-                                           * buffer addins might do.
-                                           */
                                           IDE_BUFFER_OPEN_FLAGS_DISABLE_ADDINS,
                                           NULL,
                                           cancellable,
@@ -1318,8 +1275,6 @@ ide_buffer_manager_foreach_cb (IdeObject *object,
  * @user_data: closure data for @foreach_func
  *
  * Calls @foreach_func for every buffer registered.
- *
- * Since: 3.32
  */
 void
 ide_buffer_manager_foreach (IdeBufferManager     *self,
diff --git a/src/libide/code/ide-buffer-manager.h b/src/libide/code/ide-buffer-manager.h
index 8c7fd28c8..1423ef956 100644
--- a/src/libide/code/ide-buffer-manager.h
+++ b/src/libide/code/ide-buffer-manager.h
@@ -32,24 +32,20 @@ G_BEGIN_DECLS
 
 /**
  * IdeBufferOpenFlags:
- * @IDE_BUFFER_OPEN_FLAGS_NONE: No special processing will be performed
- * @IDE_BUFFER_OPEN_FLAGS_BACKGROUND: Open the document in the background (behind current view)
- * @IDE_BUFFER_OPEN_FLAGS_NO_VIEW: Open the document but do not create a new view for it
- * @IDE_BUFFER_OPEN_FLAGS_DISABLE_ADDINS: Disables any buffer addin for this buffer.
+ * @IDE_BUFFER_OPEN_FLAGS_NONE: No special processing will be performed.
+ * @IDE_BUFFER_OPEN_FLAGS_FORCE_RELOAD: Reload the buffer if already loaded.
+ * @IDE_BUFFER_OPEN_FLAGS_DISABLE_ADDINS: Disables any buffer addin for this
+ *   buffer.
  *
- * The #IdeBufferOpenFlags enumeration is used to specify how a document should
- * be opened by the workbench. Plugins may want to have a bit of control over
- * where the document is opened, and this provides a some control over that.
- *
- * Since: 3.32
+ * The #IdeBufferOpenFlags enumeration is used to specify how the buffer
+ * manager should handle loading the buffer and if certain features should
+ * be enabled or disabled.
  */
 typedef enum
 {
-  IDE_BUFFER_OPEN_FLAGS_NONE         = 0,
-  IDE_BUFFER_OPEN_FLAGS_BACKGROUND   = 1 << 0,
-  IDE_BUFFER_OPEN_FLAGS_NO_VIEW      = 1 << 1,
-  IDE_BUFFER_OPEN_FLAGS_FORCE_RELOAD = 1 << 2,
-  IDE_BUFFER_OPEN_FLAGS_DISABLE_ADDINS = 1 << 3,
+  IDE_BUFFER_OPEN_FLAGS_NONE           = 0,
+  IDE_BUFFER_OPEN_FLAGS_FORCE_RELOAD   = 1 << 1,
+  IDE_BUFFER_OPEN_FLAGS_DISABLE_ADDINS = 1 << 2,
 } IdeBufferOpenFlags;
 
 /**
@@ -58,24 +54,22 @@ typedef enum
  * @user_data: closure data
  *
  * Callback prototype for ide_buffer_manager_foreach().
- *
- * Since: 3.32
  */
 typedef void (*IdeBufferForeachFunc) (IdeBuffer *buffer,
                                       gpointer   user_data);
 
 #define IDE_TYPE_BUFFER_MANAGER (ide_buffer_manager_get_type())
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 G_DECLARE_FINAL_TYPE (IdeBufferManager, ide_buffer_manager, IDE, BUFFER_MANAGER, IdeObject)
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeBufferManager *ide_buffer_manager_from_context       (IdeContext            *context);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_buffer_manager_foreach            (IdeBufferManager      *self,
                                                          IdeBufferForeachFunc   foreach_func,
                                                          gpointer               user_data);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_buffer_manager_load_file_async    (IdeBufferManager      *self,
                                                          GFile                 *file,
                                                          IdeBufferOpenFlags     flags,
@@ -83,46 +77,46 @@ void              ide_buffer_manager_load_file_async    (IdeBufferManager      *
                                                          GCancellable          *cancellable,
                                                          GAsyncReadyCallback    callback,
                                                          gpointer               user_data);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeBuffer        *ide_buffer_manager_load_file_finish   (IdeBufferManager      *self,
                                                          GAsyncResult          *result,
                                                          GError               **error);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_buffer_manager_save_all_async     (IdeBufferManager      *self,
                                                          GCancellable          *cancellable,
                                                          GAsyncReadyCallback    callback,
                                                          gpointer               user_data);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean          ide_buffer_manager_save_all_finish    (IdeBufferManager      *self,
                                                          GAsyncResult          *result,
                                                          GError               **error);
-IDE_AVAILABLE_IN_3_36
+IDE_AVAILABLE_IN_ALL
 void              ide_buffer_manager_reload_all_async   (IdeBufferManager      *self,
                                                          GCancellable          *cancellable,
                                                          GAsyncReadyCallback    callback,
                                                          gpointer               user_data);
-IDE_AVAILABLE_IN_3_36
+IDE_AVAILABLE_IN_ALL
 gboolean          ide_buffer_manager_reload_all_finish  (IdeBufferManager      *self,
                                                          GAsyncResult          *result,
                                                          GError               **error);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean          ide_buffer_manager_has_file           (IdeBufferManager      *self,
                                                          GFile                 *file);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeBuffer        *ide_buffer_manager_find_buffer        (IdeBufferManager      *self,
                                                          GFile                 *file);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gssize            ide_buffer_manager_get_max_file_size  (IdeBufferManager      *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_buffer_manager_set_max_file_size  (IdeBufferManager      *self,
                                                          gssize                 max_file_size);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void              ide_buffer_manager_apply_edits_async  (IdeBufferManager      *self,
                                                          GPtrArray             *edits,
                                                          GCancellable          *cancellable,
                                                          GAsyncReadyCallback    callback,
                                                          gpointer               user_data);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean          ide_buffer_manager_apply_edits_finish (IdeBufferManager      *self,
                                                          GAsyncResult          *result,
                                                          GError               **error);
diff --git a/src/libide/code/ide-buffer.c b/src/libide/code/ide-buffer.c
index 44a2c8c4c..899290472 100644
--- a/src/libide/code/ide-buffer.c
+++ b/src/libide/code/ide-buffer.c
@@ -22,7 +22,6 @@
 
 #include "config.h"
 
-#include <dazzle.h>
 #include <glib/gi18n.h>
 #include <libide-io.h>
 #include <libide-plugins.h>
@@ -51,14 +50,14 @@
 
 #define SETTLING_DELAY_MSEC  333
 
-#define TAG_ERROR            "diagnostician::error"
-#define TAG_WARNING          "diagnostician::warning"
-#define TAG_DEPRECATED       "diagnostician::deprecated"
-#define TAG_UNUSED           "diagnostician::unused"
-#define TAG_NOTE             "diagnostician::note"
-#define TAG_SNIPPET_TAB_STOP "snippet::tab-stop"
-#define TAG_DEFINITION       "action::hover-definition"
-#define TAG_CURRENT_BKPT     "debugger::current-breakpoint"
+#define TAG_ERROR            "-Builder:error"
+#define TAG_WARNING          "-Builder:warning"
+#define TAG_DEPRECATED       "-Builder:deprecated"
+#define TAG_UNUSED           "-Builder:unused"
+#define TAG_NOTE             "-Builder:note"
+#define TAG_SNIPPET_TAB_STOP "-Builder:tab-stop"
+#define TAG_DEFINITION       "-Builder:hover-definition"
+#define TAG_CURRENT_BKPT     "-Builder:current-breakpoint"
 
 #define DEPRECATED_COLOR     "#babdb6"
 #define UNUSED_COLOR         "#c17d11"
@@ -72,6 +71,8 @@ struct _IdeBuffer
 {
   GtkSourceBuffer         parent_instance;
 
+  const GtkSourceEncoding *encoding;
+
   /* Owned references */
   IdeExtensionSetAdapter *addins;
   IdeExtensionSetAdapter *symbol_resolvers;
@@ -89,12 +90,15 @@ struct _IdeBuffer
   GFile                  *readlink_file;
 
   IdeTask                *in_flight_symbol_at_location;
-  gint                    in_flight_symbol_at_location_pos;
+  int                     in_flight_symbol_at_location_pos;
 
-  /* Scalars */
   guint                   change_count;
   guint                   settling_source;
-  gint                    hold;
+  int                     hold;
+  guint                   release_in_idle;
+
+  GArray                 *commit_funcs;
+  guint                   next_commit_handler;
 
   /* Bit-fields */
   IdeBufferState          state : 3;
@@ -104,8 +108,20 @@ struct _IdeBuffer
   guint                   changed_on_volume : 1;
   guint                   read_only : 1;
   guint                   highlight_diagnostics : 1;
+  GtkSourceNewlineType    newline_type : 2;
 };
 
+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;
@@ -134,6 +150,7 @@ enum {
   PROP_BUFFER_MANAGER,
   PROP_CHANGE_MONITOR,
   PROP_CHANGED_ON_VOLUME,
+  PROP_CHARSET,
   PROP_ENABLE_ADDINS,
   PROP_DIAGNOSTICS,
   PROP_FAILED,
@@ -144,6 +161,7 @@ enum {
   PROP_HIGHLIGHT_DIAGNOSTICS,
   PROP_IS_TEMPORARY,
   PROP_LANGUAGE_ID,
+  PROP_NEWLINE_TYPE,
   PROP_READ_ONLY,
   PROP_STATE,
   PROP_STYLE_SCHEME_NAME,
@@ -197,7 +215,6 @@ static void     ide_buffer_notify_style_scheme     (IdeBuffer              *self
 static void     ide_buffer_reload_file_settings    (IdeBuffer              *self);
 static void     ide_buffer_set_file_settings       (IdeBuffer              *self,
                                                     IdeFileSettings        *file_settings);
-static void     ide_buffer_emit_cursor_moved       (IdeBuffer              *self);
 static void     ide_buffer_changed                 (GtkTextBuffer          *buffer);
 static void     ide_buffer_delete_range            (GtkTextBuffer          *buffer,
                                                     GtkTextIter            *start,
@@ -206,9 +223,6 @@ static void     ide_buffer_insert_text             (GtkTextBuffer          *buff
                                                     GtkTextIter            *location,
                                                     const gchar            *text,
                                                     gint                    len);
-static void     ide_buffer_mark_set                (GtkTextBuffer          *buffer,
-                                                    const GtkTextIter      *iter,
-                                                    GtkTextMark            *mark);
 static void     ide_buffer_delay_settling          (IdeBuffer              *self);
 static gboolean ide_buffer_settled_cb              (gpointer                user_data);
 static void     ide_buffer_apply_diagnostics       (IdeBuffer              *self);
@@ -277,6 +291,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,
@@ -293,6 +316,7 @@ _ide_buffer_new (IdeBufferManager *buffer_manager,
                        "enable-addins", enable_addins,
                        "is-temporary", is_temporary,
                        "implicit-trailing-newline", FALSE,
+                       "style-scheme-name", "Adwaita",
                        NULL);
 }
 
@@ -467,6 +491,8 @@ ide_buffer_notify_language (IdeBuffer  *self,
 
   if (self->code_action_provider)
     ide_extension_adapter_set_value (self->code_action_provider, lang_id);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LANGUAGE_ID]);
 }
 
 static void
@@ -491,24 +517,30 @@ ide_buffer_dispose (GObject *object)
   g_assert (IDE_IS_MAIN_THREAD ());
 
   g_clear_handle_id (&self->settling_source, g_source_remove);
-
-  /* Remove ourselves from the object-tree if necessary */
-  if ((box = ide_object_box_from_object (object)) &&
-      !ide_object_in_destruction (IDE_OBJECT (box)))
-    ide_object_destroy (IDE_OBJECT (box));
+  g_clear_handle_id (&self->release_in_idle, g_source_remove);
 
   ide_clear_and_destroy_object (&self->addins);
+
   ide_clear_and_destroy_object (&self->rename_provider);
   ide_clear_and_destroy_object (&self->symbol_resolvers);
   ide_clear_and_destroy_object (&self->formatter);
   ide_clear_and_destroy_object (&self->code_action_provider);
   ide_clear_and_destroy_object (&self->highlight_engine);
-  g_clear_object (&self->buffer_manager);
   ide_clear_and_destroy_object (&self->change_monitor);
-  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_clear_object (&self->diagnostics);
+  g_clear_object (&self->buffer_manager);
+
+  /* Remove ourselves from the object-tree if necessary */
+  if ((box = ide_object_box_from_object (object)) &&
+      !ide_object_in_destruction (IDE_OBJECT (box)))
+    ide_object_destroy (IDE_OBJECT (box));
+
+  g_clear_pointer (&self->content, g_bytes_unref);
+
   G_OBJECT_CLASS (ide_buffer_parent_class)->dispose (object);
 }
 
@@ -542,6 +574,10 @@ ide_buffer_get_property (GObject    *object,
       g_value_set_boolean (value, ide_buffer_get_changed_on_volume (self));
       break;
 
+    case PROP_CHARSET:
+      g_value_set_string (value, ide_buffer_get_charset (self));
+      break;
+
     case PROP_ENABLE_ADDINS:
       g_value_set_boolean (value, self->enable_addins);
       break;
@@ -578,6 +614,10 @@ ide_buffer_get_property (GObject    *object,
       g_value_set_string (value, ide_buffer_get_language_id (self));
       break;
 
+    case PROP_NEWLINE_TYPE:
+      g_value_set_enum (value, ide_buffer_get_newline_type (self));
+      break;
+
     case PROP_IS_TEMPORARY:
       g_value_set_boolean (value, ide_buffer_get_is_temporary (self));
       break;
@@ -621,6 +661,10 @@ ide_buffer_set_property (GObject      *object,
       ide_buffer_set_change_monitor (self, g_value_get_object (value));
       break;
 
+    case PROP_CHARSET:
+      ide_buffer_set_charset (self, g_value_get_string (value));
+      break;
+
     case PROP_ENABLE_ADDINS:
       self->enable_addins = g_value_get_boolean (value);
       break;
@@ -641,6 +685,10 @@ ide_buffer_set_property (GObject      *object,
       ide_buffer_set_language_id (self, g_value_get_string (value));
       break;
 
+    case PROP_NEWLINE_TYPE:
+      ide_buffer_set_newline_type (self, g_value_get_enum (value));
+      break;
+
     case PROP_IS_TEMPORARY:
       self->is_temporary = g_value_get_boolean (value);
       break;
@@ -669,15 +717,12 @@ ide_buffer_class_init (IdeBufferClass *klass)
   buffer_class->changed = ide_buffer_changed;
   buffer_class->delete_range = ide_buffer_delete_range;
   buffer_class->insert_text = ide_buffer_insert_text;
-  buffer_class->mark_set = ide_buffer_mark_set;
 
   /**
    * IdeBuffer:buffer-manager:
    *
    * Sets the "buffer-manager" property, which is used by the buffer to
    * clean-up state when the buffer is no longer in use.
-   *
-   * Since: 3.32
    */
   properties [PROP_BUFFER_MANAGER] =
     g_param_spec_object ("buffer-manager",
@@ -692,8 +737,6 @@ ide_buffer_class_init (IdeBufferClass *klass)
    * The "change-monitor" property is an #IdeBufferChangeMonitor that will be
    * used to track changes in the #IdeBuffer. This can be used to show line
    * changes in the editor gutter.
-   *
-   * Since: 3.32
    */
   properties [PROP_CHANGE_MONITOR] =
     g_param_spec_object ("change-monitor",
@@ -702,14 +745,26 @@ ide_buffer_class_init (IdeBufferClass *klass)
                          IDE_TYPE_BUFFER_CHANGE_MONITOR,
                          (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
 
+  /**
+   * IdeBuffer:charset:
+   *
+   * Sets the encoding to use for the buffer based on the "charset"
+   * specified. This is useful to ensure that characters may not be
+   * lost from the original encoding.
+   */
+  properties [PROP_CHARSET] =
+    g_param_spec_string ("charset",
+                         "Character Set",
+                         "The encoding character set",
+                         "UTF-8",
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
   /**
    * IdeBuffer:changed-on-volume:
    *
    * The "changed-on-volume" property is set to %TRUE when it has been
    * discovered that the file represented by the #IdeBuffer has changed
    * externally to Builder.
-   *
-   * Since: 3.32
    */
   properties [PROP_CHANGED_ON_VOLUME] =
     g_param_spec_boolean ("changed-on-volume",
@@ -724,8 +779,6 @@ ide_buffer_class_init (IdeBufferClass *klass)
    * The "enable-addins" property determines whether addins will be aware of
    * this buffer. When set to %FALSE no ide_buffer_addin_*() functions will be
    * called on this buffer.
-   *
-   * Since: 41.0
    */
   properties [PROP_ENABLE_ADDINS] =
     g_param_spec_boolean ("enable-addins",
@@ -739,8 +792,6 @@ ide_buffer_class_init (IdeBufferClass *klass)
    *
    * The "diagnostics" property contains an #IdeDiagnostics that represent
    * the diagnostics found in the buffer.
-   *
-   * Since: 3.32
    */
   properties [PROP_DIAGNOSTICS] =
     g_param_spec_object ("diagnostics",
@@ -754,8 +805,6 @@ ide_buffer_class_init (IdeBufferClass *klass)
    *
    * The "failed" property is %TRUE when the buffer has entered a failed
    * state such as when loading or saving the buffer to disk.
-   *
-   * Since: 3.32
    */
   properties [PROP_FAILED] =
     g_param_spec_boolean ("failed",
@@ -768,8 +817,6 @@ ide_buffer_class_init (IdeBufferClass *klass)
    * IdeBuffer:file:
    *
    * The "file" property is the underlying file represented by the buffer.
-   *
-   * Since: 3.32
    */
   properties [PROP_FILE] =
     g_param_spec_object ("file",
@@ -786,8 +833,6 @@ ide_buffer_class_init (IdeBufferClass *klass)
    *
    * These are automatically discovered and kept up to date based on the
    * #IdeFileSettings extension points.
-   *
-   * Since: 3.32
    */
   properties [PROP_FILE_SETTINGS] =
     g_param_spec_object ("file-settings",
@@ -801,8 +846,6 @@ ide_buffer_class_init (IdeBufferClass *klass)
    *
    * The "has-diagnostics" property denotes that there are a non-zero number
    * of diangostics registered for the buffer.
-   *
-   * Since: 3.32
    */
   properties [PROP_HAS_DIAGNOSTICS] =
     g_param_spec_boolean ("has-diagnostics",
@@ -816,8 +859,6 @@ ide_buffer_class_init (IdeBufferClass *klass)
    *
    * The "has-symbol-resolvers" property is %TRUE if there are any symbol
    * resolvers loaded.
-   *
-   * Since: 3.32
    */
   properties [PROP_HAS_SYMBOL_RESOLVERS] =
     g_param_spec_boolean ("has-symbol-resolvers",
@@ -831,8 +872,6 @@ ide_buffer_class_init (IdeBufferClass *klass)
    *
    * The "highlight-diagnostics" property indicates that diagnostics which
    * are discovered should be styled.
-   *
-   * Since: 3.32
    */
   properties [PROP_HIGHLIGHT_DIAGNOSTICS] =
     g_param_spec_boolean ("highlight-diagnostics",
@@ -850,8 +889,6 @@ ide_buffer_class_init (IdeBufferClass *klass)
    * to select the destination file.
    *
    * Upon saving the file, the property will change to %FALSE.
-   *
-   * Since: 3.32
    */
   properties [PROP_IS_TEMPORARY] =
     g_param_spec_boolean ("is-temporary",
@@ -865,8 +902,6 @@ ide_buffer_class_init (IdeBufferClass *klass)
    *
    * The "language-id" property is a convenience property to set the
    * #GtkSourceBuffer:langauge property using a string name.
-   *
-   * Since: 3.32
    */
   properties [PROP_LANGUAGE_ID] =
     g_param_spec_string ("language-id",
@@ -875,14 +910,34 @@ ide_buffer_class_init (IdeBufferClass *klass)
                          NULL,
                          (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
 
+  /**
+   * IdeBuffer:newline-type:
+   *
+   * Sets the style of newline to append to each line.
+   *
+   * This sets the style of newlines to use within the file. Generally,
+   * only LF should be used (\n) as it is common on Unix, Linux, and most
+   * editors at this point.
+   *
+   * Older Windows editors might prefer CR+LF (\r\n) which is similar to what
+   * is displayed in a raw terminal without a \n line discipline.
+   *
+   * Really old Mac Classic systems use just a \r.
+   */
+  properties [PROP_NEWLINE_TYPE] =
+    g_param_spec_enum ("newline-type",
+                       "Newline Type",
+                       "The style of newlines to append at the end of each line",
+                       GTK_SOURCE_TYPE_NEWLINE_TYPE,
+                       GTK_SOURCE_NEWLINE_TYPE_LF,
+                       (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
   /**
    * IdeBuffer:read-only:
    *
    * The "read-only" property is set to %TRUE when it has been
    * discovered that the file represented by the #IdeBuffer is read-only
    * on the underlying storage.
-   *
-   * Since: 3.32
    */
   properties [PROP_READ_ONLY] =
     g_param_spec_boolean ("read-only",
@@ -897,8 +952,6 @@ ide_buffer_class_init (IdeBufferClass *klass)
    * The "state" property can be used to determine if the buffer is
    * currently performing any specific background work, such as loading
    * from or saving a buffer to storage.
-   *
-   * Since: 3.32
    */
   properties [PROP_STATE] =
     g_param_spec_enum ("state",
@@ -914,8 +967,6 @@ ide_buffer_class_init (IdeBufferClass *klass)
    * The "style-scheme-name" is the name of the style scheme that is used.
    * It is a convenience property so that you do not need to use the
    * #GtkSourceStyleSchemeManager to lookup style schemes.
-   *
-   * Since: 3.32
    */
   properties [PROP_STYLE_SCHEME_NAME] =
     g_param_spec_string ("style-scheme-name",
@@ -929,8 +980,6 @@ ide_buffer_class_init (IdeBufferClass *klass)
    *
    * The "title" for the buffer which includes some variant of the path
    * to the underlying file.
-   *
-   * Since: 3.32
    */
   properties [PROP_TITLE] =
     g_param_spec_string ("title",
@@ -949,8 +998,6 @@ ide_buffer_class_init (IdeBufferClass *klass)
    * being edited for a short period of time. This is useful to connect
    * to when you want to perform work as the user is editing, but you
    * don't want to get in the way of their editing.
-   *
-   * Since: 3.32
    */
   signals [CHANGE_SETTLED] =
     g_signal_new ("change-settled",
@@ -964,32 +1011,6 @@ ide_buffer_class_init (IdeBufferClass *klass)
                               G_TYPE_FROM_CLASS (klass),
                               g_cclosure_marshal_VOID__VOIDv);
 
-  /**
-   * IdeBuffer::cursor-moved:
-   * @self: an #IdeBuffer
-   * @location: a #GtkTextIter
-   *
-   * This signal is emitted when the insertion location has moved. You might
-   * want to attach to this signal to update the location of the insert mark in
-   * the display.
-   *
-   * Since: 3.32
-   */
-  signals [CURSOR_MOVED] =
-    g_signal_new ("cursor-moved",
-                  G_TYPE_FROM_CLASS (klass),
-                  G_SIGNAL_RUN_LAST,
-                  0,
-                  NULL,
-                  NULL,
-                  g_cclosure_marshal_VOID__BOXED,
-                  G_TYPE_NONE,
-                  1,
-                  GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE);
-  g_signal_set_va_marshaller (signals [CURSOR_MOVED],
-                              G_TYPE_FROM_CLASS (klass),
-                              g_cclosure_marshal_VOID__BOXEDv);
-
   /**
    * IdeBuffer::line-flags-changed:
    * @self: an #IdeBuffer
@@ -997,8 +1018,6 @@ ide_buffer_class_init (IdeBufferClass *klass)
    * The "line-flags-changed" signal is emitted when the buffer has detected
    * ancillary information has changed for lines in the buffer. Such information
    * might include diagnostics or version control information.
-   *
-   * Since: 3.32
    */
   signals [LINE_FLAGS_CHANGED] =
     g_signal_new_class_handler ("line-flags-changed",
@@ -1020,8 +1039,6 @@ ide_buffer_class_init (IdeBufferClass *klass)
    *
    * This is useful to watch if you want to perform a given action but do
    * not want to interfere with buffer loading.
-   *
-   * Since: 3.32
    */
   signals [LOADED] =
     g_signal_new_class_handler ("loaded",
@@ -1041,8 +1058,6 @@ ide_buffer_class_init (IdeBufferClass *klass)
    * Requests that attached views scroll to insert location.
    *
    * This is generally only used when loading a buffer.
-   *
-   * Since: 3.32
    */
   signals [REQUEST_SCROLL_TO_INSERT] =
     g_signal_new_class_handler ("request-scroll-to-insert",
@@ -1060,13 +1075,17 @@ 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;
+  self->newline_type = GTK_SOURCE_NEWLINE_TYPE_LF;
 
-  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",
@@ -1183,6 +1202,8 @@ void
 _ide_buffer_attach (IdeBuffer *self,
                     IdeObject *parent)
 {
+  const char *language_id;
+
   g_return_if_fail (IDE_IS_MAIN_THREAD ());
   g_return_if_fail (IDE_IS_OBJECT_BOX (parent));
   g_return_if_fail (ide_object_box_contains (IDE_OBJECT_BOX (parent), self));
@@ -1192,6 +1213,10 @@ _ide_buffer_attach (IdeBuffer *self,
   g_return_if_fail (self->formatter == NULL);
   g_return_if_fail (self->rename_provider == NULL);
 
+  /* We use "--disabled--" just like sourceview does */
+  if (!(language_id = ide_buffer_get_language_id (self)))
+    language_id = "--disabled--";
+
   /* Setup the semantic highlight engine */
   self->highlight_engine = ide_highlight_engine_new (self);
 
@@ -1200,7 +1225,7 @@ _ide_buffer_attach (IdeBuffer *self,
                                                 peas_engine_get_default (),
                                                 IDE_TYPE_BUFFER_ADDIN,
                                                 "Buffer-Addin-Languages",
-                                                ide_buffer_get_language_id (self));
+                                                language_id);
   g_signal_connect (self->addins,
                     "extension-added",
                     G_CALLBACK (_ide_buffer_addin_load_cb),
@@ -1218,7 +1243,7 @@ _ide_buffer_attach (IdeBuffer *self,
                                                      peas_engine_get_default (),
                                                      IDE_TYPE_RENAME_PROVIDER,
                                                      "Rename-Provider-Languages",
-                                                     ide_buffer_get_language_id (self));
+                                                     language_id);
   g_signal_connect_object (self->rename_provider,
                            "notify::extension",
                            G_CALLBACK (ide_buffer_rename_provider_notify_extension),
@@ -1231,7 +1256,7 @@ _ide_buffer_attach (IdeBuffer *self,
                                                peas_engine_get_default (),
                                                IDE_TYPE_FORMATTER,
                                                "Formatter-Languages",
-                                               ide_buffer_get_language_id (self));
+                                               language_id);
   g_signal_connect_object (self->formatter,
                            "notify::extension",
                            G_CALLBACK (ide_buffer_formatter_notify_extension),
@@ -1244,7 +1269,7 @@ _ide_buffer_attach (IdeBuffer *self,
                                                           peas_engine_get_default (),
                                                           IDE_TYPE_CODE_ACTION_PROVIDER,
                                                           "Code-Action-Languages",
-                                                          ide_buffer_get_language_id (self));
+                                                          language_id);
 
   g_signal_connect_object (self->code_action_provider,
                            "notify::extension",
@@ -1258,7 +1283,7 @@ _ide_buffer_attach (IdeBuffer *self,
                                                           peas_engine_get_default (),
                                                           IDE_TYPE_SYMBOL_RESOLVER,
                                                           "Symbol-Resolver-Languages",
-                                                          ide_buffer_get_language_id (self));
+                                                          language_id);
   g_signal_connect_object (self->symbol_resolvers,
                            "extension-added",
                            G_CALLBACK (ide_buffer_symbol_resolver_added),
@@ -1281,8 +1306,6 @@ _ide_buffer_attach (IdeBuffer *self,
  * Gets the #IdeBuffer:file property.
  *
  * Returns: (transfer none): a #GFile
- *
- * Since: 3.32
  */
 GFile *
 ide_buffer_get_file (IdeBuffer *self)
@@ -1306,8 +1329,6 @@ ide_buffer_get_file (IdeBuffer *self)
  * Gets the URI for the underlying file and returns a copy of it.
  *
  * Returns: (transfer full): a new string
- *
- * Since: 3.32
  */
 gchar *
 ide_buffer_dup_uri (IdeBuffer *self)
@@ -1326,8 +1347,6 @@ ide_buffer_dup_uri (IdeBuffer *self)
  * when the user requests to save the buffer.
  *
  * Returns: %TRUE if the buffer is for a temporary file
- *
- * Since: 3.32
  */
 gboolean
 ide_buffer_get_is_temporary (IdeBuffer *self)
@@ -1347,8 +1366,6 @@ ide_buffer_get_is_temporary (IdeBuffer *self)
  * This will changed while files are loaded or saved to disk.
  *
  * Returns: an #IdeBufferState
- *
- * Since: 3.32
  */
 IdeBufferState
 ide_buffer_get_state (IdeBuffer *self)
@@ -1510,8 +1527,6 @@ _ide_buffer_load_file_async (IdeBuffer            *self,
  * that the completion of signals and addins may be notified.
  *
  * Returns: %TRUE if the file was successfully loaded
- *
- * Since: 3.32
  */
 gboolean
 _ide_buffer_load_file_finish (IdeBuffer     *self,
@@ -1645,12 +1660,21 @@ ide_buffer_save_file_settle_cb (GObject      *object,
     }
 
   saver = gtk_source_file_saver_new (GTK_SOURCE_BUFFER (self), state->source_file);
+
   /* At this point, we've notified the user of changes to the underlying file using
    * the infobar, so just save the file knowing that we are overwriting things.
    */
   gtk_source_file_saver_set_flags (saver,
                                    (GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_INVALID_CHARS |
                                     GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_MODIFICATION_TIME));
+
+  /* Propagate the requested encoding, if necessary */
+  if (self->encoding != NULL)
+    gtk_source_file_saver_set_encoding (saver, self->encoding);
+
+  /* Same for newlines */
+  gtk_source_file_saver_set_newline_type (saver, self->newline_type);
+
   gtk_source_file_saver_save_async (saver,
                                     G_PRIORITY_DEFAULT,
                                     ide_task_get_cancellable (task),
@@ -1667,6 +1691,7 @@ ide_buffer_save_file_settle_cb (GObject      *object,
  * @self: an #IdeBuffer
  * @file: (nullable): a #GFile or %NULL
  * @cancellable: (nullable): a #GCancellable
+ * @notif: (out) (optional) (transfer full): a location for an #IdeNotification or %NULL
  * @callback: a #GAsyncReadyCallback to execute upon completion
  * @user_data: closure data for @callback
  *
@@ -1679,8 +1704,6 @@ ide_buffer_save_file_settle_cb (GObject      *object,
  *
  * @callback is executed upon completion and should call
  * ide_buffer_save_file_finish() to get the result of the operation.
- *
- * Since: 3.32
  */
 void
 ide_buffer_save_file_async (IdeBuffer            *self,
@@ -1805,8 +1828,6 @@ set_out_param:
  * ide_buffer_save_file_async().
  *
  * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
- *
- * Since: 3.32
  */
 gboolean
 ide_buffer_save_file_finish (IdeBuffer     *self,
@@ -1827,8 +1848,6 @@ ide_buffer_save_file_finish (IdeBuffer     *self,
  * A helper to get the language identifier of the buffers current language.
  *
  * Returns: (nullable): a string containing the language id, or %NULL
- *
- * Since: 3.32
  */
 const gchar *
 ide_buffer_get_language_id (IdeBuffer *self)
@@ -1897,8 +1916,6 @@ _ide_buffer_set_failure (IdeBuffer    *self,
  * buffer.
  *
  * Returns: (transfer none): a #GError, or %NULL
- *
- * Since: 3.32
  */
 const GError *
 ide_buffer_get_failure (IdeBuffer *self)
@@ -1917,8 +1934,6 @@ ide_buffer_get_failure (IdeBuffer *self)
  * in some aspect such as loading or saving.
  *
  * Returns: %TRUE if the buffer is in a failed state
- *
- * Since: 3.32
  */
 gboolean
 ide_buffer_get_failed (IdeBuffer *self)
@@ -1978,23 +1993,6 @@ ide_buffer_reload_file_settings (IdeBuffer *self)
     }
 }
 
-static void
-ide_buffer_emit_cursor_moved (IdeBuffer *self)
-{
-  g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (IDE_IS_BUFFER (self));
-
-  if (!ide_buffer_get_loading (self))
-    {
-      GtkTextMark *mark;
-      GtkTextIter iter;
-
-      mark = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (self));
-      gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (self), &iter, mark);
-      g_signal_emit (self, signals [CURSOR_MOVED], 0, &iter);
-    }
-}
-
 /**
  * ide_buffer_get_loading:
  * @self: an #IdeBuffer
@@ -2003,8 +2001,6 @@ ide_buffer_emit_cursor_moved (IdeBuffer *self)
  * to calling ide_buffer_get_state() and checking for %IDE_BUFFER_STATE_LOADING.
  *
  * Returns: %TRUE if the buffer is loading; otherwise %FALSE.
- *
- * Since: 3.32
  */
 gboolean
 ide_buffer_get_loading (IdeBuffer *self)
@@ -2037,10 +2033,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);
 
@@ -2060,9 +2060,26 @@ 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);
 
-  ide_buffer_emit_cursor_moved (IDE_BUFFER (buffer));
+  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;
 }
@@ -2073,12 +2090,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);
 
@@ -2092,33 +2112,31 @@ ide_buffer_insert_text (GtkTextBuffer *buffer,
       ((text [0] == '\n') || ((len > 1) && (strchr (text, '\n') != NULL))))
     recheck_language = TRUE;
 
-  GTK_TEXT_BUFFER_CLASS (ide_buffer_parent_class)->insert_text (buffer, location, text, len);
+  position = gtk_text_iter_get_offset (location);
+  length = g_utf8_strlen (text, len);
 
-  ide_buffer_emit_cursor_moved (IDE_BUFFER (buffer));
+  for (guint i = 0; i < self->commit_funcs->len; i++)
+    {
+      const CommitHooks *hooks = &g_array_index (self->commit_funcs, CommitHooks, i);
 
-  if G_UNLIKELY (recheck_language)
-    ide_buffer_guess_language (IDE_BUFFER (buffer));
+      if (hooks->before_insert_text != NULL)
+        hooks->before_insert_text (self, position, length, hooks->user_data);
+    }
 
-  IDE_EXIT;
-}
+  GTK_TEXT_BUFFER_CLASS (ide_buffer_parent_class)->insert_text (buffer, location, text, len);
 
-static void
-ide_buffer_mark_set (GtkTextBuffer     *buffer,
-                     const GtkTextIter *iter,
-                     GtkTextMark       *mark)
-{
-  IdeBuffer *self = (IdeBuffer *)buffer;
+  for (guint i = 0; i < self->commit_funcs->len; i++)
+    {
+      const CommitHooks *hooks = &g_array_index (self->commit_funcs, CommitHooks, i);
 
-  g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (IDE_IS_BUFFER (self));
+      if (hooks->after_insert_text != NULL)
+        hooks->after_insert_text (self, position, length, hooks->user_data);
+    }
 
-  GTK_TEXT_BUFFER_CLASS (ide_buffer_parent_class)->mark_set (buffer, iter, mark);
+  if G_UNLIKELY (recheck_language)
+    ide_buffer_guess_language (IDE_BUFFER (buffer));
 
-  if (!ide_buffer_get_loading (self))
-    {
-      if (mark == gtk_text_buffer_get_insert (buffer))
-        ide_buffer_emit_cursor_moved (IDE_BUFFER (buffer));
-    }
+  IDE_EXIT;
 }
 
 /**
@@ -2129,8 +2147,6 @@ ide_buffer_mark_set (GtkTextBuffer     *buffer,
  * externally from this #IdeBuffer.
  *
  * Returns: %TRUE if @self is known to be modified on storage
- *
- * Since: 3.32
  */
 gboolean
 ide_buffer_get_changed_on_volume (IdeBuffer *self)
@@ -2150,8 +2166,6 @@ ide_buffer_get_changed_on_volume (IdeBuffer *self)
  *
  * Set this to %TRUE if the buffer has been discovered to have changed
  * outside of this buffer.
- *
- * Since: 3.32
  */
 void
 _ide_buffer_set_changed_on_volume (IdeBuffer *self,
@@ -2179,8 +2193,6 @@ _ide_buffer_set_changed_on_volume (IdeBuffer *self,
  * the user about saving the file.
  *
  * Returns: %TRUE if the underlying file is read-only
- *
- * Since: 3.32
  */
 gboolean
 ide_buffer_get_read_only (IdeBuffer *self)
@@ -2198,8 +2210,6 @@ ide_buffer_get_read_only (IdeBuffer *self)
  *
  * Sets the #IdeBuffer:read-only property, which should be set when the buffer
  * has been discovered to be read-only on disk.
- *
- * Since: 3.32
  */
 void
 _ide_buffer_set_read_only (IdeBuffer *self,
@@ -2225,8 +2235,6 @@ _ide_buffer_set_read_only (IdeBuffer *self,
  * property.
  *
  * Returns: (nullable): a string containing the style scheme or %NULL
- *
- * Since: 3.32
  */
 const gchar *
 ide_buffer_get_style_scheme_name (IdeBuffer *self)
@@ -2249,8 +2257,6 @@ ide_buffer_get_style_scheme_name (IdeBuffer *self)
  *
  * Sets the #IdeBuffer:style-scheme property by locating the style scheme
  * matching @style_scheme_name.
- *
- * Since: 3.32
  */
 void
 ide_buffer_set_style_scheme_name (IdeBuffer   *self,
@@ -2277,8 +2283,6 @@ ide_buffer_set_style_scheme_name (IdeBuffer   *self,
  * make this relative to the project workdir if possible.
  *
  * Returns: (transfer full): a string containing a title
- *
- * Since: 3.32
  */
 gchar *
 ide_buffer_dup_title (IdeBuffer *self)
@@ -2326,8 +2330,6 @@ ide_buffer_dup_title (IdeBuffer *self)
  * Checks if diagnostics should be highlighted.
  *
  * Returns: %TRUE if diagnostics should be highlighted
- *
- * Since: 3.32
  */
 gboolean
 ide_buffer_get_highlight_diagnostics (IdeBuffer *self)
@@ -2345,8 +2347,6 @@ ide_buffer_get_highlight_diagnostics (IdeBuffer *self)
  * Sets the #IdeBuffer:highlight-diagnostics property.
  *
  * If set to %TRUE, diagnostics will be styled in the buffer.
- *
- * Since: 3.32
  */
 void
 ide_buffer_set_highlight_diagnostics (IdeBuffer *self,
@@ -2374,8 +2374,6 @@ ide_buffer_set_highlight_diagnostics (IdeBuffer *self,
  * Gets an #IdeLocation for the position represented by @iter.
  *
  * Returns: (transfer full): an #IdeLocation
- *
- * Since: 3.32
  */
 IdeLocation *
 ide_buffer_get_iter_location (IdeBuffer         *self,
@@ -2397,8 +2395,6 @@ ide_buffer_get_iter_location (IdeBuffer         *self,
  * Gets an #IdeRange to represent the current buffer selection.
  *
  * Returns: (transfer full): an #IdeRange
- *
- * Since: 3.32
  */
 IdeRange *
 ide_buffer_get_selection_range (IdeBuffer *self)
@@ -2426,8 +2422,6 @@ ide_buffer_get_selection_range (IdeBuffer *self)
  * Gets the monotonic change count for the buffer.
  *
  * Returns: the change count for the buffer
- *
- * Since: 3.32
  */
 guint
 ide_buffer_get_change_count (IdeBuffer *self)
@@ -2464,9 +2458,9 @@ ide_buffer_delay_settling (IdeBuffer *self)
   g_assert (IDE_IS_BUFFER (self));
 
   g_clear_handle_id (&self->settling_source, g_source_remove);
-  self->settling_source = gdk_threads_add_timeout (SETTLING_DELAY_MSEC,
-                                                   ide_buffer_settled_cb,
-                                                   self);
+  self->settling_source = g_timeout_add (SETTLING_DELAY_MSEC,
+                                         ide_buffer_settled_cb,
+                                         self);
 }
 
 /**
@@ -2477,8 +2471,6 @@ ide_buffer_delay_settling (IdeBuffer *self)
  * Sets the #IdeDiagnostics for the buffer. These will be used to highlight
  * the buffer for errors and warnings if #IdeBuffer:highlight-diagnostics
  * is %TRUE.
- *
- * Since: 3.32
  */
 void
 ide_buffer_set_diagnostics (IdeBuffer      *self,
@@ -2520,8 +2512,6 @@ ide_buffer_set_diagnostics (IdeBuffer      *self,
  * Gets the #IdeDiagnostics for the buffer if any have been registered.
  *
  * Returns: (transfer none) (nullable): an #IdeDiagnostics or %NULL
- *
- * Since: 3.32
  */
 IdeDiagnostics *
 ide_buffer_get_diagnostics (IdeBuffer *self)
@@ -2539,8 +2529,6 @@ ide_buffer_get_diagnostics (IdeBuffer *self)
  * Returns %TRUE if any diagnostics have been registered for the buffer.
  *
  * Returns: %TRUE if there are a non-zero number of diagnostics.
- *
- * Since: 3.32
  */
 gboolean
 ide_buffer_has_diagnostics (IdeBuffer *self)
@@ -2573,19 +2561,19 @@ ide_buffer_clear_diagnostics (IdeBuffer *self)
   table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (self));
 
   if (NULL != (tag = gtk_text_tag_table_lookup (table, TAG_NOTE)))
-    dzl_gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end, TRUE);
+    gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end);
 
   if (NULL != (tag = gtk_text_tag_table_lookup (table, TAG_WARNING)))
-    dzl_gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end, TRUE);
+    gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end);
 
   if (NULL != (tag = gtk_text_tag_table_lookup (table, TAG_DEPRECATED)))
-    dzl_gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end, TRUE);
+    gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end);
 
   if (NULL != (tag = gtk_text_tag_table_lookup (table, TAG_UNUSED)))
-    dzl_gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end, TRUE);
+    gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end);
 
   if (NULL != (tag = gtk_text_tag_table_lookup (table, TAG_ERROR)))
-    dzl_gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end, TRUE);
+    gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end);
 }
 
 static void
@@ -2727,8 +2715,6 @@ ide_buffer_apply_diagnostics (IdeBuffer *self)
  * @location: a #IdeLocation
  *
  * Set @iter to the position designated by @location.
- *
- * Since: 3.32
  */
 void
 ide_buffer_get_iter_at_location (IdeBuffer   *self,
@@ -2770,8 +2756,6 @@ ide_buffer_get_iter_at_location (IdeBuffer   *self,
  * Gets the #IdeBuffer:change-monitor for the buffer.
  *
  * Returns: (transfer none) (nullable): an #IdeBufferChangeMonitor or %NULL
- *
- * Since: 3.32
  */
 IdeBufferChangeMonitor *
 ide_buffer_get_change_monitor (IdeBuffer *self)
@@ -2787,8 +2771,6 @@ ide_buffer_get_change_monitor (IdeBuffer *self)
  * @change_monitor: (nullable): an #IdeBufferChangeMonitor or %NULL
  *
  * Sets an #IdeBufferChangeMonitor to use for the buffer.
- *
- * Since: 3.32
  */
 void
 ide_buffer_set_change_monitor (IdeBuffer              *self,
@@ -2859,8 +2841,6 @@ ide_buffer_can_do_newline_hack (IdeBuffer *self,
  * if the content is out of sync.
  *
  * Returns: (transfer full): a #GBytes containing the buffer content.
- *
- * Since: 3.32
  */
 GBytes *
 ide_buffer_dup_content (IdeBuffer *self)
@@ -2889,8 +2869,7 @@ ide_buffer_dup_content (IdeBuffer *self)
        * files will restore to a buffer, for which \n is acceptable.
        */
       len = strlen (text);
-      if (gtk_source_buffer_get_implicit_trailing_newline (GTK_SOURCE_BUFFER (self)) &&
-          (len == 0 || text[len - 1] != '\n'))
+      if (gtk_source_buffer_get_implicit_trailing_newline (GTK_SOURCE_BUFFER (self)))
         {
           if (!ide_buffer_can_do_newline_hack (self, len))
             {
@@ -2975,8 +2954,6 @@ ide_buffer_format_selection_range_cb (GObject      *object,
  * @user_data: user data for @callback
  *
  * Formats the selection using an available #IdeFormatter for the buffer.
- *
- * Since: 3.32
  */
 void
 ide_buffer_format_selection_async (IdeBuffer           *self,
@@ -3050,8 +3027,6 @@ ide_buffer_format_selection_async (IdeBuffer           *self,
  * Completes an asynchronous request to ide_buffer_format_selection_async().
  *
  * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
- *
- * Since: 3.32
  */
 gboolean
 ide_buffer_format_selection_finish (IdeBuffer     *self,
@@ -3102,8 +3077,6 @@ ide_buffer_query_code_action_cb(GObject      *object,
  * @user_data: user data for @callback
  *
  * Queries for code actions in the current buffer.
- *
- * Since: 42.0
  */
 void
 ide_buffer_code_action_query_async(IdeBuffer           *self,
@@ -3157,8 +3130,6 @@ ide_buffer_code_action_query_async(IdeBuffer           *self,
  * Completes an asynchronous request to ide_buffer_query_code_action_async().
  *
  * Returns: (transfer full) (element-type IdeCodeAction): a #GPtrArray of #IdeCodeAction.
- *
- * Since: 42.0
  */
 GPtrArray*
 ide_buffer_code_action_query_finish(IdeBuffer     *self,
@@ -3184,8 +3155,6 @@ ide_buffer_code_action_query_finish(IdeBuffer     *self,
  * Gets the location of the insert mark as an #IdeLocation.
  *
  * Returns: (transfer full): An #IdeLocation
- *
- * Since: 3.32
  */
 IdeLocation *
 ide_buffer_get_insert_location (IdeBuffer *self)
@@ -3210,8 +3179,6 @@ ide_buffer_get_insert_location (IdeBuffer *self)
  * Gets the word found under the position denoted by @iter.
  *
  * Returns: (transfer full): A newly allocated string.
- *
- * Since: 3.32
  */
 gchar *
 ide_buffer_get_word_at_iter (IdeBuffer         *self,
@@ -3243,8 +3210,6 @@ ide_buffer_get_word_at_iter (IdeBuffer         *self,
  *
  * Returns: (nullable) (transfer none): An #IdeRenameProvider or %NULL if
  *   there is no #IdeRenameProvider that can statisfy the buffer.
- *
- * Since: 3.32
  */
 IdeRenameProvider *
 ide_buffer_get_rename_provider (IdeBuffer *self)
@@ -3268,8 +3233,6 @@ ide_buffer_get_rename_provider (IdeBuffer *self)
  * syntax are chnaged.
  *
  * Returns: (transfer none) (nullable): an #IdeFileSettings or %NULL
- *
- * Since: 3.32
  */
 IdeFileSettings *
 ide_buffer_get_file_settings (IdeBuffer *self)
@@ -3286,8 +3249,6 @@ ide_buffer_get_file_settings (IdeBuffer *self)
  * Locates the #IdeContext for the buffer and returns it.
  *
  * Returns: (transfer full): an #IdeContext
- *
- * Since: 3.32
  */
 IdeContext *
 ide_buffer_ref_context (IdeBuffer *self)
@@ -3550,8 +3511,6 @@ ide_buffer_init_tags (IdeBuffer *self)
  * Gets an #IdeFormatter for the buffer, if any.
  *
  * Returns: (transfer none) (nullable): an #IdeFormatter or %NULL
- *
- * Since: 3.32
  */
 IdeFormatter *
 ide_buffer_get_formatter (IdeBuffer *self)
@@ -3581,8 +3540,6 @@ _ide_buffer_sync_to_unsaved_files (IdeBuffer *self)
  * @self: an #IdeBuffer
  *
  * Force @self to rebuild the highlighted words.
- *
- * Since: 3.32
  */
 void
 ide_buffer_rehighlight (IdeBuffer *self)
@@ -3683,8 +3640,6 @@ ide_buffer_get_symbol_at_location_cb (GObject      *object,
  * @user_data: a #gpointer to hold user data
  *
  * Asynchronously get a possible symbol at @location.
- *
- * Since: 3.32
  */
 void
 ide_buffer_get_symbol_at_location_async (IdeBuffer           *self,
@@ -3765,8 +3720,6 @@ ide_buffer_get_symbol_at_location_async (IdeBuffer           *self,
  * Completes an asynchronous request to locate a symbol at a location.
  *
  * Returns: (transfer full): An #IdeSymbol or %NULL.
- *
- * Since: 3.32
  */
 IdeSymbol *
 ide_buffer_get_symbol_at_location_finish (IdeBuffer     *self,
@@ -3792,8 +3745,6 @@ ide_buffer_get_symbol_at_location_finish (IdeBuffer     *self,
  *
  * Calling gtk_text_iter_order() with the results of this function would be
  * equivalent to calling gtk_text_buffer_get_selection_bounds().
- *
- * Since: 3.32
  */
 void
 ide_buffer_get_selection_bounds (IdeBuffer   *self,
@@ -3845,8 +3796,6 @@ ide_buffer_get_symbol_resolvers_cb (IdeExtensionSetAdapter *set,
  *
  * Returns: (transfer full) (element-type IdeSymbolResolver): a #GPtrArray
  *   of #IdeSymbolResolver.
- *
- * Since: 3.32
  */
 GPtrArray *
 ide_buffer_get_symbol_resolvers (IdeBuffer *self)
@@ -3875,8 +3824,6 @@ ide_buffer_get_symbol_resolvers (IdeBuffer *self)
  *
  * Returns: (transfer full) (nullable): a string containing the line's text
  *   or %NULL if the line does not exist.
- *
- * Since: 3.32
  */
 gchar *
 ide_buffer_get_line_text (IdeBuffer *self,
@@ -3986,8 +3933,6 @@ _ide_buffer_cancel_cursor_restore (IdeBuffer *self)
  * When the hold count reaches zero, the buffer will be destroyed.
  *
  * Returns: (transfer full): @self
- *
- * Since: 3.32
  */
 IdeBuffer *
 ide_buffer_hold (IdeBuffer *self)
@@ -3997,9 +3942,27 @@ ide_buffer_hold (IdeBuffer *self)
 
   self->hold++;
 
+  g_clear_handle_id (&self->release_in_idle, g_source_remove);
+
   return g_object_ref (self);
 }
 
+static gboolean
+ide_buffer_release_in_idle (gpointer data)
+{
+  IdeBuffer *self = data;
+  IdeObjectBox *box;
+
+  g_assert (IDE_IS_BUFFER (self));
+
+  self->release_in_idle = 0;
+
+  if ((box = ide_object_box_from_object (G_OBJECT (self))))
+    ide_object_destroy (IDE_OBJECT (box));
+
+  return G_SOURCE_REMOVE;
+}
+
 /**
  * ide_buffer_release:
  * @self: a #IdeBuffer
@@ -4008,8 +3971,6 @@ ide_buffer_hold (IdeBuffer *self)
  *
  * The buffer will be destroyed and unloaded when the hold count
  * reaches zero.
- *
- * Since: 3.32
  */
 void
 ide_buffer_release (IdeBuffer *self)
@@ -4022,10 +3983,13 @@ ide_buffer_release (IdeBuffer *self)
 
   if (self->hold == 0)
     {
-      IdeObjectBox *box = ide_object_box_from_object (G_OBJECT (self));
-
-      if (box != NULL)
-        ide_object_destroy (IDE_OBJECT (box));
+      g_assert (self->release_in_idle == 0);
+      self->release_in_idle =
+        g_idle_add_full (G_PRIORITY_DEFAULT,
+                         ide_buffer_release_in_idle,
+                         self,
+                         g_object_unref);
+      return;
     }
 
   g_object_unref (self);
@@ -4056,8 +4020,6 @@ _ide_buffer_line_flags_changed (IdeBuffer *self)
  * Checks if any symbol resolvers are available.
  *
  * Returns: %TRUE if at least one symbol resolvers is available
- *
- * Since: 3.32
  */
 gboolean
 ide_buffer_has_symbol_resolvers (IdeBuffer *self)
@@ -4190,3 +4152,139 @@ _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);
+}
+
+const char *
+ide_buffer_get_charset (IdeBuffer *self)
+{
+  g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
+
+  return self->encoding
+         ? gtk_source_encoding_get_charset (self->encoding)
+         : "UTF-8";
+}
+
+void
+ide_buffer_set_charset (IdeBuffer  *self,
+                        const char *charset)
+{
+  GSList *all;
+
+  g_return_if_fail (IDE_IS_BUFFER (self));
+
+  if (charset == NULL || charset[0] == 0)
+    charset = "UTF-8";
+
+  all = gtk_source_encoding_get_all ();
+
+  for (const GSList *iter = all; iter; iter = iter->next)
+    {
+      const GtkSourceEncoding *encoding = iter->data;
+
+      if (g_strcmp0 (charset, gtk_source_encoding_get_charset (encoding)) == 0)
+        {
+          if (self->encoding != encoding)
+            {
+              self->encoding = encoding;
+              g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CHARSET]);
+              break;
+            }
+        }
+    }
+
+  g_slist_free (all);
+}
+
+GtkSourceNewlineType
+ide_buffer_get_newline_type (IdeBuffer *self)
+{
+  g_return_val_if_fail (IDE_IS_BUFFER (self), GTK_SOURCE_NEWLINE_TYPE_LF);
+
+  return self->newline_type;
+}
+
+void
+ide_buffer_set_newline_type (IdeBuffer            *self,
+                             GtkSourceNewlineType  newline_type)
+{
+  g_return_if_fail (IDE_IS_BUFFER (self));
+  g_return_if_fail (newline_type == GTK_SOURCE_NEWLINE_TYPE_LF ||
+                    newline_type == GTK_SOURCE_NEWLINE_TYPE_CR ||
+                    newline_type == GTK_SOURCE_NEWLINE_TYPE_CR_LF);
+
+  if (newline_type == self->newline_type)
+    return;
+
+  self->newline_type = newline_type;
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_NEWLINE_TYPE]);
+}
diff --git a/src/libide/code/ide-buffer.h b/src/libide/code/ide-buffer.h
index 7c186076f..b4a938a88 100644
--- a/src/libide/code/ide-buffer.h
+++ b/src/libide/code/ide-buffer.h
@@ -48,139 +48,165 @@ typedef enum
   IDE_BUFFER_STATE_FAILED,
 } IdeBufferState;
 
-IDE_AVAILABLE_IN_3_32
+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)
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 GBytes                 *ide_buffer_dup_content                   (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gchar                  *ide_buffer_dup_title                     (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void                    ide_buffer_format_selection_async        (IdeBuffer               *self,
                                                                   IdeFormatterOptions     *options,
                                                                   GCancellable            *cancellable,
                                                                   GAsyncReadyCallback      callback,
                                                                   gpointer                 user_data);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean                ide_buffer_format_selection_finish       (IdeBuffer               *self,
                                                                   GAsyncResult            *result,
                                                                   GError                 **error);
-IDE_AVAILABLE_IN_42
+IDE_AVAILABLE_IN_ALL
 void                    ide_buffer_code_action_query_async       (IdeBuffer               *self,
                                                                   GCancellable            *cancellable,
                                                                   GAsyncReadyCallback      callback,
                                                                   gpointer                 user_data);
 
-IDE_AVAILABLE_IN_42
+IDE_AVAILABLE_IN_ALL
 GPtrArray*              ide_buffer_code_action_query_finish      (IdeBuffer               *self,
                                                                   GAsyncResult            *result,
                                                                   GError                 **error);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 guint                   ide_buffer_get_change_count              (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeBufferChangeMonitor *ide_buffer_get_change_monitor            (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean                ide_buffer_get_changed_on_volume         (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeDiagnostics         *ide_buffer_get_diagnostics               (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeLocation            *ide_buffer_get_insert_location           (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean                ide_buffer_get_is_temporary              (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean                ide_buffer_get_failed                    (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 const GError           *ide_buffer_get_failure                   (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gchar                  *ide_buffer_dup_uri                       (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 GFile                  *ide_buffer_get_file                      (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeFileSettings        *ide_buffer_get_file_settings             (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeFormatter           *ide_buffer_get_formatter                 (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean                ide_buffer_get_highlight_diagnostics     (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void                    ide_buffer_get_iter_at_location          (IdeBuffer               *self,
                                                                   GtkTextIter             *iter,
                                                                   IdeLocation             *location);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeLocation            *ide_buffer_get_iter_location             (IdeBuffer               *self,
                                                                   const GtkTextIter       *iter);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 const gchar            *ide_buffer_get_language_id               (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void                    ide_buffer_set_language_id               (IdeBuffer               *self,
                                                                   const gchar             *language_id);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gchar                  *ide_buffer_get_line_text                 (IdeBuffer               *self,
                                                                   guint                    line);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean                ide_buffer_get_loading                   (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean                ide_buffer_get_read_only                 (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeRenameProvider      *ide_buffer_get_rename_provider           (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void                    ide_buffer_get_selection_bounds          (IdeBuffer               *self,
                                                                   GtkTextIter             *insert,
                                                                   GtkTextIter             *selection);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeRange               *ide_buffer_get_selection_range           (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeBufferState          ide_buffer_get_state                     (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 const gchar            *ide_buffer_get_style_scheme_name         (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void                    ide_buffer_get_symbol_at_location_async  (IdeBuffer               *self,
                                                                   const GtkTextIter       *location,
                                                                   GCancellable            *cancellable,
                                                                   GAsyncReadyCallback      callback,
                                                                   gpointer                 user_data);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeSymbol              *ide_buffer_get_symbol_at_location_finish (IdeBuffer               *self,
                                                                   GAsyncResult            *result,
                                                                   GError                 **error);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 GPtrArray              *ide_buffer_get_symbol_resolvers          (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gchar                  *ide_buffer_get_word_at_iter              (IdeBuffer               *self,
                                                                   const GtkTextIter       *iter);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean                ide_buffer_has_diagnostics               (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean                ide_buffer_has_symbol_resolvers          (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeBuffer              *ide_buffer_hold                          (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 IdeContext             *ide_buffer_ref_context                   (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void                    ide_buffer_rehighlight                   (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void                    ide_buffer_release                       (IdeBuffer               *self);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void                    ide_buffer_save_file_async               (IdeBuffer               *self,
                                                                   GFile                   *file,
                                                                   GCancellable            *cancellable,
                                                                   IdeNotification        **notif,
                                                                   GAsyncReadyCallback      callback,
                                                                   gpointer                 user_data);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 gboolean                ide_buffer_save_file_finish              (IdeBuffer               *self,
                                                                   GAsyncResult            *result,
                                                                   GError                 **error);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void                    ide_buffer_set_change_monitor            (IdeBuffer               *self,
                                                                   IdeBufferChangeMonitor  *change_monitor);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void                    ide_buffer_set_diagnostics               (IdeBuffer               *self,
                                                                   IdeDiagnostics          *diagnostics);
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 void                    ide_buffer_set_highlight_diagnostics     (IdeBuffer               *self,
                                                                   gboolean                 
highlight_diagnostics);
-IDE_AVAILABLE_IN_3_32
+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);
+IDE_AVAILABLE_IN_ALL
+const char             *ide_buffer_get_charset                   (IdeBuffer               *self);
+IDE_AVAILABLE_IN_ALL
+void                    ide_buffer_set_charset                   (IdeBuffer               *self,
+                                                                  const char              *charset);
+IDE_AVAILABLE_IN_ALL
+GtkSourceNewlineType    ide_buffer_get_newline_type              (IdeBuffer               *self);
+IDE_AVAILABLE_IN_ALL
+void                    ide_buffer_set_newline_type              (IdeBuffer               *self,
+                                                                  GtkSourceNewlineType     newline_type);
 
 G_END_DECLS


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