[gnome-builder: 69/139] command-bar: port command bar to libide-editor



commit c42c8ec7dfb0123c3c5612ef0850490a227fc2c2
Author: Christian Hergert <chergert redhat com>
Date:   Wed Jan 9 17:11:13 2019 -0800

    command-bar: port command bar to libide-editor
    
    This also implements a new design as part of this work to make the
    command bar front-and-center when used. We have lost a little bit of
    features in the process (naming the tab completion still needs work as
    well as dyanmic data such as %s/foo/bar).
    
    We expect more changes in this plugin in the future as design work
    fleshes out more of a command bar experience.

 src/plugins/command-bar/command-bar-plugin.c       |   40 +
 src/plugins/command-bar/command-bar.gresource.xml  |    8 +
 src/plugins/command-bar/command-bar.plugin         |   12 +-
 src/plugins/command-bar/gb-command-bar.c           |  774 ---------
 .../command-bar/gb-command-bar.gresource.xml       |   11 -
 src/plugins/command-bar/gb-command-bar.ui          |   87 -
 .../command-bar/gb-command-gaction-provider.c      |  475 ------
 src/plugins/command-bar/gb-command-gaction.c       |  210 ---
 src/plugins/command-bar/gb-command-manager.c       |  164 --
 src/plugins/command-bar/gb-command-manager.h       |   42 -
 src/plugins/command-bar/gb-command-provider.c      |  427 -----
 src/plugins/command-bar/gb-command-provider.h      |   57 -
 src/plugins/command-bar/gb-command-result.c        |  264 ----
 src/plugins/command-bar/gb-command-result.h        |   45 -
 src/plugins/command-bar/gb-command-vim-provider.c  |  106 --
 src/plugins/command-bar/gb-command-vim.c           |  202 ---
 src/plugins/command-bar/gb-command.c               |   72 -
 src/plugins/command-bar/gb-command.h               |   43 -
 src/plugins/command-bar/gb-vim.c                   | 1663 --------------------
 src/plugins/command-bar/gb-vim.h                   |   46 -
 .../command-bar/gbp-command-bar-command-provider.c |  198 +++
 ...ovider.h => gbp-command-bar-command-provider.h} |   11 +-
 src/plugins/command-bar/gbp-command-bar-model.c    |  236 +++
 src/plugins/command-bar/gbp-command-bar-model.h    |   42 +
 ...{gb-command-vim.h => gbp-command-bar-private.h} |   10 +-
 .../command-bar/gbp-command-bar-shortcuts.c        |   64 +
 .../command-bar/gbp-command-bar-suggestion.c       |  156 ++
 ...ion-provider.h => gbp-command-bar-suggestion.h} |   15 +-
 .../command-bar/gbp-command-bar-workspace-addin.c  |  165 ++
 ...gaction.h => gbp-command-bar-workspace-addin.h} |   10 +-
 src/plugins/command-bar/gbp-command-bar.c          |  294 ++++
 .../{gb-command-bar.h => gbp-command-bar.h}        |   14 +-
 src/plugins/command-bar/gbp-command-bar.ui         |   18 +
 src/plugins/command-bar/gbp-gaction-command.c      |  160 ++
 src/plugins/command-bar/gbp-gaction-command.h      |   40 +
 src/plugins/command-bar/meson.build                |   47 +-
 src/plugins/command-bar/themes/shared.css          |   33 +-
 37 files changed, 1475 insertions(+), 4786 deletions(-)
