[gnome-builder/gnome-builder-43] plugins/codeui: bridge IdeFormatter on save



commit 86b3a5871ffaba0cbbcfc087314e636bcdfa8b53
Author: Christian Hergert <chergert redhat com>
Date:   Wed Sep 21 16:51:44 2022 -0700

    plugins/codeui: bridge IdeFormatter on save
    
    This uses the formatter for the buffer to format the document upon save
    if the format-on-save GSetting is set. It requires the setting to be
    enabled and an IdeFormatter registered for the document syntax.
    
    Additionally, since the buffer does not have access to view settings like
    the size to draw indentation, we rely on IdeFileSettings to get those
    values. In general, those should be in-sync with the document view since
    it uses those to bridge to the display too.
    
    Related #1795

 src/plugins/codeui/gbp-codeui-buffer-addin.c | 176 +++++++++++++++++++++++++++
 1 file changed, 176 insertions(+)
---
diff --git a/src/plugins/codeui/gbp-codeui-buffer-addin.c b/src/plugins/codeui/gbp-codeui-buffer-addin.c
index b4c453e19..343bcaf97 100644
--- a/src/plugins/codeui/gbp-codeui-buffer-addin.c
+++ b/src/plugins/codeui/gbp-codeui-buffer-addin.c
@@ -22,12 +22,18 @@
 
 #include "config.h"
 
+#include <glib/gi18n.h>
+
 #include <libide-code.h>
+#include <libide-gui.h>
 
+#include "ide-application-private.h"
 #include "ide-diagnostics-manager-private.h"
 
 #include "gbp-codeui-buffer-addin.h"
 
