[gnome-builder/wip/chergert/perspective] search: port search to libide



commit 3b94edcebbe92ad307a25bcd2c85a251c79651a6
Author: Christian Hergert <chergert redhat com>
Date:   Tue Nov 24 01:35:39 2015 -0800

    search: port search to libide
    
    This has a few minor style design changes too, but not everything we are
    going to need. I'd also like to drop the arrow on the popover if possible.
    It's handy to reuse popover for focus tracking and subsurfaces, but we
    might consider just managing that ourself if necessary.

 data/theme/Adwaita-dark.css                        |    5 +
 data/theme/Adwaita.css                             |    5 +
 data/theme/shared.css                              |    8 +
 data/ui/gb-search-display-group.ui                 |   62 --
 data/ui/gb-search-display-row.ui                   |   49 --
 .../{gb-search-box.ui => ide-omni-search-entry.ui} |   10 +-
 data/ui/ide-omni-search-group.ui                   |   14 +
 data/ui/ide-omni-search-row.ui                     |   25 +
 data/ui/ide-workbench-header-bar.ui                |    3 +-
 libide/Makefile.am                                 |    8 +
 libide/editor/ide-editor-perspective.c             |    1 +
 libide/ide.h                                       |    1 +
 libide/resources/libide.gresource.xml              |    3 +
 libide/search/ide-omni-search-display.c            |  598 ++++++++++++++++++++
 libide/search/ide-omni-search-display.h            |   39 ++
 libide/search/ide-omni-search-entry.c              |  396 +++++++++++++
 libide/search/ide-omni-search-entry.h              |   38 ++
 libide/search/ide-omni-search-group.c              |  449 +++++++++++++++
 libide/search/ide-omni-search-group.h              |   45 ++
 libide/search/ide-omni-search-row.c                |  184 ++++++
 libide/search/ide-omni-search-row.h                |   38 ++
 plugins/file-search/gb-file-search-index.c         |    4 +-
 plugins/file-search/gb-file-search-provider.c      |   12 +-
 23 files changed, 1870 insertions(+), 127 deletions(-)
---
diff --git a/data/theme/Adwaita-dark.css b/data/theme/Adwaita-dark.css
index 0a1bcb7..2d13ecf 100644
--- a/data/theme/Adwaita-dark.css
+++ b/data/theme/Adwaita-dark.css
@@ -84,3 +84,8 @@ workbench > GtkOverlay > stack > box > stackswitcher.vertical:first-child button
 workbench > GtkOverlay > stack > box > stackswitcher.vertical:first-child button:checked {
   color: #babdb6;
 }
+
+
+omnisearchrow {
+  color: #eeeeec;
+}
diff --git a/data/theme/Adwaita.css b/data/theme/Adwaita.css
index a06d9dc..2e2b086 100644
--- a/data/theme/Adwaita.css
+++ b/data/theme/Adwaita.css
@@ -83,3 +83,8 @@ workbench > GtkOverlay > stack > box > stackswitcher.vertical:first-child button
 workbench > GtkOverlay > stack > box > stackswitcher.vertical:first-child button:checked {
   color: #2e3436;
 }
+
+
+omnisearchrow {
+  color: #2e3436;
+}
diff --git a/data/theme/shared.css b/data/theme/shared.css
index 775250c..b2a951a 100644
--- a/data/theme/shared.css
+++ b/data/theme/shared.css
@@ -180,3 +180,11 @@ workbench IdePreferencesBin entry {
   box-shadow: none;
   opacity: 0.50;
 }
+
+
+omnisearchrow {
+  padding-top: 9px;
+  padding-left: 12px;
+  padding-right: 12px;
+  padding-bottom: 9px;
+}
diff --git a/data/ui/gb-search-box.ui b/data/ui/ide-omni-search-entry.ui
similarity index 84%
rename from data/ui/gb-search-box.ui
rename to data/ui/ide-omni-search-entry.ui
index baab89c..09dce37 100644
--- a/data/ui/gb-search-box.ui
+++ b/data/ui/ide-omni-search-entry.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <!-- interface-requires gtk+ 3.14 -->
-  <template class="GbSearchBox" parent="GtkBox">
+  <template class="IdeOmniSearchEntry" parent="GtkBox">
     <property name="orientation">horizontal</property>
     <style>
       <class name="linked"/>
@@ -16,7 +16,7 @@
     <child>
       <object class="GtkMenuButton" id="button">
         <property name="focus-on-click">false</property>
-        <property name="visible">true</property>
+        <property name="visible">false</property>
         <property name="popover">popover</property>
         <style>
           <class name="button"/>
@@ -39,11 +39,11 @@
     <child>
       <object class="EggScrolledWindow" id="scroller">
         <property name="max-content-height">800</property>
-        <property name="min-content-height">50</property>
-        <property name="min-content-width">775</property>
+        <property name="min-content-height">30</property>
+        <property name="min-content-width">550</property>
         <property name="visible">True</property>
         <child>
-          <object class="GbSearchDisplay" id="display">
+          <object class="IdeOmniSearchDisplay" id="display">
             <property name="visible">true</property>
           </object>
         </child>
diff --git a/data/ui/ide-omni-search-group.ui b/data/ui/ide-omni-search-group.ui
new file mode 100644
index 0000000..e4e0fb9
--- /dev/null
+++ b/data/ui/ide-omni-search-group.ui
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.8 -->
+  <template class="IdeOmniSearchGroup" parent="GtkBox">
+    <property name="orientation">vertical</property>
+    <property name="visible">true</property>
+    <child>
+      <object class="GtkListBox" id="rows">
+        <property name="hexpand">True</property>
+        <property name="visible">True</property>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/ide-omni-search-row.ui b/data/ui/ide-omni-search-row.ui
new file mode 100644
index 0000000..bfb167c
--- /dev/null
+++ b/data/ui/ide-omni-search-row.ui
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.18 -->
+  <template class="IdeOmniSearchRow" parent="GtkListBoxRow">
+    <child>
+      <object class="GtkBox">
+        <property name="visible">true</property>
+        <property name="orientation">horizontal</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkImage" id="image">
+            <property name="icon-name">folder-symbolic</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="title">
+            <property name="visible">true</property>
+            <property name="xalign">0.0</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/ide-workbench-header-bar.ui b/data/ui/ide-workbench-header-bar.ui
index 39e92b6..9f1396c 100644
--- a/data/ui/ide-workbench-header-bar.ui
+++ b/data/ui/ide-workbench-header-bar.ui
@@ -4,11 +4,10 @@
   <template class="IdeWorkbenchHeaderBar" parent="GtkHeaderBar">
     <property name="show-close-button">true</property>
     <child type="title">
-      <object class="GtkSearchEntry" id="search_entry">
+      <object class="IdeOmniSearchEntry" id="search_entry">
         <property name="hexpand">true</property>
         <property name="margin-end">6</property>
         <property name="margin-start">6</property>
-        <property name="max-width-chars">50</property>
         <property name="visible">true</property>
       </object>
     </child>
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 0f073bc..53e4592 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -349,6 +349,14 @@ libide_1_0_la_SOURCES = \
        preferences/ide-preferences-spin-button.h \
        preferences/ide-preferences-switch.c \
        preferences/ide-preferences-switch.h \
+       search/ide-omni-search-display.c \
+       search/ide-omni-search-display.h \
+       search/ide-omni-search-entry.c \
+       search/ide-omni-search-entry.h \
+       search/ide-omni-search-group.c \
+       search/ide-omni-search-group.h \
+       search/ide-omni-search-row.c \
+       search/ide-omni-search-row.h \
        theatrics/ide-box-theatric.c \
        theatrics/ide-box-theatric.h \
        util/ide-cairo.c \
diff --git a/libide/editor/ide-editor-perspective.c b/libide/editor/ide-editor-perspective.c
index d413213..a4bc8dd 100644
--- a/libide/editor/ide-editor-perspective.c
+++ b/libide/editor/ide-editor-perspective.c
@@ -155,6 +155,7 @@ ide_editor_perspective_locate_buffer (GtkWidget *view,
           if (stack != NULL)
             {
               ide_layout_stack_set_active_view (IDE_LAYOUT_STACK (stack), view);
+              gtk_widget_grab_focus (GTK_WIDGET (view));
               *buffer = NULL;
             }
         }
