[gnome-builder/wip/gtk4-port: 1733/1774] libide/foundry: add IdeRunCommands




commit c3c921c80d4cefc981d440c537cc0e1b568e413f
Author: Christian Hergert <chergert redhat com>
Date:   Thu Jun 30 11:28:37 2022 -0700

    libide/foundry: add IdeRunCommands
    
    This is the start of an abstraction to simplify getting access to run
    commands and to make them persistent/shared across all the subsystems.
    
    It's still incomplete, but shows what the API will look like. I want to
    hook some things up so that we wait until the pipeline is ready before we
    populate providers.

 src/libide/foundry/ide-foundry-compat.c       |  17 ++
 src/libide/foundry/ide-run-command-provider.c |  32 +++
 src/libide/foundry/ide-run-command-provider.h |   3 +
 src/libide/foundry/ide-run-commands.c         | 317 ++++++++++++++++++++++++++
 src/libide/foundry/ide-run-commands.h         |  47 ++++
 src/libide/foundry/ide-run-manager.h          |   3 +-
 src/libide/foundry/libide-foundry.h           |   1 +
 src/libide/foundry/meson.build                |   2 +
 8 files changed, 421 insertions(+), 1 deletion(-)
---
diff --git a/src/libide/foundry/ide-foundry-compat.c b/src/libide/foundry/ide-foundry-compat.c
index b6a3746c7..b72fd2d9b 100644
--- a/src/libide/foundry/ide-foundry-compat.c
+++ b/src/libide/foundry/ide-foundry-compat.c
@@ -27,6 +27,7 @@
 #include "ide-device-manager.h"
 #include "ide-config-manager.h"
 #include "ide-foundry-compat.h"
+#include "ide-run-commands.h"
 #include "ide-run-manager.h"
 #include "ide-runtime-manager.h"
 #include "ide-test-manager.h"
@@ -216,3 +217,19 @@ ide_test_manager_from_context (IdeContext *context)
 
   return ensure_child_typed_borrowed (context, IDE_TYPE_TEST_MANAGER);
 }
+
+/**
+ * ide_run_commands_from_context:
+ * @context: an #IdeContext
+ *
+ * Gets the default #IdeRunCommands instance for @context.
+ *
+ * Returns: (transfer none): an #IdeRunCommands
+ */
+IdeRunCommands *
+ide_run_commands_from_context (IdeContext *context)
+{
+  g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
+
+  return ensure_child_typed_borrowed (context, IDE_TYPE_RUN_COMMANDS);
+}
diff --git a/src/libide/foundry/ide-run-command-provider.c b/src/libide/foundry/ide-run-command-provider.c
index cefee71ca..c685b1635 100644
--- a/src/libide/foundry/ide-run-command-provider.c
+++ b/src/libide/foundry/ide-run-command-provider.c
@@ -26,9 +26,24 @@
 
 G_DEFINE_INTERFACE (IdeRunCommandProvider, ide_run_command_provider, IDE_TYPE_OBJECT)
 
+enum {
+  INVALIDATED,
+  N_SIGNALS
+};
+
+static guint signals[N_SIGNALS];
+
 static void
 ide_run_command_provider_default_init (IdeRunCommandProviderInterface *iface)
 {
+  signals[INVALIDATED] =
+    g_signal_new ("invalidated",
+                  G_TYPE_FROM_INTERFACE (iface),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (IdeRunCommandProviderInterface, invalidated),
+                  NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE, 0);
 }
 
 void
@@ -62,3 +77,20 @@ ide_run_command_provider_list_commands_finish (IdeRunCommandProvider  *self,
 
   return IDE_RUN_COMMAND_PROVIDER_GET_IFACE (self)->list_commands_finish (self, result, error);
 }
+
+/**
+ * ide_run_command_provider_invalidate:
+ * @self: a #IdeRunCommandProvider
+ *
+ * Emits the #IdeRunCommandProvider::invalidated signal.
+ *
+ * This often results in #IdeRunCommands requesting a new set of results for
+ * the run command provider via ide_run_command_provider_list_commands_async().
+ */
+void
+ide_run_command_provider_invalidate (IdeRunCommandProvider *self)
+{
+  g_return_if_fail (IDE_IS_RUN_COMMAND_PROVIDER (self));
+
+  g_signal_emit (self, signals[INVALIDATED], 0);
+}
diff --git a/src/libide/foundry/ide-run-command-provider.h b/src/libide/foundry/ide-run-command-provider.h
index ff1345774..79f491704 100644
--- a/src/libide/foundry/ide-run-command-provider.h
+++ b/src/libide/foundry/ide-run-command-provider.h
@@ -37,6 +37,7 @@ struct _IdeRunCommandProviderInterface
 {
   GTypeInterface parent_iface;
 
+  void        (*invalidated)          (IdeRunCommandProvider  *self);
   void        (*list_commands_async)  (IdeRunCommandProvider  *self,
                                        GCancellable           *cancellable,
                                        GAsyncReadyCallback     callback,
@@ -46,6 +47,8 @@ struct _IdeRunCommandProviderInterface
                                        GError                **error);
 };
 