+#define FORMAT_ON_SAVE_TIMEOUT_MSEC 2000
+
 struct _GbpCodeuiBufferAddin
 {
   GObject                parent_instance;
@@ -191,6 +197,174 @@ gbp_codeui_buffer_addin_unload (IdeBufferAddin *addin,
   g_clear_object (&self->diagnostics_manager);
 }
 
+static gboolean
+gbp_codeui_buffer_addin_cancel_format_on_save (gpointer user_data)
+{
+  GCancellable *cancellable = user_data;
+  g_assert (G_IS_CANCELLABLE (cancellable));
+  g_cancellable_cancel (cancellable);
+  return G_SOURCE_REMOVE;
+}
+
+static void
+gbp_codeui_buffer_addin_format_on_save_cb (GObject      *object,
+                                           GAsyncResult *result,
+                                           gpointer      user_data)
+{
+  IdeBuffer *buffer = (IdeBuffer *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_BUFFER (buffer));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  /* First check for cancellation */
+  if (ide_task_had_error (task) ||
+      ide_task_return_error_if_cancelled (task))
+    IDE_EXIT;
+
+  /* If we fail get text edits for formatting the selection, just
+   * bail and consider the buffer "settlted". Better to not touch
+   * anything if the LSP and/or formatter fail for us.
+   */
+  if (!ide_buffer_format_selection_finish (buffer, result, &error))
+    {
+      IdeObjectBox *box;
+
+      if ((box = ide_object_box_from_object (G_OBJECT (buffer))))
+        ide_object_warning (IDE_OBJECT (box),
+                            _("Failed to format while saving document: %s"),
+                            error->message);
+    }
+
+  ide_task_return_boolean (task, TRUE);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_codeui_buffer_addin_settle_async (IdeBufferAddin      *addin,
+                                      GCancellable        *cancellable,
+                                      GAsyncReadyCallback  callback,
+                                      gpointer             user_data)
+{
+  GbpCodeuiBufferAddin *self = (GbpCodeuiBufferAddin *)addin;
+  g_autoptr(IdeFormatterOptions) options = NULL;
+  g_autoptr(GCancellable) local_cancellable = NULL;
+  g_autoptr(IdeTask) task = NULL;
+  IdeFileSettings *file_settings;
+  IdeApplication *app;
+  GSource *cancel_format_source;
+  gboolean insert_spaces;
+  guint tab_width;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_CODEUI_BUFFER_ADDIN (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_assert (IDE_IS_BUFFER (self->buffer));
+
+  app = IDE_APPLICATION_DEFAULT;
+
+  /* We use our timeout cancellable instead of @cancellable so that
+   * we are in control of cancellation of formatting without affecting
+   * the cancellation of other save flows.
+   */
+  local_cancellable = g_cancellable_new ();
+
+  task = ide_task_new (self, local_cancellable, callback, user_data);
+  ide_task_set_source_tag (task, gbp_codeui_buffer_addin_settle_async);
+  ide_task_set_release_on_propagate (task, FALSE);
+  ide_task_set_return_on_cancel (task, TRUE);
+
+  /* Make sure the user enabled "format-on-save" */
+  if (!g_settings_get_boolean (app->settings, "format-on-save"))
+    {
+      ide_task_return_boolean (task, TRUE);
+      IDE_EXIT;
+    }
+
+  /* Make sure we even have a formatter to work with */
+  if (ide_buffer_get_formatter (self->buffer) == NULL)
+    {
+      ide_task_return_boolean (task, TRUE);
+      IDE_EXIT;
+    }
+
+  /* Can't do anything if there are no file settings to access, as
+   * we don't have access to the values from the view. We could
+   * eventually coordinate with a UI element on that, but probably
+   * not worth the layer violations.
+   */
+  if (!(file_settings = ide_buffer_get_file_settings (self->buffer)))
+    {
+      ide_task_return_boolean (task, TRUE);
+      IDE_EXIT;
+    }
+
+  /* Setup options for the formatter, right now that only includes
+   * a couple small things, tab size and if spaces should be used.
+   */
+  tab_width = ide_file_settings_get_tab_width (file_settings);
+  insert_spaces = ide_file_settings_get_indent_style (file_settings) == IDE_INDENT_STYLE_SPACES;
+  options = ide_formatter_options_new ();
+  ide_formatter_options_set_tab_width (options, tab_width);
+  ide_formatter_options_set_insert_spaces (options, insert_spaces);
+
+  /* LSPs can be finicky, and take a really long time to serve requests.
+   * If that happens, we don't want to block forever and be annoying.
+   * Instead, bail after a short timeout.
+   */
+  cancel_format_source = g_timeout_source_new (FORMAT_ON_SAVE_TIMEOUT_MSEC);
+  g_source_set_priority (cancel_format_source, G_PRIORITY_HIGH);
+  g_source_set_name (cancel_format_source, "[ide-buffer-format-on-save]");
+  g_source_set_callback (cancel_format_source,
+                         gbp_codeui_buffer_addin_cancel_format_on_save,
+                         g_object_ref (local_cancellable),
+                         g_object_unref);
+  g_source_attach (cancel_format_source, NULL);
+  g_source_unref (cancel_format_source);
+
+  /* Request the text edits to format. Changes will be applyed during
+   * the callback, after which point we can consider our plugin settled.
+   */
+  ide_buffer_format_selection_async (self->buffer,
+                                     options,
+                                     local_cancellable,
+                                     gbp_codeui_buffer_addin_format_on_save_cb,
+                                     g_steal_pointer (&task));
+
+  IDE_EXIT;
+}
+
+static gboolean
+gbp_codeui_buffer_addin_settle_finish (IdeBufferAddin  *addin,
+                                       GAsyncResult    *result,
+                                       GError         **error)
+{
+  g_autoptr(GError) local_error = NULL;
+  gboolean ret;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_CODEUI_BUFFER_ADDIN (addin));
+  g_assert (IDE_IS_TASK (result));
+
+  if (!(ret = ide_task_propagate_boolean (IDE_TASK (result), &local_error)))
+    {
+      if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        IDE_RETURN (TRUE);
+      g_propagate_error (error, g_steal_pointer (&local_error));
+    }
+
+  IDE_RETURN (ret);
+}
+
 static void
 buffer_addin_iface_init (IdeBufferAddinInterface *iface)
 {
@@ -200,6 +374,8 @@ buffer_addin_iface_init (IdeBufferAddinInterface *iface)
   iface->language_set = gbp_codeui_buffer_addin_language_set;
   iface->load = gbp_codeui_buffer_addin_load;
   iface->unload = gbp_codeui_buffer_addin_unload;
+  iface->settle_async = gbp_codeui_buffer_addin_settle_async;
+  iface->settle_finish = gbp_codeui_buffer_addin_settle_finish;
 }
 
 G_DEFINE_FINAL_TYPE_WITH_CODE (GbpCodeuiBufferAddin, gbp_codeui_buffer_addin, G_TYPE_OBJECT,


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