diff --git a/libide/ide.h b/libide/ide.h
index f27f34f..f592fc6 100644
--- a/libide/ide.h
+++ b/libide/ide.h
@@ -121,6 +121,7 @@ G_BEGIN_DECLS
 #include "git/ide-git-remote-callbacks.h"
 #include "git/ide-git-vcs.h"
 #include "local/ide-local-device.h"
+#include "search/ide-omni-search-row.h"
 #include "util/ide-file-manager.h"
 #include "util/ide-gtk.h"
 #include "util/ide-line-reader.h"
diff --git a/libide/resources/libide.gresource.xml b/libide/resources/libide.gresource.xml
index a3c330f..201fd89 100644
--- a/libide/resources/libide.gresource.xml
+++ b/libide/resources/libide.gresource.xml
@@ -50,6 +50,9 @@
     <file alias="ide-layout-pane.ui">../../data/ui/ide-layout-pane.ui</file>
     <file alias="ide-layout-stack.ui">../../data/ui/ide-layout-stack.ui</file>
     <file alias="ide-layout-view.ui">../../data/ui/ide-layout-view.ui</file>
+    <file alias="ide-omni-search-entry.ui">../../data/ui/ide-omni-search-entry.ui</file>
+    <file alias="ide-omni-search-group.ui">../../data/ui/ide-omni-search-group.ui</file>
+    <file alias="ide-omni-search-row.ui">../../data/ui/ide-omni-search-row.ui</file>
     <file alias="ide-preferences-entry.ui">../../data/ui/ide-preferences-entry.ui</file>
     <file alias="ide-preferences-font-button.ui">../../data/ui/ide-preferences-font-button.ui</file>
     <file alias="ide-preferences-group.ui">../../data/ui/ide-preferences-group.ui</file>
