[gnome-builder/wip/chergert/shellcmd: 7/7] shellcmd: wip on a shellcmd command provider



commit 2d79735355e540b1acc711e5f8b808971edd1d56
Author: Christian Hergert <chergert redhat com>
Date:   Sun Aug 4 23:41:00 2019 -0700

    shellcmd: wip on a shellcmd command provider

 meson_options.txt                                  |   1 +
 src/plugins/meson.build                            |   1 +
 .../shellcmd/gbp-shellcmd-command-provider.c       | 134 ++++
 .../shellcmd/gbp-shellcmd-command-provider.h       |  31 +
 src/plugins/shellcmd/gbp-shellcmd-command.c        | 780 +++++++++++++++++++++
 src/plugins/shellcmd/gbp-shellcmd-command.h        |  58 ++
 src/plugins/shellcmd/meson.build                   |  29 +
 src/plugins/shellcmd/shellcmd-plugin.c             |  34 +
 src/plugins/shellcmd/shellcmd.gresource.xml        |   6 +
 src/plugins/shellcmd/shellcmd.plugin               |  10 +
 10 files changed, 1084 insertions(+)
---
diff --git a/meson_options.txt b/meson_options.txt
index 36fcfc969..8984e9f69 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -65,6 +65,7 @@ option('plugin_quick_highlight', type: 'boolean')
 option('plugin_retab', type: 'boolean')
 option('plugin_rls', type: 'boolean')
 option('plugin_rustup', type: 'boolean')
+option('plugin_shellcmd', type: 'boolean')
 option('plugin_spellcheck', type: 'boolean')
 option('plugin_sysprof', type: 'boolean')
 option('plugin_sysroot', type: 'boolean')
diff --git a/src/plugins/meson.build b/src/plugins/meson.build
index 17583bf9e..3731824aa 100644
--- a/src/plugins/meson.build
+++ b/src/plugins/meson.build
@@ -105,6 +105,7 @@ subdir('restore-cursor')
 subdir('retab')
 subdir('rls')
 subdir('rustup')
+subdir('shellcmd')
 subdir('snippets')
 subdir('spellcheck')
 subdir('sublime')