+IDE_AVAILABLE_IN_ALL
+void        ide_run_command_provider_invalidate           (IdeRunCommandProvider  *self);
 IDE_AVAILABLE_IN_ALL
 void        ide_run_command_provider_list_commands_async  (IdeRunCommandProvider  *self,
                                                            GCancellable           *cancellable,
diff --git a/src/libide/foundry/ide-run-commands.c b/src/libide/foundry/ide-run-commands.c
new file mode 100644
index 000000000..743fcc9de
--- /dev/null
+++ b/src/libide/foundry/ide-run-commands.c
@@ -0,0 +1,317 @@
+/* ide-run-commands.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-run-commands"
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#include <libide-plugins.h>
+#include <libide-threading.h>
+
+#include "ide-run-command.h"
+#include "ide-run-command-provider.h"
+#include "ide-run-commands.h"
+
+struct _IdeRunCommands
+{
+  IdeObject               parent_instance;
+  IdeExtensionSetAdapter *addins;
+  GListStore             *models;
+  GtkFlattenListModel    *flatten_model;
+  GHashTable             *provider_to_model;
+};
+
+static GType
+ide_run_commands_get_item_type (GListModel *model)
+{
+  return IDE_TYPE_RUN_COMMAND;
+}
+
+static guint
+ide_run_commands_get_n_items (GListModel *model)
+{
+  IdeRunCommands *self = IDE_RUN_COMMANDS (model);
+  return g_list_model_get_n_items (G_LIST_MODEL (self->flatten_model));
+}
+
+static gpointer
+ide_run_commands_get_item (GListModel *model,
+                           guint       position)
+{
+  IdeRunCommands *self = IDE_RUN_COMMANDS (model);
+  return g_list_model_get_item (G_LIST_MODEL (self->flatten_model), position);
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+  iface->get_item_type = ide_run_commands_get_item_type;
+  iface->get_n_items = ide_run_commands_get_n_items;
+  iface->get_item = ide_run_commands_get_item;
+}
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (IdeRunCommands, ide_run_commands, IDE_TYPE_OBJECT,
+                               G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+
+static void
+ide_run_commands_items_changed_cb (IdeRunCommands *self,
+                                   guint           position,
+                                   guint           removed,
+                                   guint           added,
+                                   GListModel     *model)
+{
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_RUN_COMMANDS (self));
+  g_assert (G_IS_LIST_MODEL (model));
+
+  g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
+
+  IDE_EXIT;
+}
+
+static void
+ide_run_commands_provider_invalidated_cb (IdeRunCommands        *self,
+                                          IdeRunCommandProvider *provider)
+{
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_RUN_COMMANDS (self));
+  g_assert (IDE_IS_RUN_COMMAND_PROVIDER (provider));
+
+  /* TODO: queue update for changes */
+
+  IDE_EXIT;
+}
+
+static void
+ide_run_commands_provider_added_cb (IdeExtensionSetAdapter *set,
+                                    PeasPluginInfo         *plugin_info,
+                                    PeasExtension          *exten,
+                                    gpointer                user_data)
+{
+  IdeRunCommandProvider *provider = (IdeRunCommandProvider *)exten;
+  IdeRunCommands *self = user_data;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_RUN_COMMAND_PROVIDER (provider));
+  g_assert (IDE_IS_RUN_COMMANDS (self));
+
+  g_signal_connect_object (provider,
+                           "invalidated",
+                           G_CALLBACK (ide_run_commands_provider_invalidated_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  ide_run_commands_provider_invalidated_cb (self, provider);
+
+  IDE_EXIT;
+}
+
+static void
+ide_run_commands_provider_removed_cb (IdeExtensionSetAdapter *set,
+                                      PeasPluginInfo         *plugin_info,
+                                      PeasExtension          *exten,
+                                      gpointer                user_data)
+{
+  IdeRunCommandProvider *provider = (IdeRunCommandProvider *)exten;
+  g_autoptr(IdeRunCommandProvider) stolen_key = NULL;
+  g_autoptr(GListModel) stolen_value = NULL;
+  IdeRunCommands *self = user_data;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_RUN_COMMAND_PROVIDER (provider));
+  g_assert (IDE_IS_RUN_COMMANDS (self));
+
+  if (g_hash_table_steal_extended (self->provider_to_model,
+                                   provider,
+                                   (gpointer *)&stolen_key,
+                                   (gpointer *)&stolen_value))
+    {
+      if (stolen_value != NULL)
+        {
+          guint position;
+
+          if (g_list_store_find (self->models, stolen_value, &position))
+            g_list_store_remove (self->models, position);
+        }
+    }
+
+  IDE_EXIT;
+}
+
+static void
+ide_run_commands_parent_set (IdeObject *object,
+                             IdeObject *parent)
+{
+  IdeRunCommands *self = (IdeRunCommands *)object;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_RUN_COMMANDS (self));
+  g_assert (!parent || IDE_IS_OBJECT (parent));
+
+  if (parent == NULL)
+    IDE_EXIT;
+
+  self->addins = ide_extension_set_adapter_new (IDE_OBJECT (self),
+                                                peas_engine_get_default (),
+                                                IDE_TYPE_RUN_COMMAND_PROVIDER,
+                                                NULL, NULL);
+  g_signal_connect (self->addins,
+                    "extension-added",
+                    G_CALLBACK (ide_run_commands_provider_added_cb),
+                    self);
+  g_signal_connect (self->addins,
+                    "extension-removed",
+                    G_CALLBACK (ide_run_commands_provider_removed_cb),
+                    self);
+  ide_extension_set_adapter_foreach (self->addins,
+                                     ide_run_commands_provider_added_cb,
+                                     self);
+
+  IDE_EXIT;
+}
+
+static void
+ide_run_commands_dispose (GObject *object)
+{
+  IdeRunCommands *self = (IdeRunCommands *)object;
+
+  ide_clear_and_destroy_object (&self->addins);
+  g_clear_object (&self->models);
+  g_clear_pointer (&self->provider_to_model, g_hash_table_unref);
+
+  G_OBJECT_CLASS (ide_run_commands_parent_class)->dispose (object);
+}
+
+static void
+ide_run_commands_class_init (IdeRunCommandsClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  IdeObjectClass *ide_object_class = IDE_OBJECT_CLASS (klass);
+
+  object_class->dispose = ide_run_commands_dispose;
+
+  ide_object_class->parent_set = ide_run_commands_parent_set;
+}
+
+static void
+ide_run_commands_init (IdeRunCommands *self)
+{
+  self->provider_to_model = g_hash_table_new_full (NULL, NULL, g_object_unref, g_object_unref);
+  self->models = g_list_store_new (G_TYPE_LIST_STORE);
+  self->flatten_model = gtk_flatten_list_model_new (g_object_ref (G_LIST_MODEL (self->models)));
+
+  g_signal_connect_object (self->flatten_model,
+                           "items-changed",
+                           G_CALLBACK (ide_run_commands_items_changed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+/**
+ * ide_run_commands_dup_by_id:
+ * @self: a #IdeRunCommands
+ * @id: (nullable): the id of the run command
+ *
+ * Finds an #IdeRunCommand by it's id.
+ *
+ * %NULL is allowed for @id out of convenience, but will return %NULL.
+ *
+ * Returns: (transfer full) (nullable): an #IdeRunCommand or %NULL
+ */
+IdeRunCommand *
+ide_run_commands_dup_by_id (IdeRunCommands *self,
+                            const char     *id)
+{
+  guint n_items;
+
+  IDE_ENTRY;
+
+  g_return_val_if_fail (IDE_IS_RUN_COMMANDS (self), NULL);
+
+  if (id == NULL)
+    IDE_RETURN (NULL);
+
+  n_items = g_list_model_get_n_items (G_LIST_MODEL (self));
+
+  IDE_TRACE_MSG ("Locating command by id %s in list of %u commands", id, n_items);
+
+  for (guint i = 0; i < n_items; i++)
+    {
+      g_autoptr(IdeRunCommand) run_command = g_list_model_get_item (G_LIST_MODEL (self), i);
+      const char *run_command_id = ide_run_command_get_id (run_command);
+
+      if (ide_str_equal0 (run_command_id, id))
+        IDE_RETURN (g_steal_pointer (&run_command));
+    }
+
+  IDE_RETURN (NULL);
+}
+
+static gboolean
+filter_run_command_by_kind (gpointer item,
+                            gpointer user_data)
+{
+  return ide_run_command_get_kind (item) == GPOINTER_TO_INT (user_data);
+}
+
+/**
+ * ide_run_commands_list_by_kind:
+ * @self: an #IdeRunCommands
+ * @kind: an #IdeRunCommandKind
+ *
+ * Creates a new #GListModel of #IdeRunCommand filtered by @kind
+ *
+ * The model will update as new commands are added or removed from @self.
+ *
+ * Returns: (transfer full): a #GListModel
+ */
+GListModel *
+ide_run_commands_list_by_kind (IdeRunCommands    *self,
+                               IdeRunCommandKind  kind)
+{
+  g_autoptr(GtkCustomFilter) filter = NULL;
+  g_autoptr(GtkFilterListModel) model = NULL;
+
+  g_return_val_if_fail (IDE_IS_RUN_COMMANDS (self), NULL);
+
+  filter = gtk_custom_filter_new (filter_run_command_by_kind,
+                                  GINT_TO_POINTER (kind),
+                                  NULL);
+  model = gtk_filter_list_model_new (g_object_ref (G_LIST_MODEL (self)),
+                                     GTK_FILTER (g_steal_pointer (&filter)));
+
+  return G_LIST_MODEL (g_steal_pointer (&model));
+}
diff --git a/src/libide/foundry/ide-run-commands.h b/src/libide/foundry/ide-run-commands.h
new file mode 100644
index 000000000..77ad0533b
--- /dev/null
+++ b/src/libide/foundry/ide-run-commands.h
@@ -0,0 +1,47 @@
+/* ide-run-commands.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_FOUNDRY_INSIDE) && !defined (IDE_FOUNDRY_COMPILATION)
+# error "Only <libide-foundry.h> can be included directly."
+#endif
+
+#include <libide-core.h>
+
+#include "ide-run-command.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_RUN_COMMANDS (ide_run_commands_get_type())
+
+IDE_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (IdeRunCommands, ide_run_commands, IDE, RUN_COMMANDS, IdeObject)
+
+IDE_AVAILABLE_IN_ALL
+IdeRunCommands *ide_run_commands_from_context (IdeContext        *context);
+IDE_AVAILABLE_IN_ALL
+GListModel     *ide_run_commands_list_by_kind (IdeRunCommands    *self,
+                                               IdeRunCommandKind  kind);
+IDE_AVAILABLE_IN_ALL
+IdeRunCommand  *ide_run_commands_dup_by_id    (IdeRunCommands    *self,
+                                               const char        *id);
+
+G_END_DECLS
diff --git a/src/libide/foundry/ide-run-manager.h b/src/libide/foundry/ide-run-manager.h
index 23f08cf72..d10ab5501 100644
--- a/src/libide/foundry/ide-run-manager.h
+++ b/src/libide/foundry/ide-run-manager.h
@@ -24,8 +24,9 @@
 # error "Only <libide-foundry.h> can be included directly."
 #endif
 
+#include <libpeas/peas.h>
+
 #include <libide-core.h>
-#include <libide-plugins.h>
 
 #include "ide-foundry-types.h"
 
diff --git a/src/libide/foundry/libide-foundry.h b/src/libide/foundry/libide-foundry.h
index af98a548e..362c7eeab 100644
--- a/src/libide/foundry/libide-foundry.h
+++ b/src/libide/foundry/libide-foundry.h
@@ -58,6 +58,7 @@ G_BEGIN_DECLS
 #include "ide-pipeline.h"
 #include "ide-run-command.h"
 #include "ide-run-command-provider.h"
+#include "ide-run-commands.h"
 #include "ide-run-context.h"
 #include "ide-run-manager.h"
 #include "ide-run-tool.h"
diff --git a/src/libide/foundry/meson.build b/src/libide/foundry/meson.build
index 8c3430f1f..e8155ec93 100644
--- a/src/libide/foundry/meson.build
+++ b/src/libide/foundry/meson.build
@@ -43,6 +43,7 @@ libide_foundry_public_headers = [
   'ide-pipeline.h',
   'ide-run-command.h',
   'ide-run-command-provider.h',
+  'ide-run-commands.h',
   'ide-run-context.h',
   'ide-run-manager.h',
   'ide-run-tool.h',
@@ -123,6 +124,7 @@ libide_foundry_public_sources = [
   'ide-pipeline.c',
   'ide-run-command.c',
   'ide-run-command-provider.c',
+  'ide-run-commands.c',
   'ide-run-context.c',
   'ide-run-manager.c',
   'ide-run-tool.c',


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