diff --git a/libide/search/ide-omni-search-display.c b/libide/search/ide-omni-search-display.c
new file mode 100644
index 0000000..8f84436
--- /dev/null
+++ b/libide/search/ide-omni-search-display.c
@@ -0,0 +1,598 @@
+/* ide-omni-search-display.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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/>.
+ */
+
+#define G_LOG_DOMAIN "ide-omni-search-display"
+
+#include <glib/gi18n.h>
+
+#include "ide-omni-search-group.h"
+#include "ide-omni-search-display.h"
+
+struct _IdeOmniSearchDisplay
+{
+  GtkBox               parent_instance;
+
+  IdeSearchContext    *context;
+  GPtrArray           *providers;
+};
+
+typedef struct
+{
+  IdeSearchProvider   *provider;
+  IdeOmniSearchGroup  *group;
+} ProviderEntry;
+
+G_DEFINE_TYPE (IdeOmniSearchDisplay, ide_omni_search_display, GTK_TYPE_BOX)
+
+enum {
+  PROP_0,
+  PROP_CONTEXT,
+  LAST_PROP
+};
+
+enum {
+  RESULT_ACTIVATED,
+  LAST_SIGNAL
+};
+
+static GParamSpec *properties [LAST_PROP];
+static guint       signals [LAST_SIGNAL];
+
+static void
+provider_entry_destroy (gpointer data)
+{
+  ProviderEntry *entry = data;
+
+  IDE_ENTRY;
+
+  IDE_TRACE_MSG ("releasing %p", data);
+
+  ide_clear_weak_pointer (&entry->group);
+  g_clear_object (&entry->provider);
+  g_free (entry);
+
+  IDE_EXIT;
+}
+
+static gint
+provider_entry_sort (gconstpointer ptra,
+                     gconstpointer ptrb)
+{
+  ProviderEntry **entrya = (ProviderEntry **)ptra;
+  ProviderEntry **entryb = (ProviderEntry **)ptrb;
+  gint a;
+  gint b;
+
+  a = ide_search_provider_get_priority ((IDE_SEARCH_PROVIDER ((*entrya)->provider)));
+  b = ide_search_provider_get_priority ((IDE_SEARCH_PROVIDER ((*entryb)->provider)));
+
+  return a - b;
+}
+
+GtkWidget *
+ide_omni_search_display_new (void)
+{
+  return g_object_new (IDE_TYPE_OMNI_SEARCH_DISPLAY, NULL);
+}
+
+static void
+ide_omni_search_display_real_result_activated (IdeOmniSearchDisplay *self,
+                                              IdeSearchResult     *result)
+{
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_DISPLAY (self));
+  g_return_if_fail (IDE_IS_SEARCH_RESULT (result));
+
+}
+
+static void
+ide_omni_search_display_result_activated (IdeOmniSearchDisplay *self,
+                                         GtkWidget           *widget,
+                                         IdeSearchResult     *result,
+                                         IdeOmniSearchGroup  *group)
+{
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_DISPLAY (self));
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+  g_return_if_fail (IDE_IS_SEARCH_RESULT (result));
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_GROUP (group));
+
+  g_signal_emit (self, signals [RESULT_ACTIVATED], 0, result);
+}
+
+static void
+ide_omni_search_display_result_selected (IdeOmniSearchDisplay *self,
+                                        IdeSearchResult     *result,
+                                        IdeOmniSearchGroup  *group)
+{
+  guint i;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_DISPLAY (self));
+  g_return_if_fail (!result || IDE_IS_SEARCH_RESULT (result));
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_GROUP (group));
+
+  for (i = 0; i < self->providers->len; i++)
+    {
+      ProviderEntry *ptr;
+
+      ptr = g_ptr_array_index (self->providers, i);
+      if ((ptr->group != NULL) && (ptr->group != group))
+        ide_omni_search_group_unselect (ptr->group);
+    }
+}
+
+static gboolean
+ide_omni_search_display_keynav_failed (IdeOmniSearchDisplay *self,
+                                      GtkDirectionType     dir,
+                                      IdeOmniSearchGroup  *group)
+{
+  GList *list = NULL;
+  GList *iter;
+  gint position = -1;
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (IDE_IS_OMNI_SEARCH_DISPLAY (self), FALSE);
+  g_return_val_if_fail (IDE_IS_OMNI_SEARCH_GROUP (group), FALSE);
+
+  gtk_container_child_get (GTK_CONTAINER (self), GTK_WIDGET (group),
+                           "position", &position,
+                           NULL);
+
+  if (dir == GTK_DIR_DOWN)
+    {
+      list = gtk_container_get_children (GTK_CONTAINER (self));
+      iter = g_list_nth (list, position + 1);
+      for (; iter; iter = iter->next)
+        {
+          if (ide_omni_search_group_get_first (iter->data))
+            {
+              ide_omni_search_group_unselect (group);
+              ide_omni_search_group_focus_first (iter->data);
+              ret = TRUE;
+            }
+        }
+    }
+  else if (dir == GTK_DIR_UP && position > 0)
+    {
+      list = gtk_container_get_children (GTK_CONTAINER (self));
+      iter = g_list_nth (list, position - 1);
+      for (; iter; iter = iter->prev)
+        {
+          if (ide_omni_search_group_get_first (iter->data))
+            {
+              ide_omni_search_group_unselect (group);
+              ide_omni_search_group_focus_last (iter->data);
+              ret = TRUE;
+            }
+        }
+    }
+
+  g_list_free (list);
+
+  return ret;
+}
+
+void
+ide_omni_search_display_activate (IdeOmniSearchDisplay *self)
+{
+  gsize i;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_DISPLAY (self));
+
+  for (i = 0; i < self->providers->len; i++)
+    {
+      ProviderEntry *ptr;
+
+      ptr = g_ptr_array_index (self->providers, i);
+
+      if (ptr->group != NULL)
+        {
+          if (ide_omni_search_group_activate (ptr->group))
+            break;
+        }
+    }
+}
+
+static void
+ide_omni_search_display_add_provider (IdeOmniSearchDisplay *self,
+                                     IdeSearchProvider   *provider)
+{
+  ProviderEntry *entry;
+  guint i;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_DISPLAY (self));
+  g_return_if_fail (IDE_IS_SEARCH_PROVIDER (provider));
+
+  /*
+   * Make sure we don't add an item twice. Probably can assert here, but
+   * warning will do for now.
+   */
+  for (i = 0; i < self->providers->len; i++)
+    {
+      ProviderEntry *ptr;
+
+      ptr = g_ptr_array_index (self->providers, i);
+
+      if (ptr->provider == provider)
+        {
+          g_warning (_("Cannot add provider more than once."));
+          return;
+        }
+    }
+
+  /*
+   * Add the entry to our array and sort the array to determine our target
+   * widget packing position.
+   */
+  entry = g_new0 (ProviderEntry, 1);
+  entry->provider = g_object_ref (provider);
+  entry->group = g_object_new (IDE_TYPE_OMNI_SEARCH_GROUP,
+                               "provider", provider,
+                               "visible", FALSE,
+                               NULL);
+  g_object_add_weak_pointer (G_OBJECT (entry->group), (gpointer *)&entry->group);
+  g_signal_connect_object (entry->group,
+                           "result-activated",
+                           G_CALLBACK (ide_omni_search_display_result_activated),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (entry->group,
+                           "result-selected",
+                           G_CALLBACK (ide_omni_search_display_result_selected),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (entry->group,
+                           "keynav-failed",
+                           G_CALLBACK (ide_omni_search_display_keynav_failed),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_ptr_array_add (self->providers, entry);
+  g_ptr_array_sort (self->providers, provider_entry_sort);
+
+  /*
+   * Find the location of the entry and use the index to pack the display
+   * group widget.
+   */
+  for (i = 0; i < self->providers->len; i++)
+    {
+      ProviderEntry *ptr;
+
+      ptr = g_ptr_array_index (self->providers, i);
+
+      if (ptr->provider == provider)
+        {
+          gtk_container_add_with_properties (GTK_CONTAINER (self),
+                                             GTK_WIDGET (entry->group),
+                                             "position", i,
+                                             NULL);
+          break;
+        }
+    }
+}
+
+static void
+ide_omni_search_display_remove_provider (IdeOmniSearchDisplay *self,
+                                        IdeSearchProvider   *provider)
+{
+  guint i;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_DISPLAY (self));
+  g_return_if_fail (IDE_IS_SEARCH_PROVIDER (provider));
+
+  for (i = 0; i < self->providers->len; i++)
+    {
+      ProviderEntry *ptr;
+
+      ptr = g_ptr_array_index (self->providers, i);
+
+      if (ptr->provider == provider)
+        {
+          IdeOmniSearchGroup *group = ptr->group;
+
+          if (group)
+            gtk_container_remove (GTK_CONTAINER (self), GTK_WIDGET (group));
+          g_ptr_array_remove_index (self->providers, i);
+          return;
+        }
+    }
+
+  g_warning (_("The provider could not be found."));
+}
+
+static void
+ide_omni_search_display_result_added (IdeOmniSearchDisplay *self,
+                                     IdeSearchProvider   *provider,
+                                     IdeSearchResult     *result,
+                                     IdeSearchContext    *context)
+{
+  guint i;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_DISPLAY (self));
+  g_return_if_fail (IDE_IS_SEARCH_PROVIDER (provider));
+  g_return_if_fail (IDE_IS_SEARCH_RESULT (result));
+  g_return_if_fail (IDE_IS_SEARCH_CONTEXT (context));
+
+  for (i = 0; i < self->providers->len; i++)
+    {
+      ProviderEntry *ptr;
+
+      ptr = g_ptr_array_index (self->providers, i);
+
+      if (ptr->provider == provider)
+        {
+          if (ptr->group != NULL)
+            {
+              ide_omni_search_group_add_result (ptr->group, result);
+              gtk_widget_show (GTK_WIDGET (ptr->group));
+            }
+          break;
+        }
+    }
+}
+
+static void
+ide_omni_search_display_result_removed (IdeOmniSearchDisplay *self,
+                                       IdeSearchProvider   *provider,
+                                       IdeSearchResult     *result,
+                                       IdeSearchContext    *context)
+{
+  guint i;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_DISPLAY (self));
+  g_return_if_fail (IDE_IS_SEARCH_PROVIDER (provider));
+  g_return_if_fail (IDE_IS_SEARCH_RESULT (result));
+  g_return_if_fail (IDE_IS_SEARCH_CONTEXT (context));
+
+  for (i = 0; i < self->providers->len; i++)
+    {
+      ProviderEntry *ptr;
+
+      ptr = g_ptr_array_index (self->providers, i);
+
+      if (ptr->provider == provider)
+        {
+          if (ptr->group != NULL)
+            ide_omni_search_group_remove_result (ptr->group, result);
+          break;
+        }
+    }
+}
+
+static void
+ide_omni_search_display_count_set (IdeOmniSearchDisplay *self,
+                                  IdeSearchProvider   *provider,
+                                  guint64              count,
+                                  IdeSearchContext    *context)
+{
+#if 0
+  guint i;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_DISPLAY (self));
+  g_return_if_fail (IDE_IS_SEARCH_PROVIDER (provider));
+  g_return_if_fail (IDE_IS_SEARCH_CONTEXT (context));
+
+  for (i = 0; i < self->providers->len; i++)
+    {
+      ProviderEntry *ptr;
+
+      ptr = g_ptr_array_index (self->providers, i);
+
+      if (ptr->provider == provider)
+        {
+          if (ptr->group != NULL)
+            ide_omni_search_group_set_count (ptr->group, count);
+          break;
+        }
+    }
+#endif
+}
+
+static void
+ide_omni_search_display_connect_context (IdeOmniSearchDisplay *self,
+                                        IdeSearchContext    *context)
+{
+  const GList *providers;
+  const GList *iter;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_DISPLAY (self));
+  g_return_if_fail (IDE_IS_SEARCH_CONTEXT (context));
+
+  providers = ide_search_context_get_providers (context);
+
+  for (iter = providers; iter; iter = iter->next)
+    ide_omni_search_display_add_provider (self, iter->data);
+
+  g_signal_connect_object (context,
+                           "result-added",
+                           G_CALLBACK (ide_omni_search_display_result_added),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (context,
+                           "result-removed",
+                           G_CALLBACK (ide_omni_search_display_result_removed),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (context,
+                           "count-set",
+                           G_CALLBACK (ide_omni_search_display_count_set),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+static void
+ide_omni_search_display_disconnect_context (IdeOmniSearchDisplay *self,
+                                           IdeSearchContext    *context)
+{
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_DISPLAY (self));
+  g_return_if_fail (IDE_IS_SEARCH_CONTEXT (context));
+
+  g_signal_handlers_disconnect_by_func (context,
+                                        G_CALLBACK (ide_omni_search_display_result_added),
+                                        self);
+
+  while (self->providers->len)
+    {
+      ProviderEntry *ptr;
+
+      ptr = g_ptr_array_index (self->providers,
+                               self->providers->len - 1);
+      ide_omni_search_display_remove_provider (self, ptr->provider);
+    }
+}
+
+IdeSearchContext *
+ide_omni_search_display_get_context (IdeOmniSearchDisplay *self)
+{
+  g_return_val_if_fail (IDE_IS_OMNI_SEARCH_DISPLAY (self), NULL);
+
+  return self->context;
+}
+
+void
+ide_omni_search_display_set_context (IdeOmniSearchDisplay *self,
+                                    IdeSearchContext    *context)
+{
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_DISPLAY (self));
+  g_return_if_fail (!context || IDE_IS_SEARCH_CONTEXT (context));
+
+  if (self->context != context)
+    {
+      if (self->context)
+        {
+          ide_omni_search_display_disconnect_context (self, self->context);
+          g_clear_object (&self->context);
+        }
+
+      if (context)
+        {
+          self->context = g_object_ref (context);
+          ide_omni_search_display_connect_context (self, self->context);
+        }
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CONTEXT]);
+    }
+}
+
+static void
+ide_omni_search_display_grab_focus (GtkWidget *widget)
+{
+  IdeOmniSearchDisplay *self = (IdeOmniSearchDisplay *)widget;
+  gsize i;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_DISPLAY (self));
+
+  for (i = 0; i < self->providers->len; i++)
+    {
+      ProviderEntry *ptr;
+
+      ptr = g_ptr_array_index (self->providers, i);
+
+      if (ide_omni_search_group_get_first (ptr->group))
+        {
+          gtk_widget_child_focus (GTK_WIDGET (ptr->group), GTK_DIR_DOWN);
+          break;
+        }
+    }
+}
+
+static void
+ide_omni_search_display_dispose (GObject *object)
+{
+  IdeOmniSearchDisplay *self = (IdeOmniSearchDisplay *)object;
+
+  g_clear_pointer (&self->providers, g_ptr_array_unref);
+  g_clear_object (&self->context);
+
+  G_OBJECT_CLASS (ide_omni_search_display_parent_class)->dispose (object);
+}
+
+static void
+ide_omni_search_display_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  IdeOmniSearchDisplay *self = IDE_OMNI_SEARCH_DISPLAY (object);
+
+  switch (prop_id)
+    {
+    case PROP_CONTEXT:
+      g_value_set_object (value, ide_omni_search_display_get_context (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_omni_search_display_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  IdeOmniSearchDisplay *self = IDE_OMNI_SEARCH_DISPLAY (object);
+
+  switch (prop_id)
+    {
+    case PROP_CONTEXT:
+      ide_omni_search_display_set_context (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_omni_search_display_class_init (IdeOmniSearchDisplayClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  widget_class->grab_focus = ide_omni_search_display_grab_focus;
+
+  object_class->dispose = ide_omni_search_display_dispose;
+  object_class->get_property = ide_omni_search_display_get_property;
+  object_class->set_property = ide_omni_search_display_set_property;
+
+  properties [PROP_CONTEXT] =
+    g_param_spec_object ("context",
+                         "Context",
+                         "The active search context.",
+                         IDE_TYPE_SEARCH_CONTEXT,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+  signals [RESULT_ACTIVATED] =
+    g_signal_new_class_handler ("result-activated",
+                                G_TYPE_FROM_CLASS (klass),
+                                G_SIGNAL_RUN_LAST,
+                                G_CALLBACK (ide_omni_search_display_real_result_activated),
+                                NULL, NULL, NULL,
+                                G_TYPE_NONE,
+                                1,
+                                IDE_TYPE_SEARCH_RESULT);
+}
+
+static void
+ide_omni_search_display_init (IdeOmniSearchDisplay *self)
+{
+  self->providers = g_ptr_array_new_with_free_func (provider_entry_destroy);
+
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL);
+}
diff --git a/libide/search/ide-omni-search-display.h b/libide/search/ide-omni-search-display.h
new file mode 100644
index 0000000..6351924
--- /dev/null
+++ b/libide/search/ide-omni-search-display.h
@@ -0,0 +1,39 @@
+/* ide-omni-search-display.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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/>.
+ */
+
+#ifndef IDE_OMNI_SEARCH_DISPLAY_H
+#define IDE_OMNI_SEARCH_DISPLAY_H
+
+#include <gtk/gtk.h>
+
+#include "ide-search-context.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_OMNI_SEARCH_DISPLAY (ide_omni_search_display_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeOmniSearchDisplay, ide_omni_search_display, IDE, OMNI_SEARCH_DISPLAY, GtkBin)
+
+void              ide_omni_search_display_activate    (IdeOmniSearchDisplay *display);
+IdeSearchContext *ide_omni_search_display_get_context (IdeOmniSearchDisplay *display);
+void              ide_omni_search_display_set_context (IdeOmniSearchDisplay *display,
+                                                      IdeSearchContext    *context);
+
+G_END_DECLS
+
+#endif /* IDE_OMNI_SEARCH_DISPLAY_H */
diff --git a/libide/search/ide-omni-search-entry.c b/libide/search/ide-omni-search-entry.c
new file mode 100644
index 0000000..1248843
--- /dev/null
+++ b/libide/search/ide-omni-search-entry.c
@@ -0,0 +1,396 @@
+/* ide-omni-search-entry.c
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * 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/>.
+ */
+
+#define G_LOG_DOMAIN "ide-search-box"
+
+#include <glib/gi18n.h>
+
+#include "ide-macros.h"
+#include "ide-omni-search-entry.h"
+#include "ide-omni-search-display.h"
+
+#define SHORT_DELAY_TIMEOUT_MSEC 30
+#define LONG_DELAY_TIMEOUT_MSEC  30
+
+struct _IdeOmniSearchEntry
+{
+  GtkBox           parent_instance;
+
+  /* Weak references */
+  IdeWorkbench     *workbench;
+  gulong           set_focus_handler;
+
+  /* Template references */
+  GtkMenuButton   *button;
+  IdeOmniSearchDisplay *display;
+  GtkSearchEntry  *entry;
+  GtkPopover      *popover;
+
+  guint            delay_timeout;
+};
+
+G_DEFINE_TYPE (IdeOmniSearchEntry, ide_omni_search_entry, GTK_TYPE_BOX)
+
+GtkWidget *
+ide_omni_search_entry_new (void)
+{
+  return g_object_new (IDE_TYPE_OMNI_SEARCH_ENTRY, NULL);
+}
+
+IdeSearchEngine *
+ide_omni_search_entry_get_search_engine (IdeOmniSearchEntry *self)
+{
+  IdeContext *context;
+  IdeSearchEngine *search_engine;
+
+  g_return_val_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self), NULL);
+
+  if (self->workbench == NULL)
+    return NULL;
+
+  context = ide_workbench_get_context (self->workbench);
+  if (context == NULL)
+      return NULL;
+
+  search_engine = ide_context_get_search_engine (context);
+
+  return search_engine;
+}
+
+static gboolean
+ide_omni_search_entry_delay_cb (gpointer user_data)
+{
+  IdeOmniSearchEntry *self = user_data;
+  IdeSearchEngine *search_engine;
+  IdeSearchContext *context;
+  const gchar *search_text;
+
+  g_return_val_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self), G_SOURCE_REMOVE);
+
+  self->delay_timeout = 0;
+
+  if (self->display)
+    {
+      context = ide_omni_search_display_get_context (self->display);
+      if (context)
+        ide_search_context_cancel (context);
+
+      search_engine = ide_omni_search_entry_get_search_engine (self);
+      if (!search_engine)
+        return G_SOURCE_REMOVE;
+
+      search_text = gtk_entry_get_text (GTK_ENTRY (self->entry));
+      if (!search_text)
+        return G_SOURCE_REMOVE;
+
+      /* TODO: Remove search text */
+      context = ide_search_engine_search (search_engine, search_text);
+      ide_omni_search_display_set_context (self->display, context);
+      ide_search_context_execute (context, search_text, 7);
+      g_object_unref (context);
+    }
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+ide_omni_search_entry_popover_set_visible (IdeOmniSearchEntry *self,
+                                           gboolean            visible)
+{
+  gboolean entry_has_text;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self));
+
+  entry_has_text = !!(gtk_entry_get_text_length (GTK_ENTRY (self->entry)));
+
+  if (visible == gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->button)))
+    return;
+
+  if (visible && entry_has_text)
+    {
+      if (!gtk_widget_has_focus (GTK_WIDGET (self->entry)))
+        gtk_widget_grab_focus (GTK_WIDGET (self->entry));
+
+      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->button), TRUE);
+    }
+  else if (!visible)
+    {
+      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->button), FALSE);
+    }
+}
+
+static void
+ide_omni_search_entry_entry_activate (IdeOmniSearchEntry *self,
+                                      GtkSearchEntry     *entry)
+{
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self));
+  g_return_if_fail (GTK_IS_SEARCH_ENTRY (entry));
+
+  ide_omni_search_display_activate (self->display);
+  gtk_entry_set_text (GTK_ENTRY (self->entry), "");
+}
+
+static void
+ide_omni_search_entry_entry_changed (IdeOmniSearchEntry *self,
+                                     GtkSearchEntry     *entry)
+{
+  GtkWidget *button;
+  gboolean active;
+  gboolean sensitive;
+  guint delay_msec = SHORT_DELAY_TIMEOUT_MSEC;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self));
+  g_return_if_fail (GTK_IS_SEARCH_ENTRY (entry));
+
+  button = GTK_WIDGET (self->button);
+  active = gtk_widget_has_focus (GTK_WIDGET (entry)) || (self->delay_timeout != 0);
+  sensitive = !!(gtk_entry_get_text_length (GTK_ENTRY (self->entry)));
+
+  if (gtk_widget_get_sensitive (button) != sensitive)
+    gtk_widget_set_sensitive (button, sensitive);
+
+  if (active)
+    ide_omni_search_entry_popover_set_visible (self, TRUE);
+
+  if (!self->delay_timeout)
+    {
+      const gchar *search_text;
+
+      search_text = gtk_entry_get_text (GTK_ENTRY (entry));
+      if (search_text)
+        {
+          if (strlen (search_text) < 3)
+            delay_msec = LONG_DELAY_TIMEOUT_MSEC;
+          self->delay_timeout = g_timeout_add (delay_msec,
+                                               ide_omni_search_entry_delay_cb,
+                                               self);
+        }
+    }
+}
+
+static gboolean
+ide_omni_search_entry_entry_key_press_event (IdeOmniSearchEntry *self,
+                                             GdkEventKey        *key,
+                                             GtkSearchEntry     *entry)
+{
+  g_return_val_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self), GDK_EVENT_PROPAGATE);
+  g_return_val_if_fail (key, GDK_EVENT_PROPAGATE);
+  g_return_val_if_fail (GTK_IS_SEARCH_ENTRY (entry), GDK_EVENT_PROPAGATE);
+
+  switch (key->keyval)
+    {
+    case GDK_KEY_Escape:
+      {
+        ide_omni_search_entry_popover_set_visible (self, FALSE);
+        gtk_widget_grab_focus (gtk_widget_get_toplevel (GTK_WIDGET (entry)));
+
+        return GDK_EVENT_STOP;
+      }
+      break;
+
+    case GDK_KEY_Tab:
+    case GDK_KEY_KP_Tab:
+      if ((key->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) != 0)
+        break;
+      /* Fall through */
+    case GDK_KEY_Down:
+    case GDK_KEY_KP_Down:
+      if (gtk_widget_get_visible (GTK_WIDGET (self->popover)))
+        {
+          gtk_widget_grab_focus (GTK_WIDGET (self->display));
+          return GDK_EVENT_STOP;
+        }
+      break;
+
+    default:
+      break;
+    }
+
+  return GDK_EVENT_PROPAGATE;
+}
+
+static void
+ide_omni_search_entry_display_result_activated (IdeOmniSearchEntry   *self,
+                                                IdeSearchResult      *result,
+                                                IdeOmniSearchDisplay *display)
+{
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self));
+  g_return_if_fail (IDE_IS_SEARCH_RESULT (result));
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_DISPLAY (display));
+
+  gtk_entry_set_text (GTK_ENTRY (self->entry), "");
+}
+
+static void
+ide_omni_search_entry_grab_focus (GtkWidget *widget)
+{
+  IdeOmniSearchEntry *self = (IdeOmniSearchEntry *)widget;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self));
+
+  gtk_widget_grab_focus (GTK_WIDGET (self->entry));
+}
+
+static void
+ide_omni_search_entry_workbench_set_focus (IdeOmniSearchEntry *self,
+                                           GtkWidget          *focus,
+                                           IdeWorkbench       *workbench)
+{
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self));
+  g_return_if_fail (!focus || GTK_IS_WIDGET (focus));
+  g_return_if_fail (IDE_IS_WORKBENCH (workbench));
+
+  if (!focus ||
+      (!gtk_widget_is_ancestor (focus, GTK_WIDGET (self)) &&
+       !gtk_widget_is_ancestor (focus, GTK_WIDGET (self->popover))))
+    {
+      gtk_entry_set_text (GTK_ENTRY (self->entry), "");
+    }
+  else
+    {
+      ide_omni_search_entry_popover_set_visible (self, TRUE);
+    }
+}
+
+static void
+ide_omni_search_entry_map (GtkWidget *widget)
+{
+  IdeOmniSearchEntry *self = (IdeOmniSearchEntry *)widget;
+  GtkWidget *toplevel;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self));
+
+  GTK_WIDGET_CLASS (ide_omni_search_entry_parent_class)->map (widget);
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->button), FALSE);
+  toplevel = gtk_widget_get_toplevel (widget);
+
+  if (IDE_IS_WORKBENCH (toplevel))
+    {
+      ide_set_weak_pointer (&self->workbench, IDE_WORKBENCH (toplevel));
+      self->set_focus_handler =
+        g_signal_connect_object (toplevel,
+                                 "set-focus",
+                                 G_CALLBACK (ide_omni_search_entry_workbench_set_focus),
+                                 self,
+                                 G_CONNECT_SWAPPED | G_CONNECT_AFTER);
+    }
+}
+
+static void
+ide_omni_search_entry_unmap (GtkWidget *widget)
+{
+  IdeOmniSearchEntry *self = (IdeOmniSearchEntry *)widget;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self));
+
+  if (self->workbench)
+    {
+      ide_clear_signal_handler (self->workbench, &self->set_focus_handler);
+      ide_clear_weak_pointer (&self->workbench);
+    }
+
+  GTK_WIDGET_CLASS (ide_omni_search_entry_parent_class)->unmap (widget);
+}
+
+static void
+ide_omni_search_entry_constructed (GObject *object)
+{
+  IdeOmniSearchEntry *self = (IdeOmniSearchEntry *)object;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self));
+
+  G_OBJECT_CLASS (ide_omni_search_entry_parent_class)->constructed (object);
+
+  gtk_popover_set_relative_to (self->popover, GTK_WIDGET (self->entry));
+
+  g_signal_connect_object (self->entry,
+                           "activate",
+                           G_CALLBACK (ide_omni_search_entry_entry_activate),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (self->entry,
+                           "changed",
+                           G_CALLBACK (ide_omni_search_entry_entry_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (self->entry,
+                           "key-press-event",
+                           G_CALLBACK (ide_omni_search_entry_entry_key_press_event),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (self->display,
+                           "result-activated",
+                           G_CALLBACK (ide_omni_search_entry_display_result_activated),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+static void
+ide_omni_search_entry_finalize (GObject *object)
+{
+  IdeOmniSearchEntry *self = (IdeOmniSearchEntry *)object;
+
+  if (self->delay_timeout)
+    {
+      g_source_remove (self->delay_timeout);
+      self->delay_timeout = 0;
+    }
+
+  G_OBJECT_CLASS (ide_omni_search_entry_parent_class)->finalize (object);
+}
+
+static void
+ide_omni_search_entry_class_init (IdeOmniSearchEntryClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->constructed = ide_omni_search_entry_constructed;
+  object_class->finalize = ide_omni_search_entry_finalize;
+
+  widget_class->grab_focus = ide_omni_search_entry_grab_focus;
+  widget_class->map = ide_omni_search_entry_map;
+  widget_class->unmap = ide_omni_search_entry_unmap;
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/ui/ide-omni-search-entry.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdeOmniSearchEntry, button);
+  gtk_widget_class_bind_template_child (widget_class, IdeOmniSearchEntry, display);
+  gtk_widget_class_bind_template_child (widget_class, IdeOmniSearchEntry, entry);
+  gtk_widget_class_bind_template_child (widget_class, IdeOmniSearchEntry, popover);
+
+  g_type_ensure (IDE_TYPE_OMNI_SEARCH_DISPLAY);
+}
+
+static void
+ide_omni_search_entry_init (IdeOmniSearchEntry *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  /*
+   * WORKAROUND:
+   *
+   * The GtkWidget template things that popover is a child of ours. When in
+   * reality it is a child of the GtkMenuButton (since it owns the "popover"
+   * property. Both our widget and the menu button try to call
+   * gtk_widget_destroy() on it.
+   *
+   * https://bugzilla.gnome.org/show_bug.cgi?id=741529
+   */
+  g_object_ref (self->popover);
+}
diff --git a/libide/search/ide-omni-search-entry.h b/libide/search/ide-omni-search-entry.h
new file mode 100644
index 0000000..51ff36c
--- /dev/null
+++ b/libide/search/ide-omni-search-entry.h
@@ -0,0 +1,38 @@
+/* ide-omni-search-entry.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * 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/>.
+ */
+
+#ifndef IDE_OMNI_SEARCH_ENTRY_H
+#define IDE_OMNI_SEARCH_ENTRY_H
+
+#include <gtk/gtk.h>
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_OMNI_SEARCH_ENTRY (ide_omni_search_entry_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeOmniSearchEntry, ide_omni_search_entry, IDE, OMNI_SEARCH_ENTRY, GtkBox)
+
+GtkWidget       *ide_omni_search_entry_new                (void);
+IdeSearchEngine *ide_omni_search_entry_get_search_engine  (IdeOmniSearchEntry     *box);
+void             ide_omni_search_entry_set_search_engine  (IdeOmniSearchEntry     *box,
+                                                   IdeSearchEngine *search_engine);
+
+G_END_DECLS
+
+#endif /* IDE_OMNI_SEARCH_ENTRY_H */
diff --git a/libide/search/ide-omni-search-group.c b/libide/search/ide-omni-search-group.c
new file mode 100644
index 0000000..48b500c
--- /dev/null
+++ b/libide/search/ide-omni-search-group.c
@@ -0,0 +1,449 @@
+/* ide-omni-search-display-group.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "ide-omni-search-group.h"
+#include "ide-omni-search-row.h"
+
+struct _IdeOmniSearchGroup
+{
+  GtkBox             parent_instance;
+
+  /* References owned by instance */
+  IdeSearchProvider *provider;
+
+  /* References owned by template */
+  GtkListBox        *rows;
+
+  guint64            count;
+};
+
+G_DEFINE_TYPE (IdeOmniSearchGroup, ide_omni_search_group, GTK_TYPE_BOX)
+
+enum {
+  PROP_0,
+  PROP_PROVIDER,
+  LAST_PROP
+};
+
+enum {
+  RESULT_ACTIVATED,
+  RESULT_SELECTED,
+  LAST_SIGNAL
+};
+
+static GQuark      quarkRow;
+static GParamSpec *properties [LAST_PROP];
+static guint       signals [LAST_SIGNAL];
+
+static void
+ide_omni_search_group_foreach_cb (GtkWidget *widget,
+                                  gpointer   user_data)
+{
+  GtkWidget **row = user_data;
+
+  if (*row == NULL)
+    *row = widget;
+}
+
+IdeSearchResult *
+ide_omni_search_group_get_first (IdeOmniSearchGroup *self)
+{
+  GtkListBoxRow *row = NULL;
+  IdeSearchResult *ret = NULL;
+
+  g_return_val_if_fail (IDE_IS_OMNI_SEARCH_GROUP (self), NULL);
+
+  gtk_container_foreach (GTK_CONTAINER (self->rows),
+                         ide_omni_search_group_foreach_cb,
+                         &row);
+
+  if (IDE_IS_OMNI_SEARCH_ROW (row))
+    ret = ide_omni_search_row_get_result (IDE_OMNI_SEARCH_ROW (row));
+
+  return ret;
+}
+
+IdeSearchProvider *
+ide_omni_search_group_get_provider (IdeOmniSearchGroup *self)
+{
+  g_return_val_if_fail (IDE_IS_OMNI_SEARCH_GROUP (self), NULL);
+
+  return self->provider;
+}
+
+static void
+ide_omni_search_group_set_provider (IdeOmniSearchGroup *self,
+                                    IdeSearchProvider  *provider)
+{
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_GROUP (self));
+  g_return_if_fail (!provider || IDE_IS_SEARCH_PROVIDER (provider));
+
+  if (provider)
+    self->provider = g_object_ref (provider);
+}
+
+GtkWidget *
+ide_omni_search_group_create_row (IdeSearchResult *result)
+{
+  IdeSearchProvider *provider;
+  GtkWidget *row;
+
+  g_return_val_if_fail (IDE_IS_SEARCH_RESULT (result), NULL);
+
+  provider = ide_search_result_get_provider (result);
+  row = ide_search_provider_create_row (provider, result);
+  g_object_set_qdata (G_OBJECT (result), quarkRow, row);
+
+  return row;
+}
+
+void
+ide_omni_search_group_remove_result (IdeOmniSearchGroup *self,
+                                     IdeSearchResult    *result)
+{
+  GtkWidget *row;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_GROUP (self));
+  g_return_if_fail (IDE_IS_SEARCH_RESULT (result));
+
+  row = g_object_get_qdata (G_OBJECT (result), quarkRow);
+
+  if (row)
+    gtk_container_remove (GTK_CONTAINER (self->rows), row);
+}
+
+void
+ide_omni_search_group_add_result (IdeOmniSearchGroup *self,
+                                  IdeSearchResult    *result)
+{
+  GtkWidget *row;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_GROUP (self));
+  g_return_if_fail (IDE_IS_SEARCH_RESULT (result));
+
+  row = ide_omni_search_group_create_row (result);
+  gtk_container_add (GTK_CONTAINER (self->rows), row);
+
+  gtk_list_box_invalidate_sort (self->rows);
+
+  self->count++;
+}
+
+static gint
+compare_cb (GtkListBoxRow *row1,
+            GtkListBoxRow *row2,
+            gpointer       user_data)
+{
+  IdeSearchResult *result1;
+  IdeSearchResult *result2;
+  gfloat score1;
+  gfloat score2;
+
+  result1 = ide_omni_search_row_get_result (IDE_OMNI_SEARCH_ROW (row1));
+  result2 = ide_omni_search_row_get_result (IDE_OMNI_SEARCH_ROW (row2));
+
+  score1 = ide_search_result_get_score (result1);
+  score2 = ide_search_result_get_score (result2);
+
+  if (score1 < score2)
+    return 1;
+  else if (score1 > score2)
+    return -1;
+  else
+    return 0;
+}
+
+static void
+ide_omni_search_group_result_activated (IdeOmniSearchGroup *self,
+                                        GtkWidget          *widget,
+                                        IdeSearchResult    *result)
+{
+  IdeSearchProvider *provider;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_GROUP (self));
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+  g_return_if_fail (IDE_IS_SEARCH_RESULT (result));
+
+  provider = ide_search_result_get_provider (result);
+  ide_search_provider_activate (provider, widget, result);
+}
+
+void
+ide_omni_search_group_unselect (IdeOmniSearchGroup *self)
+{
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_GROUP (self));
+
+  gtk_list_box_unselect_all (self->rows);
+}
+
+static void
+ide_omni_search_group_row_activated (IdeOmniSearchGroup *self,
+                                     GtkListBoxRow      *row,
+                                     GtkListBox         *list_box)
+{
+  IdeSearchResult *result;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_GROUP (self));
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_ROW (row));
+  g_return_if_fail (GTK_IS_LIST_BOX (list_box));
+
+  result = ide_omni_search_row_get_result (IDE_OMNI_SEARCH_ROW (row));
+  if (result)
+    g_signal_emit (self, signals [RESULT_ACTIVATED], 0, row, result);
+}
+
+static void
+ide_omni_search_group_row_selected (IdeOmniSearchGroup *self,
+                                    GtkListBoxRow      *row,
+                                    GtkListBox         *list_box)
+{
+  GtkWidget *child;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_GROUP (self));
+  g_return_if_fail (!row || GTK_IS_LIST_BOX_ROW (row));
+  g_return_if_fail (GTK_IS_LIST_BOX (list_box));
+
+  if (row)
+    {
+      child = gtk_bin_get_child (GTK_BIN (row));
+
+      if (IDE_IS_OMNI_SEARCH_ROW (child))
+        {
+          IdeSearchResult *result;
+
+          result = ide_omni_search_row_get_result (IDE_OMNI_SEARCH_ROW (child));
+          if (result)
+            g_signal_emit (self, signals [RESULT_SELECTED], 0, result);
+        }
+    }
+}
+
+void
+ide_omni_search_group_focus_first (IdeOmniSearchGroup *self)
+{
+  GtkListBoxRow *row;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_GROUP (self));
+
+  row = gtk_list_box_get_row_at_y (self->rows, 1);
+
+  if (row)
+    {
+      gtk_list_box_unselect_all (self->rows);
+      gtk_widget_child_focus (GTK_WIDGET (self->rows), GTK_DIR_DOWN);
+    }
+}
+
+void
+ide_omni_search_group_focus_last (IdeOmniSearchGroup *self)
+{
+  GtkAllocation alloc;
+  GtkListBoxRow *row;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_GROUP (self));
+
+  gtk_widget_get_allocation (GTK_WIDGET (self->rows), &alloc);
+  row = gtk_list_box_get_row_at_y (self->rows, alloc.height - 2);
+
+  if (row)
+    {
+      gtk_list_box_unselect_all (self->rows);
+      gtk_widget_child_focus (GTK_WIDGET (self->rows), GTK_DIR_UP);
+    }
+}
+
+static void
+ide_omni_search_group_header_cb (GtkListBoxRow *row,
+                                 GtkListBoxRow *before,
+                                 gpointer       user_data)
+{
+  g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
+
+  if (row)
+    {
+      GtkWidget *header;
+
+      header = g_object_new (GTK_TYPE_SEPARATOR,
+                             "orientation", GTK_ORIENTATION_HORIZONTAL,
+                             "visible", TRUE,
+                             NULL);
+      gtk_list_box_row_set_header (row, header);
+    }
+}
+
+static gboolean
+ide_omni_search_group_keynav_failed (IdeOmniSearchGroup *self,
+                                     GtkDirectionType    dir,
+                                     GtkListBox         *list_box)
+{
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (IDE_IS_OMNI_SEARCH_GROUP (self), FALSE);
+  g_return_val_if_fail (GTK_IS_LIST_BOX (list_box), FALSE);
+
+  g_signal_emit_by_name (self, "keynav-failed", dir, &ret);
+
+  return ret;
+}
+
+static void
+ide_omni_search_group_finalize (GObject *object)
+{
+  IdeOmniSearchGroup *self = (IdeOmniSearchGroup *)object;
+
+  g_clear_object (&self->provider);
+
+  G_OBJECT_CLASS (ide_omni_search_group_parent_class)->finalize (object);
+}
+
+static void
+ide_omni_search_group_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  IdeOmniSearchGroup *self = IDE_OMNI_SEARCH_GROUP (object);
+
+  switch (prop_id)
+    {
+    case PROP_PROVIDER:
+      g_value_set_object (value, ide_omni_search_group_get_provider (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_omni_search_group_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  IdeOmniSearchGroup *self = IDE_OMNI_SEARCH_GROUP (object);
+
+  switch (prop_id)
+    {
+    case PROP_PROVIDER:
+      ide_omni_search_group_set_provider (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_omni_search_group_class_init (IdeOmniSearchGroupClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = ide_omni_search_group_finalize;
+  object_class->get_property = ide_omni_search_group_get_property;
+  object_class->set_property = ide_omni_search_group_set_property;
+
+  properties [PROP_PROVIDER] =
+    g_param_spec_object ("provider",
+                         "Provider",
+                         "The search provider",
+                         IDE_TYPE_SEARCH_PROVIDER,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+  signals [RESULT_ACTIVATED] =
+    g_signal_new_class_handler ("result-activated",
+                                G_TYPE_FROM_CLASS (klass),
+                                G_SIGNAL_RUN_LAST,
+                                G_CALLBACK (ide_omni_search_group_result_activated),
+                                NULL, NULL, NULL,
+                                G_TYPE_NONE,
+                                2,
+                                GTK_TYPE_WIDGET,
+                                IDE_TYPE_SEARCH_RESULT);
+
+  signals [RESULT_SELECTED] =
+    g_signal_new ("result-selected",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  1,
+                  IDE_TYPE_SEARCH_RESULT);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/ui/ide-omni-search-group.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdeOmniSearchGroup, rows);
+
+  quarkRow = g_quark_from_static_string ("IDE_OMNI_SEARCH_ROW");
+}
+
+static void
+ide_omni_search_group_init (IdeOmniSearchGroup *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  g_signal_connect_object (self->rows,
+                           "keynav-failed",
+                           G_CALLBACK (ide_omni_search_group_keynav_failed),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (self->rows,
+                           "row-activated",
+                           G_CALLBACK (ide_omni_search_group_row_activated),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (self->rows,
+                           "row-selected",
+                           G_CALLBACK (ide_omni_search_group_row_selected),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  gtk_list_box_set_sort_func (self->rows, compare_cb, NULL, NULL);
+  gtk_list_box_set_header_func (self->rows, ide_omni_search_group_header_cb, NULL, NULL);
+}
+
+gboolean
+ide_omni_search_group_activate (IdeOmniSearchGroup *group)
+{
+  GtkListBoxRow *row = NULL;
+
+  g_return_val_if_fail (IDE_IS_OMNI_SEARCH_GROUP (group), FALSE);
+
+  gtk_container_foreach (GTK_CONTAINER (group->rows),
+                         ide_omni_search_group_foreach_cb,
+                         &row);
+
+  if (IDE_IS_OMNI_SEARCH_ROW (row))
+    {
+      IdeSearchResult *result;
+      IdeSearchProvider *provider;
+
+      result = ide_omni_search_row_get_result (IDE_OMNI_SEARCH_ROW (row));
+      provider = ide_search_result_get_provider (result);
+      ide_search_provider_activate (provider, GTK_WIDGET (row), result);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
diff --git a/libide/search/ide-omni-search-group.h b/libide/search/ide-omni-search-group.h
new file mode 100644
index 0000000..2ff1464
--- /dev/null
+++ b/libide/search/ide-omni-search-group.h
@@ -0,0 +1,45 @@
+/* ide-omni-search-display-group.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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/>.
+ */
+
+#ifndef IDE_OMNI_SEARCH_GROUP_H
+#define IDE_OMNI_SEARCH_GROUP_H
+
+#include <gtk/gtk.h>
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_OMNI_SEARCH_GROUP (ide_omni_search_group_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeOmniSearchGroup, ide_omni_search_group, IDE, OMNI_SEARCH_GROUP, GtkBox)
+
+void               ide_omni_search_group_clear         (IdeOmniSearchGroup *group);
+IdeSearchProvider *ide_omni_search_group_get_provider  (IdeOmniSearchGroup *group);
+void               ide_omni_search_group_add_result    (IdeOmniSearchGroup *group,
+                                                        IdeSearchResult    *result);
+void               ide_omni_search_group_remove_result (IdeOmniSearchGroup *group,
+                                                        IdeSearchResult    *result);
+void               ide_omni_search_group_unselect      (IdeOmniSearchGroup *group);
+void               ide_omni_search_group_focus_first   (IdeOmniSearchGroup *group);
+void               ide_omni_search_group_focus_last    (IdeOmniSearchGroup *group);
+IdeSearchResult   *ide_omni_search_group_get_first     (IdeOmniSearchGroup *group);
+gboolean           ide_omni_search_group_activate      (IdeOmniSearchGroup *group);
+
+G_END_DECLS
+
+#endif /* IDE_OMNI_SEARCH_GROUP_H */
diff --git a/libide/search/ide-omni-search-row.c b/libide/search/ide-omni-search-row.c
new file mode 100644
index 0000000..4a50a1f
--- /dev/null
+++ b/libide/search/ide-omni-search-row.c
@@ -0,0 +1,184 @@
+/* ide-omni-search-row.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "ide-omni-search-row.h"
+
+struct _IdeOmniSearchRow
+{
+  GtkBox           parent_instance;
+
+  IdeSearchResult *result;
+
+  /* References owned by template */
+  GtkLabel        *title;
+  GtkImage        *image;
+};
+
+G_DEFINE_TYPE (IdeOmniSearchRow, ide_omni_search_row, GTK_TYPE_LIST_BOX_ROW)
+
+enum {
+  PROP_0,
+  PROP_ICON_NAME,
+  PROP_RESULT,
+  LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+ide_omni_search_row_set_icon_name (IdeOmniSearchRow *self,
+                                   const gchar      *icon_name)
+{
+  g_assert (IDE_IS_OMNI_SEARCH_ROW (self));
+
+  gtk_image_set_from_icon_name (self->image, icon_name, GTK_ICON_SIZE_MENU);
+}
+
+static void
+ide_omni_search_row_connect (IdeOmniSearchRow *row,
+                             IdeSearchResult  *result)
+{
+  const gchar *title;
+
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_ROW (row));
+  g_return_if_fail (IDE_IS_SEARCH_RESULT (result));
+
+  title = ide_search_result_get_title (result);
+  gtk_label_set_markup (row->title, title);
+}
+
+IdeSearchResult *
+ide_omni_search_row_get_result (IdeOmniSearchRow *row)
+{
+  g_return_val_if_fail (IDE_IS_OMNI_SEARCH_ROW (row), NULL);
+
+  return row->result;
+}
+
+void
+ide_omni_search_row_set_result (IdeOmniSearchRow *row,
+                                IdeSearchResult  *result)
+{
+  g_return_if_fail (IDE_IS_OMNI_SEARCH_ROW (row));
+  g_return_if_fail (IDE_IS_SEARCH_RESULT (result));
+
+  if (result != row->result)
+    {
+      g_clear_object (&row->result);
+
+      if (result)
+        {
+          row->result = g_object_ref (result);
+          ide_omni_search_row_connect (row, result);
+        }
+
+      g_object_notify_by_pspec (G_OBJECT (row), properties [PROP_RESULT]);
+    }
+}
+
+static void
+ide_omni_search_row_finalize (GObject *object)
+{
+  IdeOmniSearchRow *self = (IdeOmniSearchRow *)object;
+
+  g_clear_object (&self->result);
+
+  G_OBJECT_CLASS (ide_omni_search_row_parent_class)->finalize (object);
+}
+
+static void
+ide_omni_search_row_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  IdeOmniSearchRow *self = IDE_OMNI_SEARCH_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_RESULT:
+      g_value_set_object (value, ide_omni_search_row_get_result (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_omni_search_row_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  IdeOmniSearchRow *self = IDE_OMNI_SEARCH_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_ICON_NAME:
+      ide_omni_search_row_set_icon_name (self, g_value_get_string (value));
+      break;
+
+    case PROP_RESULT:
+      ide_omni_search_row_set_result (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_omni_search_row_class_init (IdeOmniSearchRowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = ide_omni_search_row_finalize;
+  object_class->get_property = ide_omni_search_row_get_property;
+  object_class->set_property = ide_omni_search_row_set_property;
+
+  properties [PROP_ICON_NAME] =
+    g_param_spec_string ("icon-name",
+                         "Icon Name",
+                         "Icon Name",
+                         NULL,
+                         (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_RESULT] =
+    g_param_spec_object ("result",
+                         "Result",
+                         "Result",
+                         IDE_TYPE_SEARCH_RESULT,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+  gtk_widget_class_set_css_name (widget_class, "omnisearchrow");
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/ide-omni-search-row.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdeOmniSearchRow, image);
+  gtk_widget_class_bind_template_child (widget_class, IdeOmniSearchRow, title);
+}
+
+static void
+ide_omni_search_row_init (IdeOmniSearchRow *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/libide/search/ide-omni-search-row.h b/libide/search/ide-omni-search-row.h
new file mode 100644
index 0000000..2e950ec
--- /dev/null
+++ b/libide/search/ide-omni-search-row.h
@@ -0,0 +1,38 @@
+/* ide-omni-search-row.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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/>.
+ */
+
+#ifndef IDE_OMNI_SEARCH_ROW_H
+#define IDE_OMNI_SEARCH_ROW_H
+
+#include <gtk/gtk.h>
+
+#include "ide-search-result.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_OMNI_SEARCH_ROW (ide_omni_search_row_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeOmniSearchRow, ide_omni_search_row, IDE, OMNI_SEARCH_ROW, GtkListBoxRow)
+
+IdeSearchResult *ide_omni_search_row_get_result (IdeOmniSearchRow *row);
+void             ide_omni_search_row_set_result (IdeOmniSearchRow *row,
+                                                 IdeSearchResult  *result);
+
+G_END_DECLS
+
+#endif /* IDE_OMNI_SEARCH_ROW_H */
diff --git a/plugins/file-search/gb-file-search-index.c b/plugins/file-search/gb-file-search-index.c
index 873d734..c85406e 100644
--- a/plugins/file-search/gb-file-search-index.c
+++ b/plugins/file-search/gb-file-search-index.c
@@ -22,7 +22,6 @@
 
 #include "gb-file-search-index.h"
 #include "gb-file-search-result.h"
-#include "gb-string.h"
 
 struct _GbFileSearchIndex
 {
@@ -325,8 +324,7 @@ gb_file_search_index_populate (GbFileSearchIndex *self,
           g_autoptr(GbFileSearchResult) result = NULL;
           g_autofree gchar *markup = NULL;
 
-          markup = gb_str_highlight_full (match->key, query, TRUE,
-                                          (GB_HIGHLIGHT_BOLD | GB_HIGHLIGHT_UNDERLINE));
+          markup = ide_completion_item_fuzzy_highlight (match->key, query);
           result = g_object_new (GB_TYPE_FILE_SEARCH_RESULT,
                                  "context", icontext,
                                  "provider", provider,
diff --git a/plugins/file-search/gb-file-search-provider.c b/plugins/file-search/gb-file-search-provider.c
index 60f04da..e25280e 100644
--- a/plugins/file-search/gb-file-search-provider.c
+++ b/plugins/file-search/gb-file-search-provider.c
@@ -17,12 +17,11 @@
  */
 
 #include <glib/gi18n.h>
+#include <ide.h>
 #include <libpeas/peas.h>
 
 #include "gb-file-search-provider.h"
 #include "gb-file-search-index.h"
-#include "gb-search-display-row.h"
-#include "gb-workbench.h"
 
 struct _GbFileSearchProvider
 {
@@ -91,7 +90,8 @@ gb_file_search_provider_create_row (IdeSearchProvider *provider,
   g_assert (IDE_IS_SEARCH_PROVIDER (provider));
   g_assert (IDE_IS_SEARCH_RESULT (result));
 
-  return g_object_new (GB_TYPE_SEARCH_DISPLAY_ROW,
+  return g_object_new (IDE_TYPE_OMNI_SEARCH_ROW,
+                       "icon-name", "text-x-generic-symbolic",
                        "result", result,
                        "visible", TRUE,
                        NULL);
@@ -110,7 +110,7 @@ gb_file_search_provider_activate (IdeSearchProvider *provider,
 
   toplevel = gtk_widget_get_toplevel (row);
 
-  if (GB_IS_WORKBENCH (toplevel))
+  if (IDE_IS_WORKBENCH (toplevel))
     {
       g_autofree gchar *path = NULL;
       g_autoptr(GFile) file = NULL;
@@ -118,13 +118,13 @@ gb_file_search_provider_activate (IdeSearchProvider *provider,
       IdeVcs *vcs;
       GFile *workdir;
 
-      context = gb_workbench_get_context (GB_WORKBENCH (toplevel));
+      context = ide_workbench_get_context (IDE_WORKBENCH (toplevel));
       vcs = ide_context_get_vcs (context);
       workdir = ide_vcs_get_working_directory (vcs);
       g_object_get (result, "path", &path, NULL);
       file = g_file_get_child (workdir, path);
 
-      gb_workbench_open (GB_WORKBENCH (toplevel), file);
+      ide_workbench_open_files_async (IDE_WORKBENCH (toplevel), &file, 1, NULL, NULL, NULL, NULL);
     }
 }
 


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