diff --git a/src/plugins/shellcmd/gbp-shellcmd-command-provider.c 
b/src/plugins/shellcmd/gbp-shellcmd-command-provider.c
new file mode 100644
index 000000000..0f6e9593d
--- /dev/null
+++ b/src/plugins/shellcmd/gbp-shellcmd-command-provider.c
@@ -0,0 +1,134 @@
+/* gbp-shellcmd-command-provider.c
+ *
+ * Copyright 2019 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 "gbp-shellcmd-command-provider"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <libide-threading.h>
+
+#include "gbp-shellcmd-command.h"
+#include "gbp-shellcmd-command-provider.h"
+
+struct _GbpShellcmdCommandProvider
+{
+  GObject parent_instance;
+};
+
+static void
+gbp_shellcmd_command_provider_query_async (IdeCommandProvider  *provider,
+                                           IdeWorkspace        *workspace,
+                                           const gchar         *typed_text,
+                                           GCancellable        *cancellable,
+                                           GAsyncReadyCallback  callback,
+                                           gpointer             user_data)
+{
+  GbpShellcmdCommandProvider *self = (GbpShellcmdCommandProvider *)provider;
+  g_autoptr(IdeTask) task = NULL;
+  g_autoptr(GPtrArray) ret = NULL;
+  g_autofree gchar *bash_c = NULL;
+  g_autofree gchar *quoted = NULL;
+
+  g_assert (GBP_IS_SHELLCMD_COMMAND_PROVIDER (provider));
+  g_assert (IDE_IS_WORKSPACE (workspace));
+  g_assert (typed_text != NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, gbp_shellcmd_command_provider_query_async);
+
+  quoted = g_shell_quote (typed_text);
+  bash_c = g_strdup_printf ("/bin/sh -c %s", quoted);
+
+  ret = g_ptr_array_new_with_free_func ((GDestroyNotify)ide_object_unref_and_destroy);
+
+  g_ptr_array_add (ret,
+                   g_object_new (GBP_TYPE_SHELLCMD_COMMAND,
+                                 "title", _("Run in host environment"),
+                                 "subtitle", typed_text,
+                                 "command", bash_c,
+                                 "locality", GBP_SHELLCMD_COMMAND_LOCALITY_HOST,
+                                 NULL));
+
+  g_ptr_array_add (ret,
+                   g_object_new (GBP_TYPE_SHELLCMD_COMMAND,
+                                 "title", _("Run in build environment"),
+                                 "subtitle", typed_text,
+                                 "command", bash_c,
+                                 "locality", GBP_SHELLCMD_COMMAND_LOCALITY_BUILD,
+                                 NULL));
+
+  g_ptr_array_add (ret,
+                   g_object_new (GBP_TYPE_SHELLCMD_COMMAND,
+                                 "title", _("Run in runtime environment"),
+                                 "subtitle", typed_text,
+                                 "command", bash_c,
+                                 "locality", GBP_SHELLCMD_COMMAND_LOCALITY_RUN,
+                                 NULL));
+
+  ide_task_return_pointer (task,
+                           g_steal_pointer (&ret),
+                           g_ptr_array_unref);
+}
+
+static GPtrArray *
+gbp_shellcmd_command_provider_query_finish (IdeCommandProvider  *provider,
+                                            GAsyncResult        *result,
+                                            GError             **error)
+{
+  g_autoptr(GPtrArray) ret = NULL;
+
+  g_assert (GBP_IS_SHELLCMD_COMMAND_PROVIDER (provider));
+  g_assert (IDE_IS_TASK (result));
+
+  ret = ide_task_propagate_pointer (IDE_TASK (result), error);
+  return IDE_PTR_ARRAY_STEAL_FULL (&ret);
+}
+
+static void
+command_provider_iface_init (IdeCommandProviderInterface *iface)
+{
+  iface->query_async = gbp_shellcmd_command_provider_query_async;
+  iface->query_finish = gbp_shellcmd_command_provider_query_finish;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GbpShellcmdCommandProvider, gbp_shellcmd_command_provider, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_COMMAND_PROVIDER,
+                                                command_provider_iface_init))
+
+static void
+gbp_shellcmd_command_provider_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (gbp_shellcmd_command_provider_parent_class)->finalize (object);
+}
+
+static void
+gbp_shellcmd_command_provider_class_init (GbpShellcmdCommandProviderClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gbp_shellcmd_command_provider_finalize;
+}
+
+static void
+gbp_shellcmd_command_provider_init (GbpShellcmdCommandProvider *self)
+{
+}
diff --git a/src/plugins/shellcmd/gbp-shellcmd-command-provider.h 
b/src/plugins/shellcmd/gbp-shellcmd-command-provider.h
new file mode 100644
index 000000000..b744937a1
--- /dev/null
+++ b/src/plugins/shellcmd/gbp-shellcmd-command-provider.h
@@ -0,0 +1,31 @@
+/* gbp-shellcmd-command-provider.h
+ *
+ * Copyright 2019 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
+
+#include <libide-gui.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_SHELLCMD_COMMAND_PROVIDER (gbp_shellcmd_command_provider_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpShellcmdCommandProvider, gbp_shellcmd_command_provider, GBP, 
SHELLCMD_COMMAND_PROVIDER, GObject)
+
+G_END_DECLS
diff --git a/src/plugins/shellcmd/gbp-shellcmd-command.c b/src/plugins/shellcmd/gbp-shellcmd-command.c
new file mode 100644
index 000000000..7d2e71857
--- /dev/null
+++ b/src/plugins/shellcmd/gbp-shellcmd-command.c
@@ -0,0 +1,780 @@
+/* gbp-shellcmd-command.c
+ *
+ * Copyright 2019 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 "gbp-shellcmd-command"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <libide-gui.h>
+#include <libide-editor.h>
+#include <libide-terminal.h>
+#include <libide-threading.h>
+
+#include "ide-gui-private.h"
+
+#include "gbp-shellcmd-command.h"
+#include "gbp-shellcmd-enums.h"
+
+struct _GbpShellcmdCommand
+{
+  IdeObject                   parent_instance;
+  GbpShellcmdCommandLocality  locality;
+  gchar                      *shortcut;
+  gchar                      *subtitle;
+  gchar                      *title;
+  gchar                      *command;
+  gchar                      *cwd;
+  IdeEnvironment             *environment;
+};
+
+enum {
+  PROP_0,
+  PROP_COMMAND,
+  PROP_CWD,
+  PROP_ENVIRONMENT,
+  PROP_LOCALITY,
+  PROP_SHORTCUT,
+  PROP_SUBTITLE,
+  PROP_TITLE,
+  N_PROPS
+};
+
+static void command_iface_init (IdeCommandInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GbpShellcmdCommand, gbp_shellcmd_command, IDE_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_COMMAND, command_iface_init))
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gbp_shellcmd_command_finalize (GObject *object)
+{
+  GbpShellcmdCommand *self = (GbpShellcmdCommand *)object;
+
+  g_clear_pointer (&self->shortcut, g_free);
+  g_clear_pointer (&self->title, g_free);
+  g_clear_pointer (&self->command, g_free);
+  g_clear_pointer (&self->cwd, g_free);
+  g_clear_object (&self->environment);
+
+  G_OBJECT_CLASS (gbp_shellcmd_command_parent_class)->finalize (object);
+}
+
+static void
+gbp_shellcmd_command_get_property (GObject    *object,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+  GbpShellcmdCommand *self = GBP_SHELLCMD_COMMAND (object);
+
+  switch (prop_id)
+    {
+    case PROP_COMMAND:
+      g_value_set_string (value, gbp_shellcmd_command_get_command (self));
+      break;
+
+    case PROP_ENVIRONMENT:
+      g_value_set_object (value, gbp_shellcmd_command_get_environment (self));
+      break;
+
+    case PROP_CWD:
+      g_value_set_string (value, gbp_shellcmd_command_get_cwd (self));
+      break;
+
+    case PROP_LOCALITY:
+      g_value_set_enum (value, gbp_shellcmd_command_get_locality (self));
+      break;
+
+    case PROP_SHORTCUT:
+      g_value_set_string (value, gbp_shellcmd_command_get_shortcut (self));
+      break;
+
+    case PROP_SUBTITLE:
+      g_value_take_string (value, ide_command_get_subtitle (IDE_COMMAND (self)));
+      break;
+
+    case PROP_TITLE:
+      g_value_take_string (value, ide_command_get_title (IDE_COMMAND (self)));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_shellcmd_command_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  GbpShellcmdCommand *self = GBP_SHELLCMD_COMMAND (object);
+
+  switch (prop_id)
+    {
+    case PROP_COMMAND:
+      gbp_shellcmd_command_set_command (self, g_value_get_string (value));
+      break;
+
+    case PROP_CWD:
+      gbp_shellcmd_command_set_cwd (self, g_value_get_string (value));
+      break;
+
+    case PROP_LOCALITY:
+      gbp_shellcmd_command_set_locality (self, g_value_get_enum (value));
+      break;
+
+    case PROP_SHORTCUT:
+      gbp_shellcmd_command_set_shortcut (self, g_value_get_string (value));
+      break;
+
+    case PROP_SUBTITLE:
+      gbp_shellcmd_command_set_subtitle (self, g_value_get_string (value));
+      break;
+
+    case PROP_TITLE:
+      gbp_shellcmd_command_set_title (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_shellcmd_command_class_init (GbpShellcmdCommandClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gbp_shellcmd_command_finalize;
+  object_class->get_property = gbp_shellcmd_command_get_property;
+  object_class->set_property = gbp_shellcmd_command_set_property;
+
+  properties [PROP_COMMAND] =
+    g_param_spec_string ("command",
+                         "Command",
+                         "The shell command to execute",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_CWD] =
+    g_param_spec_string ("cwd",
+                         "CWD",
+                         "The working directory for the command",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_ENVIRONMENT] =
+    g_param_spec_object ("environment",
+                         "Environment",
+                         "The environment variables for the command",
+                         IDE_TYPE_ENVIRONMENT,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_LOCALITY] =
+    g_param_spec_enum ("locality",
+                       "Locality",
+                       "Where the command should be executed",
+                       GBP_TYPE_SHELLCMD_COMMAND_LOCALITY,
+                       GBP_SHELLCMD_COMMAND_LOCALITY_HOST,
+                       (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_SHORTCUT] =
+    g_param_spec_string ("shortcut",
+                         "Shortcut",
+                         "The shortcut to use to activate the command",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_SUBTITLE] =
+    g_param_spec_string ("subtitle",
+                         "Subtitle",
+                         "The subtitle of the command for display purposes",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TITLE] =
+    g_param_spec_string ("title",
+                         "Title",
+                         "The title of the command for display purposes",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+  
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gbp_shellcmd_command_init (GbpShellcmdCommand *self)
+{
+}
+
+const gchar *
+gbp_shellcmd_command_get_cwd (GbpShellcmdCommand *self)
+{
+  g_return_val_if_fail (GBP_IS_SHELLCMD_COMMAND (self), NULL);
+
+  return self->cwd;
+}
+
+void
+gbp_shellcmd_command_set_cwd (GbpShellcmdCommand *self,
+                              const gchar        *cwd)
+{
+  g_return_if_fail (GBP_IS_SHELLCMD_COMMAND (self));
+
+  if (!ide_str_equal0 (cwd, self->cwd))
+    {
+      g_free (self->cwd);
+      self->cwd = g_strdup (cwd);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CWD]);
+    }
+}
+
+static gchar *
+gbp_shellcmd_command_get_title (IdeCommand *command)
+{
+  GbpShellcmdCommand *self = (GbpShellcmdCommand *)command;
+
+  g_assert (GBP_IS_SHELLCMD_COMMAND (self));
+
+  if (self->title == NULL)
+    return g_strdup (_("Shell command"));
+  else
+    return g_strdup (self->title);
+}
+
+static gchar *
+gbp_shellcmd_command_get_subtitle (IdeCommand *command)
+{
+  GbpShellcmdCommand *self = (GbpShellcmdCommand *)command;
+
+  g_assert (GBP_IS_SHELLCMD_COMMAND (self));
+
+  if (self->subtitle)
+    return g_strdup (self->subtitle);
+  else
+    return g_strdup (self->command);
+}
+
+static void
+gbp_shellcmd_command_wait_check_cb (GObject      *object,
+                                    GAsyncResult *result,
+                                    gpointer      user_data)
+{
+  IdeSubprocess *subprocess = (IdeSubprocess *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IDE_IS_SUBPROCESS (subprocess));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  if (!ide_subprocess_wait_check_finish (subprocess, result, &error))
+    ide_task_return_error (task, g_steal_pointer (&error));
+  else
+    ide_task_return_boolean (task, TRUE);
+}
+
+static void
+gbp_shellcmd_command_apply (GbpShellcmdCommand    *self,
+                            IdeSubprocessLauncher *launcher,
+                            GFile                 *relative_to)
+{
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GFile) cwd = NULL;
+
+  g_assert (GBP_IS_SHELLCMD_COMMAND (self));
+  g_assert (IDE_IS_SUBPROCESS_LAUNCHER (launcher));
+  g_assert (G_IS_FILE (relative_to));
+
+  if (self->cwd != NULL)
+    {
+      if (g_path_is_absolute (self->cwd))
+        cwd = g_file_new_for_path (self->cwd);
+      else
+        cwd = g_file_get_child (relative_to, self->cwd);
+    }
+  else
+    {
+      cwd = g_object_ref (relative_to);
+    }
+
+  ide_subprocess_launcher_set_cwd (launcher, g_file_peek_path (cwd));
+
+  if (self->environment != NULL)
+    {
+      g_auto(GStrv) env = ide_environment_get_environ (self->environment);
+
+      if (env != NULL)
+        {
+          for (guint i = 0; env[i]; i++)
+            {
+              g_autofree gchar *key = NULL;
+              g_autofree gchar *val = NULL;
+
+              if (ide_environ_parse (env[i], &key, &val))
+                ide_subprocess_launcher_setenv (launcher, key, val, TRUE);
+            }
+        }
+    }
+}
+
+static void
+gbp_shellcmd_command_run_host (GbpShellcmdCommand  *self,
+                               gchar              **argv,
+                               IdeTask             *task)
+{
+  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
+  g_autoptr(IdeTerminalLauncher) tlauncher = NULL;
+  g_autoptr(IdeSubprocess) subprocess = NULL;
+  g_autoptr(IdeContext) context = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GFile) workdir = NULL;
+  IdeWorkspace *workspace;
+  IdeWorkbench *workbench;
+  IdeSurface *surface;
+  IdePage *page;
+
+  g_assert (GBP_IS_SHELLCMD_COMMAND (self));
+  g_assert (argv != NULL);
+  g_assert (IDE_IS_TASK (task));
+
+  context = ide_object_ref_context (IDE_OBJECT (self));
+  workdir = ide_context_ref_workdir (context);
+
+  if (!(workbench = _ide_workbench_from_context (context)) ||
+      (!(workspace = ide_workbench_get_workspace_by_type (workbench, IDE_TYPE_PRIMARY_WORKSPACE)) &&
+       !(workspace = ide_workbench_get_workspace_by_type (workbench, IDE_TYPE_EDITOR_WORKSPACE)) &&
+       !(workspace = ide_workbench_get_workspace_by_type (workbench, IDE_TYPE_TERMINAL_WORKSPACE))) ||
+      (!(surface = ide_workspace_get_surface_by_name (workspace, "editor")) &&
+       !(surface = ide_workspace_get_surface_by_name (workspace, "terminal"))))
+    {
+      ide_task_return_new_error (task,
+                                 G_IO_ERROR,
+                                 G_IO_ERROR_NOT_FOUND,
+                                 "Failed to locate a workspace for the terminal page");
+      return;
+    }
+
+  launcher = ide_subprocess_launcher_new (0);
+
+  ide_subprocess_launcher_push_args (launcher, (const gchar * const *)argv);
+  ide_subprocess_launcher_set_run_on_host (launcher, TRUE);
+  ide_subprocess_launcher_set_clear_env (launcher, FALSE);
+
+  gbp_shellcmd_command_apply (self, launcher, workdir);
+
+  tlauncher = ide_terminal_launcher_new_for_launcher (launcher);
+  page = g_object_new (IDE_TYPE_TERMINAL_PAGE,
+                       "close-on-exit", FALSE,
+                       "launcher", tlauncher,
+                       "manage-spawn", TRUE,
+                       "respawn-on-exit", FALSE,
+                       "visible", TRUE,
+                       NULL);
+  gtk_container_add (GTK_CONTAINER (surface), GTK_WIDGET (page));
+
+  ide_task_return_boolean (task, TRUE);
+}
+
+static void
+gbp_shellcmd_command_run_app (GbpShellcmdCommand  *self,
+                              gchar              **argv,
+                              IdeTask             *task)
+{
+  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
+  g_autoptr(IdeTerminalLauncher) tlauncher = NULL;
+  g_autoptr(IdeContext) context = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GFile) workdir = NULL;
+  IdeWorkspace *workspace;
+  IdeWorkbench *workbench;
+  IdeSurface *surface;
+  IdePage *page;
+
+  g_assert (GBP_IS_SHELLCMD_COMMAND (self));
+  g_assert (argv != NULL);
+  g_assert (IDE_IS_TASK (task));
+
+  context = ide_object_ref_context (IDE_OBJECT (self));
+  workdir = ide_context_ref_workdir (context);
+
+  if (!(workbench = _ide_workbench_from_context (context)) ||
+      (!(workspace = ide_workbench_get_workspace_by_type (workbench, IDE_TYPE_PRIMARY_WORKSPACE)) &&
+       !(workspace = ide_workbench_get_workspace_by_type (workbench, IDE_TYPE_EDITOR_WORKSPACE)) &&
+       !(workspace = ide_workbench_get_workspace_by_type (workbench, IDE_TYPE_TERMINAL_WORKSPACE))) ||
+      (!(surface = ide_workspace_get_surface_by_name (workspace, "editor")) &&
+       !(surface = ide_workspace_get_surface_by_name (workspace, "terminal"))))
+    {
+      ide_task_return_new_error (task,
+                                 G_IO_ERROR,
+                                 G_IO_ERROR_NOT_FOUND,
+                                 "Failed to locate a workspace for the terminal page");
+      return;
+    }
+
+  launcher = ide_subprocess_launcher_new (0);
+
+  ide_subprocess_launcher_push_args (launcher, (const gchar * const *)argv);
+  ide_subprocess_launcher_set_run_on_host (launcher, FALSE);
+  ide_subprocess_launcher_set_clear_env (launcher, FALSE);
+
+  gbp_shellcmd_command_apply (self, launcher, workdir);
+
+  tlauncher = ide_terminal_launcher_new_for_launcher (launcher);
+  page = g_object_new (IDE_TYPE_TERMINAL_PAGE,
+                       "close-on-exit", FALSE,
+                       "launcher", tlauncher,
+                       "manage-spawn", TRUE,
+                       "respawn-on-exit", FALSE,
+                       "visible", TRUE,
+                       NULL);
+  gtk_container_add (GTK_CONTAINER (surface), GTK_WIDGET (page));
+
+  ide_task_return_boolean (task, TRUE);
+}
+
+static void
+gbp_shellcmd_command_run_runner (GbpShellcmdCommand  *self,
+                                 gchar              **argv,
+                                 IdeTask             *task)
+{
+  g_autoptr(IdeTerminalLauncher) launcher = NULL;
+  g_autoptr(IdeContext) context = NULL;
+  g_autofree gchar *cwd = NULL;
+  IdeBuildManager *build_manager;
+  IdeWorkspace *workspace;
+  IdeWorkbench *workbench;
+  IdePipeline *pipeline;
+  IdeRuntime *runtime;
+  IdeSurface *surface;
+  IdePage *page;
+
+  g_assert (GBP_IS_SHELLCMD_COMMAND (self));
+  g_assert (argv != NULL);
+  g_assert (IDE_IS_TASK (task));
+
+  if (!(context = ide_object_ref_context (IDE_OBJECT (self))) ||
+      !(build_manager = ide_build_manager_from_context (context)) ||
+      !(pipeline = ide_build_manager_get_pipeline (build_manager)) ||
+      !(runtime = ide_pipeline_get_runtime (pipeline)))
+    {
+      ide_task_return_new_error (task,
+                                 G_IO_ERROR,
+                                 G_IO_ERROR_NOT_INITIALIZED,
+                                 _("Cannot spawn terminal in runtime environment because build pipeline is 
not initialized"));
+      return;
+    }
+
+  if (!(workbench = _ide_workbench_from_context (context)) ||
+      (!(workspace = ide_workbench_get_workspace_by_type (workbench, IDE_TYPE_PRIMARY_WORKSPACE)) &&
+       !(workspace = ide_workbench_get_workspace_by_type (workbench, IDE_TYPE_EDITOR_WORKSPACE)) &&
+       !(workspace = ide_workbench_get_workspace_by_type (workbench, IDE_TYPE_TERMINAL_WORKSPACE))) ||
+      (!(surface = ide_workspace_get_surface_by_name (workspace, "editor")) &&
+       !(surface = ide_workspace_get_surface_by_name (workspace, "terminal"))))
+    {
+      ide_task_return_new_error (task,
+                                 G_IO_ERROR,
+                                 G_IO_ERROR_NOT_FOUND,
+                                 "Failed to locate a workspace for the terminal page");
+      return;
+    }
+
+  launcher = ide_terminal_launcher_new_for_runner (runtime);
+  ide_terminal_launcher_set_shell (launcher, argv[0]);
+  ide_terminal_launcher_set_args (launcher, (const gchar * const *)&argv[1]);
+
+  if (self->cwd != NULL)
+    {
+      if (g_path_is_absolute (self->cwd))
+        cwd = g_strdup (self->cwd);
+      else
+        ide_pipeline_build_builddir_path (pipeline, self->cwd, NULL);
+    }
+
+  if (cwd != NULL)
+    ide_terminal_launcher_set_cwd (launcher, cwd);
+  else
+    ide_terminal_launcher_set_cwd (launcher,
+                                   ide_pipeline_get_builddir (pipeline));
+
+  page = g_object_new (IDE_TYPE_TERMINAL_PAGE,
+                       "close-on-exit", FALSE,
+                       "launcher", launcher,
+                       "manage-spawn", TRUE,
+                       "respawn-on-exit", FALSE,
+                       "visible", TRUE,
+                       NULL);
+  gtk_container_add (GTK_CONTAINER (surface), GTK_WIDGET (page));
+
+  ide_task_return_boolean (task, TRUE);
+}
+
+static void
+gbp_shellcmd_command_run_build (GbpShellcmdCommand  *self,
+                                gchar              **argv,
+                                IdeTask             *task)
+{
+  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
+  g_autoptr(IdeSubprocess) subprocess = NULL;
+  g_autoptr(IdeContext) context = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GFile) builddir = NULL;
+  IdeBuildManager *build_manager;
+  GCancellable *cancellable;
+  IdeWorkbench *workbench;
+  IdeWorkspace *workspace;
+  IdePipeline *pipeline;
+
+  g_assert (GBP_IS_SHELLCMD_COMMAND (self));
+  g_assert (argv != NULL);
+  g_assert (IDE_IS_TASK (task));
+
+  context = ide_object_ref_context (IDE_OBJECT (self));
+  workbench = _ide_workbench_from_context (context);
+  workspace = ide_workbench_get_current_workspace (workbench);
+  build_manager = ide_build_manager_from_context (context);
+  pipeline = ide_build_manager_get_pipeline (build_manager);
+
+  if (pipeline == NULL)
+    {
+      ide_task_return_new_error (task,
+                                 G_IO_ERROR,
+                                 G_IO_ERROR_NOT_INITIALIZED,
+                                 _("Cannot spawn process because build pipeline is not yet available"));
+      return;
+    }
+
+  builddir = g_file_new_for_path (ide_pipeline_get_builddir (pipeline));
+  launcher = ide_pipeline_create_launcher (pipeline, &error);
+
+  if (launcher == NULL)
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  ide_pipeline_attach_pty (pipeline, launcher);
+  ide_subprocess_launcher_push_args (launcher, (const gchar * const *)argv);
+
+  if (G_IS_ACTION_GROUP (workspace) &&
+      g_action_group_has_action (G_ACTION_GROUP (workspace), "view-output"))
+    dzl_gtk_widget_action (GTK_WIDGET (workspace), "win", "view-output", NULL);
+
+  gbp_shellcmd_command_apply (self, launcher, builddir);
+
+  cancellable = ide_task_get_cancellable (task);
+  subprocess = ide_subprocess_launcher_spawn (launcher, cancellable, &error);
+
+  if (subprocess == NULL)
+    ide_task_return_error (task, g_steal_pointer (&error));
+  else
+    ide_subprocess_wait_check_async (subprocess,
+                                     cancellable,
+                                     gbp_shellcmd_command_wait_check_cb,
+                                     g_object_ref (task));
+}
+
+static void
+gbp_shellcmd_command_run_async (IdeCommand          *command,
+                                GCancellable        *cancellable,
+                                GAsyncReadyCallback  callback,
+                                gpointer             user_data)
+{
+  GbpShellcmdCommand *self = (GbpShellcmdCommand *)command;
+  g_autoptr(IdeTask) task = NULL;
+  g_autoptr(GError) error = NULL;
+  g_auto(GStrv) argv = NULL;
+  gint argc = 0;
+
+  g_assert (GBP_IS_SHELLCMD_COMMAND (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, gbp_shellcmd_command_run_async);
+
+  if (self->command == NULL)
+    {
+      ide_task_return_new_error (task,
+                                 G_IO_ERROR,
+                                 G_IO_ERROR_FAILED,
+                                 "No command to execute");
+      return;
+    }
+
+  if (!g_shell_parse_argv (self->command, &argc, &argv, &error))
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  switch (self->locality)
+    {
+    case GBP_SHELLCMD_COMMAND_LOCALITY_HOST:
+      gbp_shellcmd_command_run_host (self, argv, task);
+      break;
+
+    case GBP_SHELLCMD_COMMAND_LOCALITY_APP:
+      gbp_shellcmd_command_run_app (self, argv, task);
+      break;
+
+    case GBP_SHELLCMD_COMMAND_LOCALITY_BUILD:
+      gbp_shellcmd_command_run_build (self, argv, task);
+      break;
+
+    case GBP_SHELLCMD_COMMAND_LOCALITY_RUN:
+      gbp_shellcmd_command_run_runner (self, argv, task);
+      break;
+
+    default:
+      ide_task_return_new_error (task,
+                                 G_IO_ERROR,
+                                 G_IO_ERROR_FAILED,
+                                 "Unknown command locality");
+      return;
+    }
+}
+
+static gboolean
+gbp_shellcmd_command_run_finish (IdeCommand    *command,
+                                 GAsyncResult  *result,
+                                 GError       **error)
+{
+  g_assert (GBP_IS_SHELLCMD_COMMAND (command));
+  g_assert (IDE_IS_TASK (result));
+
+  return ide_task_propagate_boolean (IDE_TASK (result), error);
+}
+
+static void
+command_iface_init (IdeCommandInterface *iface)
+{
+  iface->get_title = gbp_shellcmd_command_get_title;
+  iface->get_subtitle = gbp_shellcmd_command_get_subtitle;
+  iface->run_async = gbp_shellcmd_command_run_async;
+  iface->run_finish = gbp_shellcmd_command_run_finish;
+}
+
+GbpShellcmdCommandLocality
+gbp_shellcmd_command_get_locality (GbpShellcmdCommand *self)
+{
+  g_return_val_if_fail (GBP_IS_SHELLCMD_COMMAND (self), 0);
+
+  return self->locality;
+}
+
+void
+gbp_shellcmd_command_set_locality (GbpShellcmdCommand         *self,
+                                   GbpShellcmdCommandLocality  locality)
+{
+  g_return_if_fail (GBP_IS_SHELLCMD_COMMAND (self));
+
+  if (self->locality != locality)
+    {
+      self->locality = locality;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LOCALITY]);
+    }
+
+}
+
+const gchar *
+gbp_shellcmd_command_get_command (GbpShellcmdCommand *self)
+{
+  g_return_val_if_fail (GBP_IS_SHELLCMD_COMMAND (self), NULL);
+
+  return self->command;
+}
+
+void
+gbp_shellcmd_command_set_command (GbpShellcmdCommand *self,
+                                  const gchar        *command)
+{
+  g_return_if_fail (GBP_IS_SHELLCMD_COMMAND (self));
+
+  if (!ide_str_equal0 (command, self->command))
+    {
+      g_free (self->command);
+      self->command = g_strdup (command);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_COMMAND]);
+    }
+}
+
+IdeEnvironment *
+gbp_shellcmd_command_get_environment (GbpShellcmdCommand *self)
+{
+  g_return_val_if_fail (GBP_IS_SHELLCMD_COMMAND (self), NULL);
+
+  if (self->environment == NULL)
+    self->environment = ide_environment_new ();
+
+  return self->environment;
+}
+
+const gchar *
+gbp_shellcmd_command_get_shortcut (GbpShellcmdCommand *self)
+{
+  g_return_val_if_fail (GBP_IS_SHELLCMD_COMMAND (self), NULL);
+
+  return self->shortcut;
+}
+
+void
+gbp_shellcmd_command_set_shortcut (GbpShellcmdCommand *self,
+                                   const gchar        *shortcut)
+{
+  g_return_if_fail (GBP_IS_SHELLCMD_COMMAND (self));
+
+  if (!ide_str_equal0 (shortcut, self->shortcut))
+    {
+      g_free (self->shortcut);
+      self->shortcut = g_strdup (shortcut);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHORTCUT]);
+    }
+}
+
+void
+gbp_shellcmd_command_set_title (GbpShellcmdCommand *self,
+                                const gchar        *title)
+{
+  g_return_if_fail (GBP_IS_SHELLCMD_COMMAND (self));
+
+  if (!ide_str_equal0 (title, self->title))
+    {
+      g_free (self->title);
+      self->title = g_strdup (title);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]);
+    }
+}
+
+void
+gbp_shellcmd_command_set_subtitle (GbpShellcmdCommand *self,
+                                   const gchar        *subtitle)
+{
+  g_return_if_fail (GBP_IS_SHELLCMD_COMMAND (self));
+
+  if (!ide_str_equal0 (subtitle, self->subtitle))
+    {
+      g_free (self->subtitle);
+      self->subtitle = g_strdup (subtitle);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SUBTITLE]);
+    }
+}
diff --git a/src/plugins/shellcmd/gbp-shellcmd-command.h b/src/plugins/shellcmd/gbp-shellcmd-command.h
new file mode 100644
index 000000000..90c600aca
--- /dev/null
+++ b/src/plugins/shellcmd/gbp-shellcmd-command.h
@@ -0,0 +1,58 @@
+/* gbp-shellcmd-command.h
+ *
+ * Copyright 2019 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
+
+#include <libide-gui.h>
+#include <libide-threading.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_SHELLCMD_COMMAND (gbp_shellcmd_command_get_type())
+
+typedef enum
+{
+  GBP_SHELLCMD_COMMAND_LOCALITY_HOST,
+  GBP_SHELLCMD_COMMAND_LOCALITY_APP,
+  GBP_SHELLCMD_COMMAND_LOCALITY_BUILD,
+  GBP_SHELLCMD_COMMAND_LOCALITY_RUN,
+} GbpShellcmdCommandLocality;
+
+G_DECLARE_FINAL_TYPE (GbpShellcmdCommand, gbp_shellcmd_command, GBP, SHELLCMD_COMMAND, IdeObject)
+
+GbpShellcmdCommandLocality  gbp_shellcmd_command_get_locality    (GbpShellcmdCommand         *self);
+void                        gbp_shellcmd_command_set_locality    (GbpShellcmdCommand         *self,
+                                                                  GbpShellcmdCommandLocality  locality);
+const gchar                *gbp_shellcmd_command_get_command     (GbpShellcmdCommand         *self);
+void                        gbp_shellcmd_command_set_command     (GbpShellcmdCommand         *self,
+                                                                  const gchar                *command);
+const gchar                *gbp_shellcmd_command_get_cwd         (GbpShellcmdCommand         *self);
+void                        gbp_shellcmd_command_set_cwd         (GbpShellcmdCommand         *self,
+                                                                  const gchar                *cwd);
+IdeEnvironment             *gbp_shellcmd_command_get_environment (GbpShellcmdCommand         *self);
+const gchar                *gbp_shellcmd_command_get_shortcut    (GbpShellcmdCommand         *self);
+void                        gbp_shellcmd_command_set_shortcut    (GbpShellcmdCommand         *self,
+                                                                  const gchar                *shortcut);
+void                        gbp_shellcmd_command_set_subtitle    (GbpShellcmdCommand         *self,
+                                                                  const gchar                *subtitle);
+void                        gbp_shellcmd_command_set_title       (GbpShellcmdCommand         *self,
+                                                                  const gchar                *title);
+
+G_END_DECLS
diff --git a/src/plugins/shellcmd/meson.build b/src/plugins/shellcmd/meson.build
new file mode 100644
index 000000000..b8d1cf63d
--- /dev/null
+++ b/src/plugins/shellcmd/meson.build
@@ -0,0 +1,29 @@
+if get_option('plugin_shellcmd')
+
+plugins_sources += files([
+  'shellcmd-plugin.c',
+  'gbp-shellcmd-command.c',
+  'gbp-shellcmd-command-provider.c',
+])
+
+plugin_shellcmd_enum_headers = [
+  'gbp-shellcmd-command.h',
+]
+
+plugin_shellcmd_enums = gnome.mkenums_simple('gbp-shellcmd-enums',
+     body_prefix: '#include "config.h"',
+   header_prefix: '#include <libide-gui.h>',
+         sources: plugin_shellcmd_enum_headers,
+)
+
+plugin_shellcmd_resources = gnome.compile_resources(
+  'shellcmd-resources',
+  'shellcmd.gresource.xml',
+  c_name: 'gbp_shellcmd',
+)
+
+plugins_sources += plugin_shellcmd_enums
+plugins_sources += plugin_shellcmd_resources
+plugins_include_directories += [include_directories('.')]
+
+endif
diff --git a/src/plugins/shellcmd/shellcmd-plugin.c b/src/plugins/shellcmd/shellcmd-plugin.c
new file mode 100644
index 000000000..fe4f15a28
--- /dev/null
+++ b/src/plugins/shellcmd/shellcmd-plugin.c
@@ -0,0 +1,34 @@
+/* shellcmd-plugin.c
+ *
+ * Copyright 2019 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
+ */
+
+#include "config.h"
+
+#include <libide-gui.h>
+#include <libpeas/peas.h>
+
+#include "gbp-shellcmd-command-provider.h"
+
+_IDE_EXTERN void
+_gbp_shellcmd_register_types (PeasObjectModule *module)
+{
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_COMMAND_PROVIDER,
+                                              GBP_TYPE_SHELLCMD_COMMAND_PROVIDER);
+}
diff --git a/src/plugins/shellcmd/shellcmd.gresource.xml b/src/plugins/shellcmd/shellcmd.gresource.xml
new file mode 100644
index 000000000..4af547c31
--- /dev/null
+++ b/src/plugins/shellcmd/shellcmd.gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/plugins/shellcmd">
+    <file>shellcmd.plugin</file>
+  </gresource>
+</gresources>
diff --git a/src/plugins/shellcmd/shellcmd.plugin b/src/plugins/shellcmd/shellcmd.plugin
new file mode 100644
index 000000000..bfd130ff7
--- /dev/null
+++ b/src/plugins/shellcmd/shellcmd.plugin
@@ -0,0 +1,10 @@
+[Plugin]
+Authors=Christian Hergert <christian hergert me>
+Builtin=true
+Copyright=Copyright © 2019 Christian Hergert
+Depends=editor;
+Description=Run shell commands from your project
+Embedded=_gbp_shellcmd_register_types
+Hidden=true
+Module=shellcmd
+Name=Shell Commands



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