---
diff --git a/src/plugins/command-bar/command-bar-plugin.c b/src/plugins/command-bar/command-bar-plugin.c
new file mode 100644
index 000000000..748ccd2a0
--- /dev/null
+++ b/src/plugins/command-bar/command-bar-plugin.c
@@ -0,0 +1,40 @@
+/* command-bar-plugin.c
+ *
+ * Copyright 2015-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 "command-bar-plugin"
+
+#include "config.h"
+
+#include <libide-gui.h>
+#include <libpeas/peas.h>
+
+#include "gbp-command-bar-command-provider.h"
+#include "gbp-command-bar-workspace-addin.h"
+
+void
+_gbp_command_bar_register_types (PeasObjectModule *module)
+{
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_COMMAND_PROVIDER,
+                                              GBP_TYPE_COMMAND_BAR_COMMAND_PROVIDER);
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_WORKSPACE_ADDIN,
+                                              GBP_TYPE_COMMAND_BAR_WORKSPACE_ADDIN);
+}
diff --git a/src/plugins/command-bar/command-bar.gresource.xml 
b/src/plugins/command-bar/command-bar.gresource.xml
new file mode 100644
index 000000000..f20869f4c
--- /dev/null
+++ b/src/plugins/command-bar/command-bar.gresource.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/plugins/command-bar">
+    <file>command-bar.plugin</file>
+    <file>themes/shared.css</file>
+    <file preprocess="xml-stripblanks">gbp-command-bar.ui</file>
+  </gresource>
+</gresources>
diff --git a/src/plugins/command-bar/command-bar.plugin b/src/plugins/command-bar/command-bar.plugin
index 48ea3d0ef..2a0b145a3 100644
--- a/src/plugins/command-bar/command-bar.plugin
+++ b/src/plugins/command-bar/command-bar.plugin
@@ -1,9 +1,11 @@
 [Plugin]
-Module=command-bar
-Name=Command Bar
-Description=Provides a command bar at the bottom of the workbench window.
 Authors=Christian Hergert <christian hergert me>
-Copyright=Copyright © 2015 Christian Hergert
 Builtin=true
+Copyright=Copyright © 2014-2018 Christian Hergert
+Depends=editor;terminal;
+Description=Builder's workspace command-bar
+Embedded=_gbp_command_bar_register_types
 Hidden=true
-Embedded=gb_command_bar_register_types
+Module=command-bar
+Name=Command Bar
+X-Workspace-Kind=primary;editor;terminal;
diff --git a/src/plugins/command-bar/gbp-command-bar-command-provider.c 
b/src/plugins/command-bar/gbp-command-bar-command-provider.c
new file mode 100644
index 000000000..3eca2a278
--- /dev/null
+++ b/src/plugins/command-bar/gbp-command-bar-command-provider.c
@@ -0,0 +1,198 @@
+/* gbp-command-bar-command-provider.c
+ *
+ * Copyright 2018-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-command-bar-command-provider"
+
+#include <libide-gui.h>
+#include <libide-sourceview.h>
+#include <libide-threading.h>
+
+#include "gbp-command-bar-command-provider.h"
+#include "gbp-gaction-command.h"
+
+struct _GbpCommandBarCommandProvider
+{
+  GObject parent_instance;
+};
+
+static gint
+sort_actions_by_priority (gconstpointer a,
+                          gconstpointer b)
+{
+  GbpGactionCommand *cmd_a = *(GbpGactionCommand **)a;
+  GbpGactionCommand *cmd_b = *(GbpGactionCommand **)b;
+
+  return gbp_gaction_command_compare (cmd_a, cmd_b);
+}
+
+static void
+add_from_group (const gchar  *needle,
+                GPtrArray    *results,
+                const gchar  *prefix,
+                GtkWidget    *widget,
+                GActionGroup *group,
+                GHashTable   *seen)
+{
+  g_auto(GStrv) actions = NULL;
+
+  g_assert (needle != NULL);
+  g_assert (results != NULL);
+  g_assert (prefix != NULL);
+  g_assert (GTK_IS_WIDGET (widget));
+  g_assert (G_IS_ACTION_GROUP (group));
+
+  /* Skip source-view actions which bridge to signals */
+  if (g_str_equal (prefix, "source-view"))
+    return;
+
+  actions = g_action_group_list_actions (group);
+
+  for (guint j = 0; actions[j]; j++)
+    {
+      g_autofree gchar *title = NULL;
+      const GVariantType *type;
+      GbpGactionCommand *command;
+      guint priority = 0;
+
+      if (g_hash_table_contains (seen, actions[j]))
+        continue;
+
+      g_hash_table_insert (seen, g_strdup (actions[j]), NULL);
+
+      /* Skip actions with params */
+      if ((type = g_action_group_get_action_parameter_type (group, actions[j])))
+        continue;
+
+      if (!ide_completion_fuzzy_match (actions[j], needle, &priority))
+        continue;
+
+      title = ide_completion_fuzzy_highlight (actions[j], needle);
+      command = gbp_gaction_command_new (widget, prefix, actions[j], NULL, title, priority);
+      g_ptr_array_add (results, g_steal_pointer (&command));
+    }
+}
+
+static void
+populate_gactions_at_widget (const gchar *needle,
+                             GPtrArray   *results,
+                             GtkWidget   *widget,
+                             GHashTable  *seen)
+{
+  g_autofree const gchar **prefixes = NULL;
+  GtkWidget *parent;
+
+  g_assert (results != NULL);
+  g_assert (GTK_IS_WIDGET (widget));
+
+  if ((prefixes = gtk_widget_list_action_prefixes (widget)))
+    {
+      for (guint i = 0; prefixes[i]; i++)
+        {
+          GActionGroup *group;
+
+          if ((group = gtk_widget_get_action_group (widget, prefixes[i])))
+            add_from_group (needle, results, prefixes[i], widget, group, seen);
+        }
+    }
+
+  if ((parent = gtk_widget_get_parent (widget)))
+    populate_gactions_at_widget (needle, results, parent, seen);
+  else
+    add_from_group (needle, results, "app", widget, G_ACTION_GROUP (IDE_APPLICATION_DEFAULT), seen);
+}
+
+static void
+gbp_command_bar_command_provider_query_async (IdeCommandProvider  *provider,
+                                              IdeWorkspace        *workspace,
+                                              const gchar         *typed_text,
+                                              GCancellable        *cancellable,
+                                              GAsyncReadyCallback  callback,
+                                              gpointer             user_data)
+{
+  GbpCommandBarCommandProvider *self = (GbpCommandBarCommandProvider *)provider;
+  g_autoptr(GHashTable) seen = NULL;
+  g_autoptr(IdeTask) task = NULL;
+  g_autoptr(GPtrArray) results = NULL;
+  g_autofree gchar *needle = NULL;
+  IdeSurface *surface;
+  IdePage *page;
+
+  g_assert (GBP_IS_COMMAND_BAR_COMMAND_PROVIDER (self));
+  g_assert (IDE_IS_WORKSPACE (workspace));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, gbp_command_bar_command_provider_query_async);
+
+  seen = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+  needle = g_utf8_casefold (typed_text, -1);
+  results = g_ptr_array_new_with_free_func (g_object_unref);
+  surface = ide_workspace_get_visible_surface (workspace);
+
+  if ((page = ide_workspace_get_most_recent_page (workspace)))
+    populate_gactions_at_widget (needle, results, GTK_WIDGET (page), seen);
+  else
+    populate_gactions_at_widget (needle, results, GTK_WIDGET (surface), seen);
+
+  g_ptr_array_sort (results, sort_actions_by_priority);
+
+  ide_task_return_pointer (task,
+                           g_steal_pointer (&results),
+                           (GDestroyNotify)g_ptr_array_unref);
+}
+
+static GPtrArray *
+gbp_command_bar_command_provider_query_finish (IdeCommandProvider  *provider,
+                                               GAsyncResult        *result,
+                                               GError             **error)
+{
+  GbpCommandBarCommandProvider *self = (GbpCommandBarCommandProvider *)provider;
+  GPtrArray *ret;
+
+  g_assert (GBP_IS_COMMAND_BAR_COMMAND_PROVIDER (self));
+  g_assert (G_IS_ASYNC_RESULT (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_command_bar_command_provider_query_async;
+  iface->query_finish = gbp_command_bar_command_provider_query_finish;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GbpCommandBarCommandProvider,
+                         gbp_command_bar_command_provider,
+                         G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_COMMAND_PROVIDER,
+                                                command_provider_iface_init))
+
+static void
+gbp_command_bar_command_provider_class_init (GbpCommandBarCommandProviderClass *klass)
+{
+}
+
+static void
+gbp_command_bar_command_provider_init (GbpCommandBarCommandProvider *self)
+{
+}
diff --git a/src/plugins/command-bar/gb-command-vim-provider.h 
b/src/plugins/command-bar/gbp-command-bar-command-provider.h
similarity index 68%
rename from src/plugins/command-bar/gb-command-vim-provider.h
rename to src/plugins/command-bar/gbp-command-bar-command-provider.h
index 11e96f4b8..6407b5ad7 100644
--- a/src/plugins/command-bar/gb-command-vim-provider.h
+++ b/src/plugins/command-bar/gbp-command-bar-command-provider.h
@@ -1,6 +1,6 @@
-/* gb-command-vim-provider.h
+/* gbp-command-bar-command-provider.h
  *
- * Copyright 2014-2019 Christian Hergert <christian hergert me>
+ * Copyright 2018-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
@@ -20,13 +20,12 @@
 
 #pragma once
 
-#include "gb-command-provider.h"
+#include <glib-object.h>
 
 G_BEGIN_DECLS
 
-#define GB_TYPE_COMMAND_VIM_PROVIDER (gb_command_vim_provider_get_type())
+#define GBP_TYPE_COMMAND_BAR_COMMAND_PROVIDER (gbp_command_bar_command_provider_get_type())
 
-G_DECLARE_FINAL_TYPE (GbCommandVimProvider, gb_command_vim_provider,
-                      GB, COMMAND_VIM_PROVIDER, GbCommandProvider)
+G_DECLARE_FINAL_TYPE (GbpCommandBarCommandProvider, gbp_command_bar_command_provider, GBP, 
COMMAND_BAR_COMMAND_PROVIDER, GObject)
 
 G_END_DECLS
diff --git a/src/plugins/command-bar/gbp-command-bar-model.c b/src/plugins/command-bar/gbp-command-bar-model.c
new file mode 100644
index 000000000..cd773fa05
--- /dev/null
+++ b/src/plugins/command-bar/gbp-command-bar-model.c
@@ -0,0 +1,236 @@
+/* gbp-command-bar-model.c
+ *
+ * Copyright 2018-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-command-bar-model"
+
+#include "config.h"
+
+#include <libide-gui.h>
+#include <libide-threading.h>
+#include <libpeas/peas.h>
+#include <libpeas/peas-autocleanups.h>
+
+#include "gbp-command-bar-suggestion.h"
+#include "gbp-command-bar-model.h"
+
+struct _GbpCommandBarModel
+{
+  IdeObject  parent_instance;
+  GPtrArray *items;
+};
+
+typedef struct
+{
+  IdeWorkspace *workspace;
+  IdeTask      *task;
+  const gchar  *typed_text;
+  GPtrArray    *providers;
+} Complete;
+
+static GType
+gbp_command_bar_model_get_item_type (GListModel *model)
+{
+  return GBP_TYPE_COMMAND_BAR_SUGGESTION;
+}
+
+static guint
+gbp_command_bar_model_get_n_items (GListModel *model)
+{
+  return GBP_COMMAND_BAR_MODEL (model)->items->len;
+}
+
+static gpointer
+gbp_command_bar_model_get_item (GListModel *model,
+                                guint       position)
+{
+  GbpCommandBarModel *self = (GbpCommandBarModel *)model;
+
+  g_assert (GBP_IS_COMMAND_BAR_MODEL (self));
+
+  if (position < self->items->len)
+    return g_object_ref (g_ptr_array_index (self->items, position));
+
+  return NULL;
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+  iface->get_item_type = gbp_command_bar_model_get_item_type;
+  iface->get_n_items = gbp_command_bar_model_get_n_items;
+  iface->get_item = gbp_command_bar_model_get_item;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GbpCommandBarModel, gbp_command_bar_model, IDE_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+
+static void
+gbp_command_bar_model_dispose (GObject *object)
+{
+  GbpCommandBarModel *self = (GbpCommandBarModel *)object;
+
+  g_clear_pointer (&self->items, g_ptr_array_unref);
+
+  G_OBJECT_CLASS (gbp_command_bar_model_parent_class)->dispose (object);
+}
+
+static void
+gbp_command_bar_model_class_init (GbpCommandBarModelClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = gbp_command_bar_model_dispose;
+}
+
+static void
+gbp_command_bar_model_init (GbpCommandBarModel *self)
+{
+  self->items = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+GbpCommandBarModel *
+gbp_command_bar_model_new (IdeContext *context)
+{
+  GbpCommandBarModel *self;
+
+  self = g_object_new (GBP_TYPE_COMMAND_BAR_MODEL, NULL);
+  ide_object_append (IDE_OBJECT (context), IDE_OBJECT (self));
+
+  return g_steal_pointer (&self);
+}
+
+static void
+gbp_command_bar_model_query_cb (GObject      *object,
+                                GAsyncResult *result,
+                                gpointer      user_data)
+{
+  IdeCommandProvider *provider = (IdeCommandProvider *)object;
+  g_autoptr(GPtrArray) items = NULL;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  GbpCommandBarModel *self;
+  GPtrArray *providers;
+  guint position;
+
+  g_assert (IDE_IS_COMMAND_PROVIDER (provider));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  self = ide_task_get_source_object (task);
+  providers = ide_task_get_task_data (task);
+
+  g_assert (GBP_IS_COMMAND_BAR_MODEL (self));
+  g_assert (providers != NULL);
+
+  position = self->items->len;
+
+  if ((items = ide_command_provider_query_finish (provider, result, &error)))
+    {
+      for (guint i = 0; i < items->len; i++)
+        {
+          IdeCommand *command = g_ptr_array_index (items, i);
+
+          g_ptr_array_add (self->items, gbp_command_bar_suggestion_new (command));
+        }
+
+      g_list_model_items_changed (G_LIST_MODEL (self), position, 0, items->len);
+    }
+
+  IDE_PTR_ARRAY_SET_FREE_FUNC (items, g_object_unref);
+
+  g_ptr_array_remove (providers, provider);
+
+  if (providers->len == 0)
+    ide_task_return_boolean (task, TRUE);
+}
+
+static void
+gbp_command_bar_query_foreach_cb (PeasExtensionSet *set,
+                                  PeasPluginInfo   *plugin_info,
+                                  PeasExtension    *exten,
+                                  gpointer          user_data)
+{
+  IdeCommandProvider *provider = (IdeCommandProvider *)exten;
+  Complete *complete = user_data;
+
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_COMMAND_PROVIDER (provider));
+  g_assert (complete != NULL);
+
+  g_ptr_array_add (complete->providers, g_object_ref (provider));
+
+  ide_command_provider_query_async (provider,
+                                    complete->workspace,
+                                    complete->typed_text,
+                                    ide_task_get_cancellable (complete->task),
+                                    gbp_command_bar_model_query_cb,
+                                    g_object_ref (complete->task));
+}
+
+void
+gbp_command_bar_model_complete_async (GbpCommandBarModel  *self,
+                                      IdeWorkspace        *workspace,
+                                      const gchar         *typed_text,
+                                      GCancellable        *cancellable,
+                                      GAsyncReadyCallback  callback,
+                                      gpointer             user_data)
+{
+  g_autoptr(PeasExtensionSet) set = NULL;
+  g_autoptr(GPtrArray) providers = NULL;
+  g_autoptr(IdeTask) task = NULL;
+  Complete complete = {0};
+
+  g_return_if_fail (GBP_IS_COMMAND_BAR_MODEL (self));
+  g_return_if_fail (IDE_IS_WORKSPACE (workspace));
+  g_return_if_fail (typed_text != NULL);
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  providers = g_ptr_array_new_with_free_func (g_object_unref);
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, gbp_command_bar_model_complete_async);
+  ide_task_set_task_data (task, g_ptr_array_ref (providers), g_ptr_array_unref);
+
+  set = peas_extension_set_new (peas_engine_get_default (),
+                                IDE_TYPE_COMMAND_PROVIDER,
+                                NULL);
+
+  complete.workspace = workspace;
+  complete.providers = providers;
+  complete.typed_text = typed_text;
+  complete.task = task;
+
+  peas_extension_set_foreach (set, gbp_command_bar_query_foreach_cb, &complete);
+
+  if (providers->len == 0)
+    ide_task_return_boolean (task, TRUE);
+}
+
+gboolean
+gbp_command_bar_model_complete_finish (GbpCommandBarModel  *self,
+                                       GAsyncResult        *result,
+                                       GError             **error)
+{
+  g_return_val_if_fail (GBP_IS_COMMAND_BAR_MODEL (self), FALSE);
+  g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
+
+  return ide_task_propagate_boolean (IDE_TASK (result), error);
+}
diff --git a/src/plugins/command-bar/gbp-command-bar-model.h b/src/plugins/command-bar/gbp-command-bar-model.h
new file mode 100644
index 000000000..d1ac820dc
--- /dev/null
+++ b/src/plugins/command-bar/gbp-command-bar-model.h
@@ -0,0 +1,42 @@
+/* gbp-command-bar-model.h
+ *
+ * Copyright 2018-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_COMMAND_BAR_MODEL (gbp_command_bar_model_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpCommandBarModel, gbp_command_bar_model, GBP, COMMAND_BAR_MODEL, IdeObject)
+
+GbpCommandBarModel *gbp_command_bar_model_new             (IdeContext           *context);
+void                gbp_command_bar_model_complete_async  (GbpCommandBarModel   *self,
+                                                           IdeWorkspace         *workspace,
+                                                           const gchar          *typed_text,
+                                                           GCancellable         *cancellable,
+                                                           GAsyncReadyCallback   callback,
+                                                           gpointer              user_data);
+gboolean            gbp_command_bar_model_complete_finish (GbpCommandBarModel   *self,
+                                                           GAsyncResult         *result,
+                                                           GError              **error);
+
+G_END_DECLS
diff --git a/src/plugins/command-bar/gb-command-vim.h b/src/plugins/command-bar/gbp-command-bar-private.h
similarity index 75%
rename from src/plugins/command-bar/gb-command-vim.h
rename to src/plugins/command-bar/gbp-command-bar-private.h
index 654807972..8becbab60 100644
--- a/src/plugins/command-bar/gb-command-vim.h
+++ b/src/plugins/command-bar/gbp-command-bar-private.h
@@ -1,6 +1,6 @@
-/* gb-command-vim.h
+/* gbp-command-bar-private.h
  *
- * Copyright 2014-2019 Christian Hergert <christian hergert me>
+ * Copyright 2018-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
@@ -20,12 +20,10 @@
 
 #pragma once
 
-#include "gb-command.h"
+#include "gbp-command-bar.h"
 
 G_BEGIN_DECLS
 
-#define GB_TYPE_COMMAND_VIM (gb_command_vim_get_type())
-
-G_DECLARE_FINAL_TYPE (GbCommandVim, gb_command_vim, GB, COMMAND_VIM, GbCommand)
+void _gbp_command_bar_init_shortcuts (GbpCommandBar *self);
 
 G_END_DECLS
diff --git a/src/plugins/command-bar/gbp-command-bar-shortcuts.c 
b/src/plugins/command-bar/gbp-command-bar-shortcuts.c
new file mode 100644
index 000000000..6e9fb51e6
--- /dev/null
+++ b/src/plugins/command-bar/gbp-command-bar-shortcuts.c
@@ -0,0 +1,64 @@
+/* gbp-command-bar-shortcuts.c
+ *
+ * Copyright 2018-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-command-bar-shortcuts"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <dazzle.h>
+
+#include "gbp-command-bar-private.h"
+
+#define I_(s) g_intern_static_string(s)
+
+static const DzlShortcutEntry command_bar_shortcuts[] = {
+  { "org.gnome.builder.command-bar.reveal",
+    DZL_SHORTCUT_PHASE_GLOBAL | DZL_SHORTCUT_PHASE_CAPTURE,
+    NULL,
+    NC_("shortcut window", "Workspace Shortcuts"),
+    NC_("shortcut window", "Command Bar"),
+    NC_("shortcut window", "Show the workspace command bar") },
+};
+
+void
+_gbp_command_bar_init_shortcuts (GbpCommandBar *self)
+{
+  DzlShortcutController *controller;
+
+  dzl_shortcut_manager_add_shortcut_entries (NULL,
+                                             command_bar_shortcuts,
+                                             G_N_ELEMENTS (command_bar_shortcuts),
+                                             GETTEXT_PACKAGE);
+
+  controller = dzl_shortcut_controller_find (GTK_WIDGET (self));
+
+  dzl_shortcut_controller_add_command_action (controller,
+                                              I_("org.gnome.builder.command-bar.reveal"),
+                                              I_("<Primary>Return"),
+                                              DZL_SHORTCUT_PHASE_GLOBAL | DZL_SHORTCUT_PHASE_CAPTURE,
+                                              I_("win.reveal-command-bar"));
+
+  dzl_shortcut_controller_add_command_action (controller,
+                                              I_("org.gnome.builder.command-bar.dismiss"),
+                                              I_("Escape"),
+                                              DZL_SHORTCUT_PHASE_CAPTURE,
+                                              I_("win.dismiss-command-bar"));
+}
diff --git a/src/plugins/command-bar/gbp-command-bar-suggestion.c 
b/src/plugins/command-bar/gbp-command-bar-suggestion.c
new file mode 100644
index 000000000..71a528314
--- /dev/null
+++ b/src/plugins/command-bar/gbp-command-bar-suggestion.c
@@ -0,0 +1,156 @@
+/* gbp-command-bar-suggestion.c
+ *
+ * Copyright 2018-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-command-bar-suggestion"
+
+#include "config.h"
+
+#include "gbp-command-bar-suggestion.h"
+
+struct _GbpCommandBarSuggestion
+{
+  DzlSuggestion  parent_instance;
+  IdeCommand    *command;
+} GbpCommandBarSuggestionPrivate;
+
+enum {
+  PROP_0,
+  PROP_COMMAND,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (GbpCommandBarSuggestion, gbp_command_bar_suggestion, DZL_TYPE_SUGGESTION)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gbp_command_bar_suggestion_set_command (GbpCommandBarSuggestion *self,
+                                        IdeCommand              *command)
+{
+  g_return_if_fail (GBP_IS_COMMAND_BAR_SUGGESTION (self));
+  g_return_if_fail (IDE_IS_COMMAND (command));
+
+  if (g_set_object (&self->command, command))
+    {
+      g_autofree gchar *title = ide_command_get_title (command);
+      g_autofree gchar *subtitle = ide_command_get_subtitle (command);
+
+      dzl_suggestion_set_title (DZL_SUGGESTION (self), title);
+      dzl_suggestion_set_subtitle (DZL_SUGGESTION (self), subtitle);
+    }
+}
+
+static void
+gbp_command_bar_suggestion_dispose (GObject *object)
+{
+  GbpCommandBarSuggestion *self = (GbpCommandBarSuggestion *)object;
+
+  g_clear_object (&self->command);
+
+  G_OBJECT_CLASS (gbp_command_bar_suggestion_parent_class)->dispose (object);
+}
+
+static void
+gbp_command_bar_suggestion_get_property (GObject    *object,
+                                         guint       prop_id,
+                                         GValue     *value,
+                                         GParamSpec *pspec)
+{
+  GbpCommandBarSuggestion *self = GBP_COMMAND_BAR_SUGGESTION (object);
+
+  switch (prop_id)
+    {
+    case PROP_COMMAND:
+      g_value_set_object (value, gbp_command_bar_suggestion_get_command (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_command_bar_suggestion_set_property (GObject      *object,
+                                         guint         prop_id,
+                                         const GValue *value,
+                                         GParamSpec   *pspec)
+{
+  GbpCommandBarSuggestion *self = GBP_COMMAND_BAR_SUGGESTION (object);
+
+  switch (prop_id)
+    {
+    case PROP_COMMAND:
+      gbp_command_bar_suggestion_set_command (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_command_bar_suggestion_class_init (GbpCommandBarSuggestionClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = gbp_command_bar_suggestion_dispose;
+  object_class->get_property = gbp_command_bar_suggestion_get_property;
+  object_class->set_property = gbp_command_bar_suggestion_set_property;
+
+  properties [PROP_COMMAND] =
+    g_param_spec_object ("command",
+                         "Command",
+                         "The command for the suggestion",
+                         IDE_TYPE_COMMAND,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gbp_command_bar_suggestion_init (GbpCommandBarSuggestion *self)
+{
+}
+
+/**
+ * gbp_command_bar_suggestion_get_command:
+ * @self: a #GbpCommandBarSuggestion
+ *
+ * Returns: (transfer none): an #IdeCommand
+ *
+ * Since: 3.32
+ */
+IdeCommand *
+gbp_command_bar_suggestion_get_command (GbpCommandBarSuggestion *self)
+{
+  g_return_val_if_fail (GBP_IS_COMMAND_BAR_SUGGESTION (self), NULL);
+
+  return self->command;
+}
+
+GbpCommandBarSuggestion *
+gbp_command_bar_suggestion_new (IdeCommand *command)
+{
+  g_return_val_if_fail (IDE_IS_COMMAND (command), NULL);
+
+  return g_object_new (GBP_TYPE_COMMAND_BAR_SUGGESTION,
+                       "command", command,
+                       NULL);
+}
diff --git a/src/plugins/command-bar/gb-command-gaction-provider.h 
b/src/plugins/command-bar/gbp-command-bar-suggestion.h
similarity index 58%
rename from src/plugins/command-bar/gb-command-gaction-provider.h
rename to src/plugins/command-bar/gbp-command-bar-suggestion.h
index ece68a396..0d4f5bee4 100644
--- a/src/plugins/command-bar/gb-command-gaction-provider.h
+++ b/src/plugins/command-bar/gbp-command-bar-suggestion.h
@@ -1,6 +1,6 @@
-/* gb-command-gaction-provider.h
+/* gbp-command-bar-suggestion.h
  *
- * Copyright 2014-2019 Christian Hergert <christian hergert me>
+ * Copyright 2018-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
@@ -20,15 +20,16 @@
 
 #pragma once
 
-#include "gb-command-provider.h"
+#include <dazzle.h>
+#include <libide-gui.h>
 
 G_BEGIN_DECLS
 
-#define GB_TYPE_COMMAND_GACTION_PROVIDER (gb_command_gaction_provider_get_type())
+#define GBP_TYPE_COMMAND_BAR_SUGGESTION (gbp_command_bar_suggestion_get_type())
 
-G_DECLARE_FINAL_TYPE (GbCommandGactionProvider, gb_command_gaction_provider,
-                      GB, COMMAND_GACTION_PROVIDER, GbCommandProvider)
+G_DECLARE_FINAL_TYPE (GbpCommandBarSuggestion, gbp_command_bar_suggestion, GBP, COMMAND_BAR_SUGGESTION, 
DzlSuggestion)
 
-GbCommandProvider *gb_command_gaction_provider_new (IdeWorkbench *workbench);
+GbpCommandBarSuggestion *gbp_command_bar_suggestion_new         (IdeCommand              *command);
+IdeCommand              *gbp_command_bar_suggestion_get_command (GbpCommandBarSuggestion *self);
 
 G_END_DECLS
diff --git a/src/plugins/command-bar/gbp-command-bar-workspace-addin.c 
b/src/plugins/command-bar/gbp-command-bar-workspace-addin.c
new file mode 100644
index 000000000..fb9ca10f4
--- /dev/null
+++ b/src/plugins/command-bar/gbp-command-bar-workspace-addin.c
@@ -0,0 +1,165 @@
+/* gbp-command-bar-workspace-addin.c
+ *
+ * Copyright 2018-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-command-bar-workspace-addin"
+
+#include "config.h"
+
+#include <libide-editor.h>
+#include <libide-gui.h>
+#include <libide-terminal.h>
+
+#include "gbp-command-bar.h"
+#include "gbp-command-bar-workspace-addin.h"
+
+struct _GbpCommandBarWorkspaceAddin
+{
+  GObject        parent_instance;
+  GbpCommandBar *command_bar;
+};
+
+static gboolean
+position_command_bar_cb (GbpCommandBarWorkspaceAddin *self,
+                         GtkWidget                   *child,
+                         GdkRectangle                *area,
+                         GtkOverlay                  *overlay)
+{
+  GtkRequisition min, nat;
+
+  g_assert (GBP_IS_COMMAND_BAR_WORKSPACE_ADDIN (self));
+  g_assert (GTK_IS_WIDGET (child));
+
+  if (!GBP_IS_COMMAND_BAR (child))
+    return FALSE;
+
+  gtk_widget_get_allocation (GTK_WIDGET (overlay), area);
+  gtk_widget_get_preferred_size (child, &min, &nat);
+
+  area->x = (area->width - nat.width) / 2;
+  area->y = 100;
+  area->width = nat.width;
+  area->height = nat.height;
+
+  return TRUE;
+}
+
+static void
+gbp_command_bar_workspace_addin_dismiss_command_bar (GSimpleAction *action,
+                                                     GVariant      *param,
+                                                     gpointer       user_data)
+{
+  GbpCommandBarWorkspaceAddin *self = user_data;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (GBP_IS_COMMAND_BAR_WORKSPACE_ADDIN (self));
+
+  if (self->command_bar)
+    gbp_command_bar_dismiss (self->command_bar);
+}
+
+static void
+gbp_command_bar_workspace_addin_reveal_command_bar (GSimpleAction *action,
+                                                    GVariant      *param,
+                                                    gpointer       user_data)
+{
+  GbpCommandBarWorkspaceAddin *self = user_data;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (GBP_IS_COMMAND_BAR_WORKSPACE_ADDIN (self));
+
+  if (self->command_bar)
+    gbp_command_bar_reveal (self->command_bar);
+}
+
+static const GActionEntry entries[] = {
+  { "dismiss-command-bar", gbp_command_bar_workspace_addin_dismiss_command_bar },
+  { "reveal-command-bar", gbp_command_bar_workspace_addin_reveal_command_bar },
+};
+
+static void
+gbp_command_bar_workspace_addin_load (IdeWorkspaceAddin *addin,
+                                      IdeWorkspace      *workspace)
+{
+  GbpCommandBarWorkspaceAddin *self = (GbpCommandBarWorkspaceAddin *)addin;
+  GtkOverlay *overlay;
+
+  g_assert (IDE_IS_WORKSPACE_ADDIN (self));
+  g_assert (IDE_IS_PRIMARY_WORKSPACE (workspace) ||
+            IDE_IS_EDITOR_WORKSPACE (workspace) ||
+            IDE_IS_TERMINAL_WORKSPACE (workspace));
+
+  self->command_bar = g_object_new (GBP_TYPE_COMMAND_BAR,
+                                    "hexpand", TRUE,
+                                    "valign", GTK_ALIGN_END,
+                                    "visible", FALSE,
+                                    NULL);
+  overlay = ide_workspace_get_overlay (workspace);
+  g_signal_connect_object (overlay,
+                           "get-child-position",
+                           G_CALLBACK (position_command_bar_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  gtk_overlay_add_overlay (overlay, GTK_WIDGET (self->command_bar));
+
+  /* Add actions for shortcuts to activate */
+  g_action_map_add_action_entries (G_ACTION_MAP (workspace),
+                                   entries,
+                                   G_N_ELEMENTS (entries),
+                                   self);
+}
+
+static void
+gbp_command_bar_workspace_addin_unload (IdeWorkspaceAddin *addin,
+                                        IdeWorkspace      *workspace)
+{
+  GbpCommandBarWorkspaceAddin *self = (GbpCommandBarWorkspaceAddin *)addin;
+
+  g_assert (IDE_IS_WORKSPACE_ADDIN (self));
+  g_assert (IDE_IS_PRIMARY_WORKSPACE (workspace) ||
+            IDE_IS_EDITOR_WORKSPACE (workspace) ||
+            IDE_IS_TERMINAL_WORKSPACE (workspace));
+
+  /* Remove all the actions we added */
+  for (guint i = 0; i < G_N_ELEMENTS (entries); i++)
+    g_action_map_remove_action (G_ACTION_MAP (workspace), entries[i].name);
+
+  if (self->command_bar != NULL)
+    gtk_widget_destroy (GTK_WIDGET (self->command_bar));
+}
+
+static void
+workspace_addin_iface_init (IdeWorkspaceAddinInterface *iface)
+{
+  iface->load = gbp_command_bar_workspace_addin_load;
+  iface->unload = gbp_command_bar_workspace_addin_unload;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GbpCommandBarWorkspaceAddin, gbp_command_bar_workspace_addin, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKSPACE_ADDIN, workspace_addin_iface_init))
+
+static void
+gbp_command_bar_workspace_addin_class_init (GbpCommandBarWorkspaceAddinClass *klass)
+{
+}
+
+static void
+gbp_command_bar_workspace_addin_init (GbpCommandBarWorkspaceAddin *self)
+{
+}
diff --git a/src/plugins/command-bar/gb-command-gaction.h 
b/src/plugins/command-bar/gbp-command-bar-workspace-addin.h
similarity index 68%
rename from src/plugins/command-bar/gb-command-gaction.h
rename to src/plugins/command-bar/gbp-command-bar-workspace-addin.h
index a7bdab6d7..4a091259d 100644
--- a/src/plugins/command-bar/gb-command-gaction.h
+++ b/src/plugins/command-bar/gbp-command-bar-workspace-addin.h
@@ -1,6 +1,6 @@
-/* gb-command-gaction.h
+/* gbp-command-bar-workspace-addin.h
  *
- * Copyright 2014-2019 Christian Hergert <christian hergert me>
+ * Copyright 2018-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
@@ -20,12 +20,12 @@
 
 #pragma once
 
-#include "gb-command.h"
+#include <glib-object.h>
 
 G_BEGIN_DECLS
 
-#define GB_TYPE_COMMAND_GACTION (gb_command_gaction_get_type())
+#define GBP_TYPE_COMMAND_BAR_WORKSPACE_ADDIN (gbp_command_bar_workspace_addin_get_type())
 
-G_DECLARE_FINAL_TYPE (GbCommandGaction, gb_command_gaction, GB, COMMAND_GACTION, GbCommand)
+G_DECLARE_FINAL_TYPE (GbpCommandBarWorkspaceAddin, gbp_command_bar_workspace_addin, GBP, 
COMMAND_BAR_WORKSPACE_ADDIN, GObject)
 
 G_END_DECLS
diff --git a/src/plugins/command-bar/gbp-command-bar.c b/src/plugins/command-bar/gbp-command-bar.c
new file mode 100644
index 000000000..a15d57cc7
--- /dev/null
+++ b/src/plugins/command-bar/gbp-command-bar.c
@@ -0,0 +1,294 @@
+/* gbp-command-bar.c
+ *
+ * Copyright 2018-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-command-bar"
+
+#include "config.h"
+
+#include <libide-gui.h>
+
+#include "gbp-command-bar.h"
+#include "gbp-command-bar-model.h"
+#include "gbp-command-bar-private.h"
+#include "gbp-command-bar-suggestion.h"
+
+struct _GbpCommandBar
+{
+  DzlBin              parent_instance;
+  DzlSuggestionEntry *entry;
+  GtkRevealer        *revealer;
+};
+
+G_DEFINE_TYPE (GbpCommandBar, gbp_command_bar, DZL_TYPE_BIN)
+
+static void
+replace_model (GbpCommandBar      *self,
+               GbpCommandBarModel *model)
+{
+  GListModel *old_model;
+
+  g_assert (GBP_IS_COMMAND_BAR (self));
+  g_assert (!model || GBP_IS_COMMAND_BAR_MODEL (model));
+
+  old_model = dzl_suggestion_entry_get_model (self->entry);
+  dzl_suggestion_entry_set_model (self->entry, G_LIST_MODEL (model));
+  if (old_model != NULL)
+    ide_object_destroy (IDE_OBJECT (old_model));
+}
+
+static void
+gbp_command_bar_complete_cb (GObject      *object,
+                             GAsyncResult *result,
+                             gpointer      user_data)
+{
+  GbpCommandBarModel *model = (GbpCommandBarModel *)object;
+  g_autoptr(GbpCommandBar) self = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (GBP_IS_COMMAND_BAR_MODEL (model));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (GBP_IS_COMMAND_BAR (self));
+
+  if (gbp_command_bar_model_complete_finish (model, result, &error))
+    replace_model (self, model);
+}
+
+static void
+gbp_command_bar_changed_cb (GbpCommandBar      *self,
+                            DzlSuggestionEntry *entry)
+{
+  g_autoptr(GbpCommandBarModel) model = NULL;
+  IdeWorkspace *workspace;
+  const gchar *text;
+  IdeContext *context;
+
+  g_assert (GBP_IS_COMMAND_BAR (self));
+  g_assert (DZL_IS_SUGGESTION_ENTRY (entry));
+
+  text = dzl_suggestion_entry_get_typed_text (entry);
+
+  if (!gtk_widget_has_focus (GTK_WIDGET (entry)) || ide_str_empty0 (text))
+    {
+      replace_model (self, NULL);
+      return;
+    }
+
+  g_debug ("Command Bar: %s", text);
+
+  context = ide_widget_get_context (GTK_WIDGET (self));
+  model = gbp_command_bar_model_new (context);
+  workspace = ide_widget_get_workspace (GTK_WIDGET (self));
+
+  gbp_command_bar_model_complete_async (model,
+                                        workspace,
+                                        text,
+                                        NULL,
+                                        gbp_command_bar_complete_cb,
+                                        g_object_ref (self));
+}
+
+static gboolean
+gbp_command_bar_focus_out_event_cb (GbpCommandBar      *self,
+                                    GdkEventFocus      *focus,
+                                    DzlSuggestionEntry *entry)
+{
+  g_assert (GBP_IS_COMMAND_BAR (self));
+  g_assert (DZL_IS_SUGGESTION_ENTRY (entry));
+
+  if (gtk_revealer_get_reveal_child (self->revealer))
+    {
+      gtk_revealer_set_reveal_child (self->revealer, FALSE);
+      gtk_entry_set_text (GTK_ENTRY (entry), "");
+    }
+
+  return GDK_EVENT_PROPAGATE;
+}
+
+static void
+gbp_command_bar_child_revealed_cb (GbpCommandBar *self,
+                                   GParamSpec    *pspec,
+                                   GtkRevealer   *revealer)
+{
+  g_assert (GBP_IS_COMMAND_BAR (self));
+  g_assert (GTK_IS_REVEALER (revealer));
+
+  if (gtk_revealer_get_child_revealed (revealer))
+    {
+      if (!gtk_widget_has_focus (GTK_WIDGET (self->entry)))
+        gtk_widget_grab_focus (GTK_WIDGET (self->entry));
+    }
+  else
+    gtk_widget_hide (GTK_WIDGET (self));
+}
+
+static void
+gbp_command_bar_activate_suggestion_cb (GbpCommandBar      *self,
+                                        DzlSuggestionEntry *entry)
+{
+  DzlSuggestion *suggestion;
+
+  g_assert (GBP_IS_COMMAND_BAR (self));
+  g_assert (DZL_IS_SUGGESTION_ENTRY (entry));
+
+  if ((suggestion = dzl_suggestion_entry_get_suggestion (entry)))
+    {
+      GbpCommandBarSuggestion *cbs = GBP_COMMAND_BAR_SUGGESTION (suggestion);
+      IdeCommand *command = gbp_command_bar_suggestion_get_command (cbs);
+
+      ide_command_run_async (command, NULL, NULL, NULL);
+    }
+}
+
+static void
+gbp_command_bar_hide_suggestions_cb (GbpCommandBar      *self,
+                                     DzlSuggestionEntry *entry)
+{
+  g_assert (GBP_IS_COMMAND_BAR (self));
+  g_assert (DZL_IS_SUGGESTION_ENTRY (entry));
+
+  if (gtk_widget_has_focus (GTK_WIDGET (entry)))
+    gbp_command_bar_dismiss (self);
+}
+
+static void
+position_popover_cb (DzlSuggestionEntry *entry,
+                     GdkRectangle       *area,
+                     gboolean           *is_absolute,
+                     gpointer            user_data)
+{
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (DZL_IS_SUGGESTION_ENTRY (entry));
+  g_assert (area != NULL);
+  g_assert (is_absolute != NULL);
+
+  dzl_suggestion_entry_default_position_func (entry, area, is_absolute, NULL);
+
+  /* We want to slightly adjust the popover positioning so it looks like the
+   * popover disappears into the entry. It makes the revealer out a bit less
+   * jarring as we hide the entry/window.
+   */
+  area->x += 3;
+  area->width -= 6;
+  area->y += 3;
+}
+
+static void
+gbp_command_bar_class_init (GbpCommandBarClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  gtk_widget_class_set_css_name (widget_class, "commandbar");
+  gtk_widget_class_set_template_from_resource (widget_class, "/plugins/command-bar/gbp-command-bar.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpCommandBar, entry);
+  gtk_widget_class_bind_template_child (widget_class, GbpCommandBar, revealer);
+}
+
+static void
+gbp_command_bar_init (GbpCommandBar *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  gtk_widget_set_can_focus (GTK_WIDGET (self), FALSE);
+
+  g_signal_connect_object (self->revealer,
+                           "notify::child-revealed",
+                           G_CALLBACK (gbp_command_bar_child_revealed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->entry,
+                           "activate-suggestion",
+                           G_CALLBACK (gbp_command_bar_activate_suggestion_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->entry,
+                           "hide-suggestions",
+                           G_CALLBACK (gbp_command_bar_hide_suggestions_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->entry,
+                           "focus-out-event",
+                           G_CALLBACK (gbp_command_bar_focus_out_event_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->entry,
+                           "changed",
+                           G_CALLBACK (gbp_command_bar_changed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  dzl_suggestion_entry_set_position_func (self->entry,
+                                          position_popover_cb,
+                                          NULL,
+                                          NULL);
+
+  _gbp_command_bar_init_shortcuts (self);
+}
+
+void
+gbp_command_bar_reveal (GbpCommandBar *self)
+{
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
+  g_return_if_fail (GBP_IS_COMMAND_BAR (self));
+
+  if (!gtk_widget_get_visible (GTK_WIDGET (self)))
+    {
+      /* First clear reveal child so that we will fade in properly
+       * when setting reveal child below.
+       */
+      gtk_revealer_set_reveal_child (self->revealer, FALSE);
+      gtk_widget_show (GTK_WIDGET (self));
+    }
+
+  gtk_revealer_set_reveal_child (self->revealer, TRUE);
+
+  /* We need to try to grab focus immediately (best effort) or there is
+   * potential for input events to be delivered to the previously focused
+   * widget. We can't do this until after setting reveal-child or we can
+   * get warnings about the widget not being ready for events.
+   */
+  gtk_widget_grab_focus (GTK_WIDGET (self->entry));
+}
+
+void
+gbp_command_bar_dismiss (GbpCommandBar *self)
+{
+  IdeWorkspace *workspace;
+  IdeSurface *surface;
+  IdePage *page;
+
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
+  g_return_if_fail (GBP_IS_COMMAND_BAR (self));
+
+  gtk_revealer_set_reveal_child (self->revealer, FALSE);
+  workspace = ide_widget_get_workspace (GTK_WIDGET (self));
+  surface = ide_workspace_get_visible_surface (workspace);
+  page = ide_workspace_get_most_recent_page (workspace);
+
+  if (page != NULL)
+    gtk_widget_grab_focus (GTK_WIDGET (page));
+  else
+    gtk_widget_child_focus (GTK_WIDGET (surface), GTK_DIR_TAB_FORWARD);
+
+  gtk_entry_set_text (GTK_ENTRY (self->entry), "");
+}
diff --git a/src/plugins/command-bar/gb-command-bar.h b/src/plugins/command-bar/gbp-command-bar.h
similarity index 67%
rename from src/plugins/command-bar/gb-command-bar.h
rename to src/plugins/command-bar/gbp-command-bar.h
index a13a9dfaa..cecff6166 100644
--- a/src/plugins/command-bar/gb-command-bar.h
+++ b/src/plugins/command-bar/gbp-command-bar.h
@@ -1,6 +1,6 @@
-/* gb-command-bar.h
+/* gbp-command-bar.h
  *
- * Copyright 2014-2019 Christian Hergert <christian hergert me>
+ * Copyright 2018-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
@@ -20,16 +20,16 @@
 
 #pragma once
 
+#include <dazzle.h>
 #include <gtk/gtk.h>
 
 G_BEGIN_DECLS
 
-#define GB_TYPE_COMMAND_BAR (gb_command_bar_get_type())
+#define GBP_TYPE_COMMAND_BAR (gbp_command_bar_get_type())
 
-G_DECLARE_FINAL_TYPE (GbCommandBar, gb_command_bar, GB, COMMAND_BAR, GtkRevealer)
+G_DECLARE_FINAL_TYPE (GbpCommandBar, gbp_command_bar, GBP, COMMAND_BAR, DzlBin)
 
-GtkWidget *gb_command_bar_new  (void);
-void       gb_command_bar_show (GbCommandBar *bar);
-void       gb_command_bar_hide (GbCommandBar *bar);
+void gbp_command_bar_dismiss (GbpCommandBar *self);
+void gbp_command_bar_reveal  (GbpCommandBar *self);
 
 G_END_DECLS
diff --git a/src/plugins/command-bar/gbp-command-bar.ui b/src/plugins/command-bar/gbp-command-bar.ui
new file mode 100644
index 000000000..49791b93f
--- /dev/null
+++ b/src/plugins/command-bar/gbp-command-bar.ui
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GbpCommandBar" parent="DzlBin">
+    <child>
+      <object class="GtkRevealer" id="revealer">
+        <property name="reveal-child">false</property>
+        <property name="transition-type">crossfade</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="DzlSuggestionEntry" id="entry">
+            <property name="width-chars">45</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/plugins/command-bar/gbp-gaction-command.c b/src/plugins/command-bar/gbp-gaction-command.c
new file mode 100644
index 000000000..9ffbf7dfa
--- /dev/null
+++ b/src/plugins/command-bar/gbp-gaction-command.c
@@ -0,0 +1,160 @@
+/* gbp-gaction-command.c
+ *
+ * Copyright 2018-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-gaction-command"
+
+#include "config.h"
+
+#include "gbp-gaction-command.h"
+
+struct _GbpGactionCommand
+{
+  IdeObject  parent_instance;
+  GtkWidget *widget;
+  gchar     *group;
+  gchar     *name;
+  GVariant  *param;
+  gchar     *title;
+  guint      priority;
+};
+
+static void
+gbp_gaction_command_run_async (IdeCommand          *command,
+                               GCancellable        *cancellable,
+                               GAsyncReadyCallback  callback,
+                               gpointer             user_data)
+{
+  GbpGactionCommand *self = (GbpGactionCommand *)command;
+  g_autoptr(IdeTask) task = NULL;
+
+  g_assert (GBP_IS_GACTION_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_gaction_command_run_async);
+
+  if (self->widget != NULL)
+    dzl_gtk_widget_action (self->widget, self->group, self->name, self->param);
+
+  ide_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+gbp_gaction_command_run_finish (IdeCommand    *command,
+                                GAsyncResult  *result,
+                                GError       **error)
+{
+  g_assert (GBP_IS_GACTION_COMMAND (command));
+  g_assert (IDE_IS_TASK (result));
+
+  return ide_task_propagate_boolean (IDE_TASK (result), error);
+}
+
+static gchar *
+gbp_gaction_command_get_title (IdeCommand *command)
+{
+  GbpGactionCommand *self = (GbpGactionCommand *)command;
+
+  g_assert (GBP_IS_GACTION_COMMAND (self));
+
+  return g_strdup (self->title);
+}
+
+static void
+command_iface_init (IdeCommandInterface *iface)
+{
+  iface->run_async = gbp_gaction_command_run_async;
+  iface->run_finish = gbp_gaction_command_run_finish;
+  iface->get_title = gbp_gaction_command_get_title;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GbpGactionCommand, gbp_gaction_command, IDE_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_COMMAND, command_iface_init))
+
+static void
+gbp_gaction_command_finalize (GObject *object)
+{
+  GbpGactionCommand *self = (GbpGactionCommand *)object;
+
+  g_clear_pointer (&self->group, g_free);
+  g_clear_pointer (&self->name, g_free);
+  g_clear_pointer (&self->param, g_variant_unref);
+  g_clear_pointer (&self->title, g_free);
+
+  if (self->widget != NULL)
+    {
+      g_signal_handlers_disconnect_by_func (self->widget,
+                                            G_CALLBACK (gtk_widget_destroyed),
+                                            &self->widget);
+      self->widget = NULL;
+    }
+
+  G_OBJECT_CLASS (gbp_gaction_command_parent_class)->finalize (object);
+}
+
+static void
+gbp_gaction_command_class_init (GbpGactionCommandClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gbp_gaction_command_finalize;
+}
+
+static void
+gbp_gaction_command_init (GbpGactionCommand *self)
+{
+}
+
+GbpGactionCommand *
+gbp_gaction_command_new (GtkWidget   *widget,
+                         const gchar *group,
+                         const gchar *name,
+                         GVariant    *param,
+                         const gchar *title,
+                         guint        priority)
+{
+  GbpGactionCommand *self;
+
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
+  g_return_val_if_fail (group != NULL, NULL);
+  g_return_val_if_fail (name != NULL, NULL);
+
+  self = g_object_new (GBP_TYPE_GACTION_COMMAND, NULL);
+  self->widget = widget;
+  self->group = g_strdup (group);
+  self->name = g_strdup (name);
+  self->param = param ? g_variant_ref_sink (param) : NULL;
+  self->title = g_strdup (title);
+  self->priority = priority;
+
+  g_signal_connect (self->widget,
+                    "destroy",
+                    G_CALLBACK (gtk_widget_destroyed),
+                    &self->widget);
+
+  return g_steal_pointer (&self);
+}
+
+gint
+gbp_gaction_command_compare (GbpGactionCommand *a,
+                             GbpGactionCommand *b)
+{
+  return (gint)a->priority - (gint)b->priority;
+}
diff --git a/src/plugins/command-bar/gbp-gaction-command.h b/src/plugins/command-bar/gbp-gaction-command.h
new file mode 100644
index 000000000..13cf7a7e3
--- /dev/null
+++ b/src/plugins/command-bar/gbp-gaction-command.h
@@ -0,0 +1,40 @@
+/* gbp-gaction-command.h
+ *
+ * Copyright 2018-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_GACTION_COMMAND (gbp_gaction_command_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpGactionCommand, gbp_gaction_command, GBP, GACTION_COMMAND, IdeObject)
+
+gint               gbp_gaction_command_compare (GbpGactionCommand *a,
+                                                GbpGactionCommand *b);
+GbpGactionCommand *gbp_gaction_command_new     (GtkWidget         *widget,
+                                                const gchar       *group,
+                                                const gchar       *name,
+                                                GVariant          *param,
+                                                const gchar       *title,
+                                                guint              priority);
+
+G_END_DECLS
diff --git a/src/plugins/command-bar/meson.build b/src/plugins/command-bar/meson.build
index 139844f76..76bde4928 100644
--- a/src/plugins/command-bar/meson.build
+++ b/src/plugins/command-bar/meson.build
@@ -1,35 +1,18 @@
-if get_option('with_command_bar')
+plugins_sources += files([
+  'command-bar-plugin.c',
+  'gbp-command-bar.c',
+  'gbp-command-bar-command-provider.c',
+  'gbp-command-bar-model.c',
+  'gbp-command-bar-shortcuts.c',
+  'gbp-command-bar-suggestion.c',
+  'gbp-command-bar-workspace-addin.c',
+  'gbp-gaction-command.c',
+])
 
-command_bar_resources = gnome.compile_resources(
-  'gb-command-bar-resources',
-  'gb-command-bar.gresource.xml',
-  c_name: 'gb_command_bar',
+plugin_command_bar_resources = gnome.compile_resources(
+  'gbp-command-bar-resources',
+  'command-bar.gresource.xml',
+  c_name: 'gbp_command_bar',
 )
 
-command_bar_sources = [
-  'gb-command-bar.c',
-  'gb-command-bar.h',
-  'gb-command-gaction-provider.c',
-  'gb-command-gaction-provider.h',
-  'gb-command-gaction.c',
-  'gb-command-gaction.h',
-  'gb-command-manager.c',
-  'gb-command-manager.h',
-  'gb-command-provider.c',
-  'gb-command-provider.h',
-  'gb-command-result.c',
-  'gb-command-result.h',
-  'gb-command-vim-provider.c',
-  'gb-command-vim-provider.h',
-  'gb-command-vim.c',
-  'gb-command-vim.h',
-  'gb-command.c',
-  'gb-command.h',
-  'gb-vim.c',
-  'gb-vim.h',
-]
-
-gnome_builder_plugins_sources += files(command_bar_sources)
-gnome_builder_plugins_sources += command_bar_resources[0]
-
-endif
+plugins_sources += plugin_command_bar_resources[0]
diff --git a/src/plugins/command-bar/themes/shared.css b/src/plugins/command-bar/themes/shared.css
index 22f302a4b..4e66a47de 100644
--- a/src/plugins/command-bar/themes/shared.css
+++ b/src/plugins/command-bar/themes/shared.css
@@ -1,32 +1,5 @@
-commandbar > box.vertical > box.horizontal {
-  border: none;
-  box-shadow: 0px 10px 5px -10px shade(@theme_selected_bg_color, 0.3) inset;
-  color: @theme_selected_fg_color;
-  background-color: @theme_selected_bg_color;
-  background-size: 8px 8px;
-  background-image: repeating-linear-gradient(0deg, alpha(@theme_selected_fg_color,0.05), 
alpha(@theme_selected_fg_color,0.05) 1px, transparent 1px, transparent 8px),
-                    repeating-linear-gradient(-90deg, alpha(@theme_selected_fg_color,0.05), 
alpha(@theme_selected_fg_color,0.05) 1px, transparent 1px, transparent 8px);
-}
 commandbar entry {
-  font-family: Monospace;
-  background-image: none;
-  background-color: transparent;
-  min-height: 0px;
-  color: @theme_selected_fg_color;
-  border: none;
-  padding: 6px;
-  caret-color: @theme_selected_fg_color;
-  box-shadow: none;
-}
-commandbar flowbox {
-  opacity: 0.9;
-  padding: 12px;
-  color: @theme_selected_fg_color;
-  background: transparent;
-}
-commandbar viewport {
-  background-color: @theme_selected_bg_color;
-  background-size: 8px 8px;
-  background-image: repeating-linear-gradient(0deg, alpha(@theme_selected_fg_color,0.05), 
alpha(@theme_selected_fg_color,0.05) 1px, transparent 1px, transparent 8px),
-                    repeating-linear-gradient(-90deg, alpha(@theme_selected_fg_color,0.05), 
alpha(@theme_selected_fg_color,0.05) 1px, transparent 1px, transparent 8px);
+  margin: 10px;
+  border-width: 3px;
+  box-shadow: 0 0 5px @wm_shadow;
 }


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