[gnome-builder/wip/chergert/suggestion] search: port search to DzlSuggestion



commit d816a50eb19e9b4171b0c731d9229953739c4547
Author: Christian Hergert <chergert redhat com>
Date:   Wed Jun 7 18:06:05 2017 -0700

    search: port search to DzlSuggestion
    
    This is a bit different style of search engine than we had before. It aims
    to just be as simple as we can and still get the same performance and ease
    of implementation.

 libide/diagnostics/ide-source-location.c           |   17 +
 libide/diagnostics/ide-source-location.h           |    4 +
 libide/ide.h                                       |    3 +-
 libide/meson.build                                 |   15 +-
 libide/resources/libide.gresource.xml              |    2 -
 libide/search/ide-omni-search-display.c            |  678 --------------------
 libide/search/ide-omni-search-display.h            |   41 --
 libide/search/ide-omni-search-entry.c              |  403 ------------
 libide/search/ide-omni-search-entry.h              |   39 --
 libide/search/ide-omni-search-group.c              |  571 ----------------
 libide/search/ide-omni-search-group.h              |   50 --
 libide/search/ide-omni-search-group.ui             |   14 -
 libide/search/ide-omni-search-row.c                |  189 ------
 libide/search/ide-omni-search-row.ui               |   26 -
 libide/search/ide-search-context.c                 |  263 --------
 libide/search/ide-search-context.h                 |   50 --
 libide/search/ide-search-engine.c                  |  294 +++++++--
 libide/search/ide-search-engine.h                  |   14 +-
 libide/search/ide-search-entry.c                   |  128 ++++
 .../{ide-omni-search-row.h => ide-search-entry.h}  |   22 +-
 libide/search/ide-search-provider.c                |  140 ++---
 libide/search/ide-search-provider.h                |   52 +-
 libide/search/ide-search-reducer.c                 |  104 +++-
 libide/search/ide-search-reducer.h                 |   28 +-
 libide/search/ide-search-result.c                  |  352 ++++-------
 libide/search/ide-search-result.h                  |   45 +-
 libide/workbench/ide-workbench-header-bar.c        |   14 +-
 libide/workbench/ide-workbench-header-bar.ui       |    2 +-
 plugins/file-search/gb-file-search-index.c         |   42 +-
 plugins/file-search/gb-file-search-index.h         |   33 +-
 plugins/file-search/gb-file-search-provider.c      |   75 +--
 plugins/file-search/gb-file-search-result.c        |   43 ++-
 32 files changed, 841 insertions(+), 2912 deletions(-)
---
diff --git a/libide/diagnostics/ide-source-location.c b/libide/diagnostics/ide-source-location.c
index e4c11de..9a997cc 100644
--- a/libide/diagnostics/ide-source-location.c
+++ b/libide/diagnostics/ide-source-location.c
@@ -20,6 +20,8 @@
 
 #include <dazzle.h>
 
+#include "ide-context.h"
+
 #include "files/ide-file.h"
 #include "diagnostics/ide-source-location.h"
 
@@ -221,3 +223,18 @@ ide_source_location_hash (IdeSourceLocation *self)
 {
   return ide_file_hash (self->file) ^ g_int_hash (&self->line) ^ g_int_hash (&self->line_offset);
 }
+
+IdeSourceLocation *
+ide_source_location_new_for_path (IdeContext  *context,
+                                  const gchar *path,
+                                  guint        line,
+                                  guint        line_offset)
+{
+  g_autoptr(IdeFile) ifile = NULL;
+
+  g_return_val_if_fail (!context || IDE_IS_CONTEXT (context), NULL);
+
+  ifile = ide_file_new_for_path (context, path);
+
+  return ide_source_location_new (ifile, line, line_offset, 0);
+}
diff --git a/libide/diagnostics/ide-source-location.h b/libide/diagnostics/ide-source-location.h
index 1dad31b..2f999b1 100644
--- a/libide/diagnostics/ide-source-location.h
+++ b/libide/diagnostics/ide-source-location.h
@@ -34,6 +34,10 @@ IdeSourceLocation *ide_source_location_new             (IdeFile
                                                         guint                    line,
                                                         guint                    line_offset,
                                                         guint                    offset);
+IdeSourceLocation *ide_source_location_new_for_path    (IdeContext              *context,
+                                                        const gchar             *path,
+                                                        guint                    line,
+                                                        guint                    line_offset);
 guint              ide_source_location_get_line        (IdeSourceLocation       *self);
 guint              ide_source_location_get_line_offset (IdeSourceLocation       *self);
 guint              ide_source_location_get_offset      (IdeSourceLocation       *self);
diff --git a/libide/ide.h b/libide/ide.h
index 8e5a8f3..5065986 100644
--- a/libide/ide.h
+++ b/libide/ide.h
@@ -106,9 +106,8 @@ G_BEGIN_DECLS
 #include "runtimes/ide-runtime-manager.h"
 #include "runtimes/ide-runtime-provider.h"
 #include "runtimes/ide-runtime.h"
-#include "search/ide-omni-search-row.h"
-#include "search/ide-search-context.h"
 #include "search/ide-search-engine.h"
+#include "search/ide-search-entry.h"
 #include "search/ide-search-provider.h"
 #include "search/ide-search-reducer.h"
 #include "search/ide-search-result.h"
diff --git a/libide/meson.build b/libide/meson.build
index e96ca2d..9943ab1 100644
--- a/libide/meson.build
+++ b/libide/meson.build
@@ -122,14 +122,9 @@ libide_public_headers = [
   'runtimes/ide-runtime-manager.h',
   'runtimes/ide-runtime-provider.h',
   'runtimes/ide-runtime.h',
-  'search/ide-omni-search-display.h',
-  'search/ide-omni-search-entry.h',
-  'search/ide-omni-search-group.h',
-  'search/ide-omni-search-row.h',
-  'search/ide-search-context.h',
   'search/ide-search-engine.h',
+  'search/ide-search-entry.h',
   'search/ide-search-provider.h',
-  'search/ide-search-reducer.h',
   'search/ide-search-result.h',
   'snippets/ide-source-snippet-chunk.h',
   'snippets/ide-source-snippet-context.h',
@@ -321,12 +316,8 @@ libide_public_sources = [
   'runtimes/ide-runtime-manager.c',
   'runtimes/ide-runtime-provider.c',
   'runtimes/ide-runtime.c',
-  'search/ide-omni-search-display.c',
-  'search/ide-omni-search-entry.c',
-  'search/ide-omni-search-group.c',
-  'search/ide-omni-search-row.c',
-  'search/ide-search-context.c',
   'search/ide-search-engine.c',
+  'search/ide-search-entry.c',
   'search/ide-search-provider.c',
   'search/ide-search-result.c',
   'snippets/ide-source-snippet-chunk.c',
@@ -502,6 +493,7 @@ libide_sources = libide_generated_headers + libide_public_sources + [
   'preferences/ide-preferences-language-row.h',
   'runner/ide-run-manager-private.h',
   'search/ide-search-reducer.c',
+  'search/ide-search-reducer.h',
   'snippets/ide-source-snippet-completion-item.c',
   'snippets/ide-source-snippet-completion-item.h',
   'snippets/ide-source-snippet-completion-provider.c',
@@ -663,6 +655,7 @@ libide_plugin_dep = declare_dependency(
                          libgd_dep,
                          libgio_dep,
                          libgtk_dep,
+                         libgd_dep,
                          libgtksource_dep,
                          libtemplate_glib_dep,
                          libjson_glib_dep,
diff --git a/libide/resources/libide.gresource.xml b/libide/resources/libide.gresource.xml
index ba3f08c..2b79289 100644
--- a/libide/resources/libide.gresource.xml
+++ b/libide/resources/libide.gresource.xml
@@ -64,8 +64,6 @@
     <file compressed="true" alias="ide-layout-stack.ui">../workbench/ide-layout-stack.ui</file>
     <file compressed="true" alias="ide-omni-bar.ui">../workbench/ide-omni-bar.ui</file>
     <file compressed="true" alias="ide-omni-bar-row.ui">../workbench/ide-omni-bar-row.ui</file>
-    <file compressed="true" alias="ide-omni-search-group.ui">../search/ide-omni-search-group.ui</file>
-    <file compressed="true" alias="ide-omni-search-row.ui">../search/ide-omni-search-row.ui</file>
     <file compressed="true" 
alias="ide-perspective-menu-button.ui">../workbench/ide-perspective-menu-button.ui</file>
     <file compressed="true" 
alias="ide-preferences-language-row.ui">../preferences/ide-preferences-language-row.ui</file>
     <file compressed="true" alias="ide-run-button.ui">../runner/ide-run-button.ui</file>
diff --git a/libide/search/ide-search-engine.c b/libide/search/ide-search-engine.c
index 43e47b5..8c35476 100644
--- a/libide/search/ide-search-engine.c
+++ b/libide/search/ide-search-engine.c
@@ -1,6 +1,6 @@
 /* ide-search-engine.c
  *
- * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ * Copyright (C) 2015-2017 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
@@ -16,71 +16,61 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <glib/gi18n.h>
-#include <libpeas/peas.h>
+#define G_LOG_DOMAIN "ide-search-engine"
 
-#include "ide-internal.h"
+#include <libpeas/peas.h>
 
-#include "plugins/ide-extension-util.h"
-#include "search/ide-search-context.h"
-#include "search/ide-search-engine.h"
-#include "search/ide-search-provider.h"
-#include "search/ide-search-result.h"
+#include "ide-search-engine.h"
+#include "ide-search-provider.h"
+#include "ide-search-result.h"
 
 struct _IdeSearchEngine
 {
   IdeObject         parent_instance;
-
   PeasExtensionSet *extensions;
+  guint             active_count;
 };
 
-G_DEFINE_TYPE (IdeSearchEngine, ide_search_engine, IDE_TYPE_OBJECT)
-
-static void
-add_provider_to_context (PeasExtensionSet *extensions,
-                         PeasPluginInfo   *plugin_info,
-                         PeasExtension    *extension,
-                         IdeSearchContext *search_context)
+typedef struct
 {
-  g_assert (PEAS_IS_EXTENSION_SET (extensions));
-  g_assert (plugin_info != NULL);
-  g_assert (IDE_IS_SEARCH_PROVIDER (extension));
-  g_assert (IDE_IS_SEARCH_CONTEXT (search_context));
+  GTask         *task;
+  gchar         *query;
+  GListStore    *store;
+  guint          outstanding;
+  guint          max_results;
+} Request;
 
-  _ide_search_context_add_provider (search_context, IDE_SEARCH_PROVIDER (extension), 0);
-}
+enum {
+  PROP_0,
+  PROP_BUSY,
+  N_PROPS
+};
 
-/**
- * ide_search_engine_search:
- * @search_terms: The search terms.
- *
- * Begins a query against the requested search providers.
- *
- * If @providers is %NULL, all registered providers will be used.
- *
- * Returns: (transfer full) (nullable): An #IdeSearchContext or %NULL if no
- *   providers could be loaded.
- */
-IdeSearchContext *
-ide_search_engine_search (IdeSearchEngine *self,
-                          const gchar     *search_terms)
-{
-  IdeSearchContext *search_context;
-  IdeContext *context;
+G_DEFINE_TYPE (IdeSearchEngine, ide_search_engine, IDE_TYPE_OBJECT)
 
-  g_return_val_if_fail (IDE_IS_SEARCH_ENGINE (self), NULL);
-  g_return_val_if_fail (search_terms, NULL);
+static GParamSpec *properties [N_PROPS];
 
-  context = ide_object_get_context (IDE_OBJECT (self));
-  search_context = g_object_new (IDE_TYPE_SEARCH_CONTEXT,
-                                 "context", context,
-                                 NULL);
+static Request *
+request_new (void)
+{
+  Request *r;
 
-  peas_extension_set_foreach (self->extensions,
-                              (PeasExtensionSetForeachFunc)add_provider_to_context,
-                              search_context);
+  r = g_slice_new0 (Request);
+  r->store = NULL;
+  r->outstanding = 0;
+  r->query = NULL;
+
+  return r;
+}
 
-  return search_context;
+static void
+request_destroy (Request *r)
+{
+  g_assert (r->outstanding == 0);
+  g_clear_object (&r->store);
+  g_clear_pointer (&r->query, g_free);
+  r->task = NULL;
+  g_slice_free (Request, r);
 }
 
 static void
@@ -89,14 +79,16 @@ ide_search_engine_constructed (GObject *object)
   IdeSearchEngine *self = (IdeSearchEngine *)object;
   IdeContext *context;
 
-  context = ide_object_get_context (IDE_OBJECT (self));
-
-  self->extensions = ide_extension_set_new (peas_engine_get_default (),
-                                            IDE_TYPE_SEARCH_PROVIDER,
-                                            "context", context,
-                                            NULL);
+  g_assert (IDE_IS_SEARCH_ENGINE (self));
 
   G_OBJECT_CLASS (ide_search_engine_parent_class)->constructed (object);
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+
+  self->extensions = peas_extension_set_new (peas_engine_get_default (),
+                                             IDE_TYPE_SEARCH_PROVIDER,
+                                             "context", context,
+                                             NULL);
 }
 
 static void
@@ -110,15 +102,205 @@ ide_search_engine_dispose (GObject *object)
 }
 
 static void
+ide_search_engine_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  IdeSearchEngine *self = IDE_SEARCH_ENGINE (object);
+
+  switch (prop_id)
+    {
+    case PROP_BUSY:
+      g_value_set_boolean (value, ide_search_engine_get_busy (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
 ide_search_engine_class_init (IdeSearchEngineClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
   object_class->constructed = ide_search_engine_constructed;
   object_class->dispose = ide_search_engine_dispose;
+  object_class->get_property = ide_search_engine_get_property;
+
+  properties [PROP_BUSY] =
+    g_param_spec_boolean ("busy",
+                          "Busy",
+                          "If the search engine is busy",
+                          FALSE,
+                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
 }
 
 static void
 ide_search_engine_init (IdeSearchEngine *self)
 {
 }
+
+IdeSearchEngine *
+ide_search_engine_new (void)
+{
+  return g_object_new (IDE_TYPE_SEARCH_ENGINE, NULL);
+}
+
+/**
+ * ide_search_engine_get_busy:
+ * @self: a #IdeSearchEngine
+ *
+ * Checks if the #IdeSearchEngine is currently executing a query.
+ *
+ * Returns: %TRUE if queries are being processed.
+ */
+gboolean
+ide_search_engine_get_busy (IdeSearchEngine *self)
+{
+  g_return_val_if_fail (IDE_IS_SEARCH_ENGINE (self), FALSE);
+
+  return self->active_count > 0;
+}
+
+static void
+ide_search_engine_search_cb (GObject      *object,
+                             GAsyncResult *result,
+                             gpointer      user_data)
+{
+  IdeSearchProvider *provider = (IdeSearchProvider *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GPtrArray) ar = NULL;
+  Request *r;
+
+  g_assert (IDE_IS_SEARCH_PROVIDER (provider));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  r = g_task_get_task_data (task);
+  g_assert (r != NULL);
+  g_assert (r->task == task);
+  g_assert (r->outstanding > 0);
+  g_assert (G_IS_LIST_STORE (r->store));
+
+  ar = ide_search_provider_search_finish (provider, result, &error);
+
+  if (error != NULL)
+    {
+      g_warning ("%s", error->message);
+      goto cleanup;
+    }
+
+  for (guint i = 0; i < ar->len; i++)
+    {
+      IdeSearchResult *item = g_ptr_array_index (ar, i);
+
+      g_assert (IDE_IS_SEARCH_RESULT (item));
+
+      g_list_store_insert_sorted (r->store,
+                                  item,
+                                  (GCompareDataFunc)ide_search_result_compare,
+                                  NULL);
+
+    }
+
+cleanup:
+  r->outstanding--;
+
+  if (r->outstanding == 0)
+    g_task_return_pointer (task, g_steal_pointer (&r->store), g_object_unref);
+}
+
+static void
+ide_search_engine_search_foreach (PeasExtensionSet *set,
+                                  PeasPluginInfo   *plugin_info,
+                                  PeasExtension    *exten,
+                                  gpointer          user_data)
+{
+  IdeSearchProvider *provider = (IdeSearchProvider *)exten;
+  Request *r = user_data;
+
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_SEARCH_PROVIDER (provider));
+  g_assert (r != NULL);
+  g_assert (G_IS_TASK (r->task));
+  g_assert (G_IS_LIST_STORE (r->store));
+
+  r->outstanding++;
+
+  ide_search_provider_search_async (provider,
+                                    r->query,
+                                    r->max_results,
+                                    g_task_get_cancellable (r->task),
+                                    ide_search_engine_search_cb,
+                                    g_object_ref (r->task));
+}
+
+void
+ide_search_engine_search_async (IdeSearchEngine     *self,
+                                const gchar         *query,
+                                GCancellable        *cancellable,
+                                GAsyncReadyCallback  callback,
+                                gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GListStore) store = NULL;
+  Request *r;
+
+  g_return_if_fail (IDE_IS_SEARCH_ENGINE (self));
+  g_return_if_fail (query != NULL);
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_search_engine_search_async);
+  g_task_set_priority (task, G_PRIORITY_LOW);
+
+  r = request_new ();
+  r->query = g_strdup (query);
+  r->max_results = 25;
+  r->task = task;
+  r->store = g_list_store_new (IDE_TYPE_SEARCH_RESULT);
+  r->outstanding = 0;
+  g_task_set_task_data (task, r, (GDestroyNotify)request_destroy);
+
+  peas_extension_set_foreach (self->extensions,
+                              ide_search_engine_search_foreach,
+                              r);
+
+  self->active_count += r->outstanding;
+
+  if (r->outstanding == 0)
+    g_task_return_pointer (task,
+                           g_object_ref (r->store),
+                           g_object_unref);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
+}
+
+/**
+ * ide_search_engine_search_finish:
+ * @self: a #IdeSearchEngine
+ * @result: a #GAsyncResult
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to ide_search_engine_search_async().
+ *
+ * The result is a #GListModel of #IdeSearchResult when successful.
+ *
+ * Returns: (transfer full): A #GListModel of #IdeSearchResult items.
+ */
+GListModel *
+ide_search_engine_search_finish (IdeSearchEngine  *self,
+                                 GAsyncResult     *result,
+                                 GError          **error)
+{
+  g_return_val_if_fail (IDE_IS_SEARCH_ENGINE (self), NULL);
+  g_return_val_if_fail (G_IS_TASK (result), NULL);
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
diff --git a/libide/search/ide-search-engine.h b/libide/search/ide-search-engine.h
index 38dab7a..2f14b1a 100644
--- a/libide/search/ide-search-engine.h
+++ b/libide/search/ide-search-engine.h
@@ -1,6 +1,6 @@
 /* ide-search-engine.h
  *
- * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ * Copyright (C) 2015-2017 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
@@ -27,8 +27,16 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (IdeSearchEngine, ide_search_engine, IDE, SEARCH_ENGINE, IdeObject)
 
-IdeSearchContext *ide_search_engine_search (IdeSearchEngine   *self,
-                                            const gchar       *search_terms);
+IdeSearchEngine *ide_search_engine_new           (void);
+gboolean         ide_search_engine_get_busy      (IdeSearchEngine      *self);
+void             ide_search_engine_search_async  (IdeSearchEngine      *self,
+                                                  const gchar          *query,
+                                                  GCancellable         *cancellable,
+                                                  GAsyncReadyCallback   callback,
+                                                  gpointer              user_data);
+GListModel      *ide_search_engine_search_finish (IdeSearchEngine      *self,
+                                                  GAsyncResult         *result,
+                                                  GError              **error);
 
 G_END_DECLS
 
diff --git a/libide/search/ide-search-entry.c b/libide/search/ide-search-entry.c
new file mode 100644
index 0000000..1d623b8
--- /dev/null
+++ b/libide/search/ide-search-entry.c
@@ -0,0 +1,128 @@
+/* ide-search-entry.c
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#define G_LOG_DOMAIN "ide-search-entry"
+
+#include "ide-context.h"
+#include "ide-macros.h"
+
+#include "editor/ide-editor-perspective.h"
+#include "search/ide-search-engine.h"
+#include "search/ide-search-entry.h"
+#include "search/ide-search-result.h"
+#include "util/ide-gtk.h"
+#include "workbench/ide-workbench.h"
+
+struct _IdeSearchEntry
+{
+  DzlSuggestionEntry parent_instance;
+};
+
+G_DEFINE_TYPE (IdeSearchEntry, ide_search_entry, DZL_TYPE_SUGGESTION_ENTRY)
+
+static void
+ide_search_entry_search_cb (GObject      *object,
+                            GAsyncResult *result,
+                            gpointer      user_data)
+{
+  IdeSearchEngine *engine = (IdeSearchEngine *)object;
+  g_autoptr(IdeSearchEntry) self = user_data;
+  g_autoptr(GListModel) suggestions = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IDE_IS_SEARCH_ENTRY (self));
+  g_assert (IDE_IS_SEARCH_ENGINE (engine));
+
+  suggestions = ide_search_engine_search_finish (engine, result, &error);
+
+  if (error != NULL)
+    {
+      /* TODO: Elevate to workbench message once we have that capability */
+      g_warning ("%s", error->message);
+      return;
+    }
+
+  g_assert (suggestions != NULL);
+  g_assert (G_IS_LIST_MODEL (suggestions));
+  g_assert (g_type_is_a (g_list_model_get_item_type (suggestions), DZL_TYPE_SUGGESTION));
+
+  dzl_suggestion_entry_set_model (DZL_SUGGESTION_ENTRY (self), suggestions);
+}
+
+static void
+ide_search_entry_changed (IdeSearchEntry *self)
+{
+  IdeSearchEngine *engine;
+  IdeContext *context;
+  const gchar *typed_text;
+
+  g_assert (IDE_IS_SEARCH_ENTRY (self));
+
+  if (NULL == (context = ide_widget_get_context (GTK_WIDGET (self))))
+    return;
+
+  typed_text = dzl_suggestion_entry_get_typed_text (DZL_SUGGESTION_ENTRY (self));
+
+  if (ide_str_empty0 (typed_text))
+    {
+      dzl_suggestion_entry_set_model (DZL_SUGGESTION_ENTRY (self), NULL);
+      return;
+    }
+
+  engine = ide_context_get_search_engine (context);
+
+  ide_search_engine_search_async (engine,
+                                  typed_text,
+                                  NULL,
+                                  ide_search_entry_search_cb,
+                                  g_object_ref (self));
+}
+
+static void
+suggestion_activated (DzlSuggestionEntry *entry,
+                      DzlSuggestion      *suggestion)
+{
+  g_autoptr(IdeSourceLocation) location = NULL;
+
+  g_assert (IDE_IS_SEARCH_ENTRY (entry));
+  g_assert (IDE_IS_SEARCH_RESULT (suggestion));
+
+  location = ide_search_result_get_source_location (IDE_SEARCH_RESULT (suggestion));
+
+  if (location != NULL)
+    {
+      IdeWorkbench *workbench = ide_widget_get_workbench (GTK_WIDGET (entry));
+      IdePerspective *perspective = ide_workbench_get_perspective_by_name (workbench, "editor");
+
+      ide_editor_perspective_focus_location (IDE_EDITOR_PERSPECTIVE (perspective), location);
+    }
+}
+
+static void
+ide_search_entry_class_init (IdeSearchEntryClass *klass)
+{
+  DzlSuggestionEntryClass *suggestion_entry_class = DZL_SUGGESTION_ENTRY_CLASS (klass);
+
+  suggestion_entry_class->suggestion_activated = suggestion_activated;
+}
+
+static void
+ide_search_entry_init (IdeSearchEntry *self)
+{
+  g_signal_connect (self, "changed", G_CALLBACK (ide_search_entry_changed), NULL);
+}
diff --git a/libide/search/ide-omni-search-row.h b/libide/search/ide-search-entry.h
similarity index 52%
rename from libide/search/ide-omni-search-row.h
rename to libide/search/ide-search-entry.h
index 2e950ec..e594d1e 100644
--- a/libide/search/ide-omni-search-row.h
+++ b/libide/search/ide-search-entry.h
@@ -1,6 +1,6 @@
-/* ide-omni-search-row.h
+/* ide-search-entry.h
  *
- * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ * Copyright (C) 2015-2017 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
@@ -16,23 +16,19 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef IDE_OMNI_SEARCH_ROW_H
-#define IDE_OMNI_SEARCH_ROW_H
+#ifndef IDE_SEARCH_ENTRY_H
+#define IDE_SEARCH_ENTRY_H
 
-#include <gtk/gtk.h>
-
-#include "ide-search-result.h"
+#include <dazzle.h>
 
 G_BEGIN_DECLS
 
-#define IDE_TYPE_OMNI_SEARCH_ROW (ide_omni_search_row_get_type())
+#define IDE_TYPE_SEARCH_ENTRY (ide_search_entry_get_type())
 
-G_DECLARE_FINAL_TYPE (IdeOmniSearchRow, ide_omni_search_row, IDE, OMNI_SEARCH_ROW, GtkListBoxRow)
+G_DECLARE_FINAL_TYPE (IdeSearchEntry, ide_search_entry, IDE, SEARCH_ENTRY, DzlSuggestionEntry)
 
-IdeSearchResult *ide_omni_search_row_get_result (IdeOmniSearchRow *row);
-void             ide_omni_search_row_set_result (IdeOmniSearchRow *row,
-                                                 IdeSearchResult  *result);
+GtkWidget *ide_search_entry_new (void);
 
 G_END_DECLS
 
-#endif /* IDE_OMNI_SEARCH_ROW_H */
+#endif /* IDE_SEARCH_ENTRY_H */
diff --git a/libide/search/ide-search-provider.c b/libide/search/ide-search-provider.c
index 187d607..0924e1c 100644
--- a/libide/search/ide-search-provider.c
+++ b/libide/search/ide-search-provider.c
@@ -1,6 +1,6 @@
 /* ide-search-provider.c
  *
- * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ * Copyright (C) 2017 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
@@ -16,127 +16,83 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <glib/gi18n.h>
+#define G_LOG_DOMAIN "ide-search-provider"
 
-#include "ide-context.h"
-#include "ide-search-context.h"
 #include "ide-search-provider.h"
-#include "ide-search-result.h"
 
 G_DEFINE_INTERFACE (IdeSearchProvider, ide_search_provider, IDE_TYPE_OBJECT)
 
-static const gchar *
-ide_search_provider_real_get_verb (IdeSearchProvider *self)
+static void
+ide_search_provider_real_search_async (IdeSearchProvider   *self,
+                                       const gchar         *query,
+                                       guint                max_results,
+                                       GCancellable        *cancellable,
+                                       GAsyncReadyCallback  callback,
+                                       gpointer             user_data)
 {
-  return "";
-}
+  g_autoptr(GTask) task = NULL;
 
-static gint
-ide_search_provider_real_get_priority (IdeSearchProvider *self)
-{
-  return -1;
-}
+  g_assert (IDE_IS_SEARCH_PROVIDER (self));
+  g_assert (query != NULL);
 
-static gunichar
-ide_search_provider_real_get_prefix (IdeSearchProvider *self)
-{
-  return '\0';
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_return_new_error (task,
+                           G_IO_ERROR,
+                           G_IO_ERROR_NOT_SUPPORTED,
+                           "search not implemented");
 }
 
-static GtkWidget *
-ide_search_provider_real_create_row (IdeSearchProvider *self,
-                                     IdeSearchResult   *result)
+static GPtrArray *
+ide_search_provider_real_search_finish (IdeSearchProvider  *self,
+                                        GAsyncResult       *result,
+                                        GError            **error)
 {
-  return NULL;
-}
+  g_assert (IDE_IS_SEARCH_PROVIDER (self));
+  g_assert (G_IS_TASK (result));
 
-static void
-ide_search_provider_real_activate (IdeSearchProvider *self,
-                                   GtkWidget         *row,
-                                   IdeSearchResult   *result)
-{
+  return g_task_propagate_pointer (G_TASK (result), error);
 }
 
 static void
 ide_search_provider_default_init (IdeSearchProviderInterface *iface)
 {
-  iface->get_verb = ide_search_provider_real_get_verb;
-  iface->get_priority = ide_search_provider_real_get_priority;
-  iface->get_prefix = ide_search_provider_real_get_prefix;
-  iface->create_row = ide_search_provider_real_create_row;
-  iface->activate = ide_search_provider_real_activate;
-}
-
-const gchar *
-ide_search_provider_get_verb (IdeSearchProvider *provider)
-{
-  g_return_val_if_fail (IDE_IS_SEARCH_PROVIDER (provider), NULL);
-
-  return IDE_SEARCH_PROVIDER_GET_IFACE (provider)->get_verb (provider);
-}
-
-gint
-ide_search_provider_get_priority (IdeSearchProvider *provider)
-{
-  g_return_val_if_fail (IDE_IS_SEARCH_PROVIDER (provider), -1);
-
-  return IDE_SEARCH_PROVIDER_GET_IFACE (provider)->get_priority (provider);
-}
-
-gunichar
-ide_search_provider_get_prefix (IdeSearchProvider *provider)
-{
-  g_return_val_if_fail (IDE_IS_SEARCH_PROVIDER (provider), -1);
-
-  return IDE_SEARCH_PROVIDER_GET_IFACE (provider)->get_prefix (provider);
+  iface->search_async = ide_search_provider_real_search_async;
+  iface->search_finish = ide_search_provider_real_search_finish;
 }
 
 void
-ide_search_provider_populate (IdeSearchProvider *provider,
-                              IdeSearchContext  *context,
-                              const gchar       *search_terms,
-                              gsize              max_results,
-                              GCancellable      *cancellable)
+ide_search_provider_search_async (IdeSearchProvider   *self,
+                                  const gchar         *query,
+                                  guint                max_results,
+                                  GCancellable        *cancellable,
+                                  GAsyncReadyCallback  callback,
+                                  gpointer             user_data)
 {
-  g_return_if_fail (IDE_IS_SEARCH_PROVIDER (provider));
-  g_return_if_fail (IDE_IS_SEARCH_CONTEXT (context));
-  g_return_if_fail (search_terms != NULL);
+  g_return_if_fail (IDE_IS_SEARCH_PROVIDER (self));
+  g_return_if_fail (query != NULL);
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
-  return IDE_SEARCH_PROVIDER_GET_IFACE (provider)->populate (provider,
-                                                             context,
-                                                             search_terms,
-                                                             max_results,
-                                                             cancellable);
+  IDE_SEARCH_PROVIDER_GET_IFACE (self)->search_async (self, query, max_results, cancellable, callback, 
user_data);
 }
 
 /**
- * ide_search_provider_create_row:
- * @provider: A #IdeSearchProvider.
- * @result: A #IdeSearchResult.
+ * ide_search_provider_search_finish:
+ * @self: a #IdeSearchProvider
+ * @result: a #GAsyncResult
+ * @error: a location for a #GError, or %NULL
  *
- * Create a row to display the search result.
+ * Completes a request to a search provider.
  *
- * Returns: (transfer full): A #GtkWidget.
+ * Returns: (transfer container) (element-type Ide.SearchResult): A #GPtrArray
+ *    of #IdeSearchResult elements.
  */
-GtkWidget *
-ide_search_provider_create_row (IdeSearchProvider *self,
-                                IdeSearchResult   *result)
+GPtrArray *
+ide_search_provider_search_finish (IdeSearchProvider  *self,
+                                   GAsyncResult       *result,
+                                   GError            **error)
 {
   g_return_val_if_fail (IDE_IS_SEARCH_PROVIDER (self), NULL);
-  g_return_val_if_fail (IDE_IS_SEARCH_RESULT (result), NULL);
-
-  return IDE_SEARCH_PROVIDER_GET_IFACE (self)->create_row (self, result);
-}
-
-void
-ide_search_provider_activate (IdeSearchProvider *self,
-                              GtkWidget         *row,
-                              IdeSearchResult   *result)
-{
-  g_return_if_fail (IDE_IS_SEARCH_PROVIDER (self));
-  g_return_if_fail (GTK_IS_WIDGET (row));
-  g_return_if_fail (IDE_IS_SEARCH_RESULT (result));
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
 
-  return IDE_SEARCH_PROVIDER_GET_IFACE (self)->activate (self, row, result);
+  return IDE_SEARCH_PROVIDER_GET_IFACE (self)->search_finish (self, result, error);
 }
diff --git a/libide/search/ide-search-provider.h b/libide/search/ide-search-provider.h
index c6f3448..6000cf9 100644
--- a/libide/search/ide-search-provider.h
+++ b/libide/search/ide-search-provider.h
@@ -1,6 +1,6 @@
 /* ide-search-provider.h
  *
- * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ * Copyright (C) 2015-2017 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
@@ -19,8 +19,6 @@
 #ifndef IDE_SEARCH_PROVIDER_H
 #define IDE_SEARCH_PROVIDER_H
 
-#include <gtk/gtk.h>
-
 #include "ide-object.h"
 
 G_BEGIN_DECLS
@@ -31,36 +29,28 @@ G_DECLARE_INTERFACE (IdeSearchProvider, ide_search_provider, IDE, SEARCH_PROVIDE
 
 struct _IdeSearchProviderInterface
 {
-  GTypeInterface parent_iface;
-
-  gunichar     (*get_prefix)   (IdeSearchProvider *provider);
-  gint         (*get_priority) (IdeSearchProvider *provider);
-  const gchar *(*get_verb)     (IdeSearchProvider *provider);
-  void         (*populate)     (IdeSearchProvider *provider,
-                                IdeSearchContext  *context,
-                                const gchar       *search_terms,
-                                gsize              max_results,
-                                GCancellable      *cancellable);
-  GtkWidget  *(*create_row)    (IdeSearchProvider *provider,
-                                IdeSearchResult   *result);
-  void        (*activate)      (IdeSearchProvider *provider,
-                                GtkWidget         *row,
-                                IdeSearchResult   *result);
+  GTypeInterface parent_interface;
+
+  void       (*search_async)  (IdeSearchProvider    *self,
+                               const gchar          *query,
+                               guint                 max_results,
+                               GCancellable         *cancellable,
+                               GAsyncReadyCallback   callback,
+                               gpointer              user_data);
+  GPtrArray *(*search_finish) (IdeSearchProvider    *self,
+                               GAsyncResult         *result,
+                               GError              **error);
 };
 
-gunichar     ide_search_provider_get_prefix   (IdeSearchProvider *provider);
-gint         ide_search_provider_get_priority (IdeSearchProvider *provider);
-const gchar *ide_search_provider_get_verb     (IdeSearchProvider *provider);
-void         ide_search_provider_populate     (IdeSearchProvider *provider,
-                                               IdeSearchContext  *context,
-                                               const gchar       *search_terms,
-                                               gsize              max_results,
-                                               GCancellable      *cancellable);
-GtkWidget   *ide_search_provider_create_row   (IdeSearchProvider *provider,
-                                               IdeSearchResult   *result);
-void         ide_search_provider_activate     (IdeSearchProvider *provider,
-                                               GtkWidget         *row,
-                                               IdeSearchResult   *result);
+void       ide_search_provider_search_async  (IdeSearchProvider    *self,
+                                              const gchar          *query,
+                                              guint                 max_results,
+                                              GCancellable         *cancellable,
+                                              GAsyncReadyCallback   callback,
+                                              gpointer              user_data);
+GPtrArray *ide_search_provider_search_finish (IdeSearchProvider    *self,
+                                              GAsyncResult         *result,
+                                              GError              **error);
 
 G_END_DECLS
 
diff --git a/libide/search/ide-search-reducer.c b/libide/search/ide-search-reducer.c
index 5d765de..e4c6592 100644
--- a/libide/search/ide-search-reducer.c
+++ b/libide/search/ide-search-reducer.c
@@ -16,61 +16,105 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "ide-search-context.h"
-#include "ide-search-provider.h"
+#define G_LOG_DOMAIN "ide-search-reducer"
+
 #include "ide-search-reducer.h"
 #include "ide-search-result.h"
 
+#define DEFAULT_MAX_ITEMS 1000
+
 void
 ide_search_reducer_init (IdeSearchReducer  *reducer,
-                         IdeSearchContext  *context,
-                         IdeSearchProvider *provider,
                          gsize              max_results)
 {
-  g_return_if_fail (reducer);
-  g_return_if_fail (IDE_IS_SEARCH_CONTEXT (context));
-  g_return_if_fail (IDE_IS_SEARCH_PROVIDER (provider));
+  g_return_if_fail (reducer != NULL);
 
-  reducer->context = context;
-  reducer->provider = provider;
   reducer->sequence = g_sequence_new (g_object_unref);
-  reducer->max_results = max_results ?: G_MAXSIZE;
+  reducer->max_results = max_results ? max_results : DEFAULT_MAX_ITEMS;
   reducer->count = 0;
 }
 
 void
 ide_search_reducer_destroy (IdeSearchReducer *reducer)
 {
-  g_return_if_fail (reducer);
+  g_return_if_fail (reducer != NULL);
 
-  if (reducer->sequence)
+  if (reducer->sequence != NULL)
     g_sequence_free (reducer->sequence);
 }
 
-void
-ide_search_reducer_push (IdeSearchReducer *reducer,
-                         IdeSearchResult  *result)
+GPtrArray *
+ide_search_reducer_free (IdeSearchReducer *reducer,
+                         gboolean          free_results)
 {
-  g_return_if_fail (reducer);
-  g_return_if_fail (IDE_IS_SEARCH_RESULT (result));
+  GPtrArray *ar;
+  GSequenceIter *iter;
+  GSequenceIter *end;
+
+  g_return_val_if_fail (reducer != NULL, NULL);
+
+  if (free_results)
+    {
+      ide_search_reducer_destroy (reducer);
+      return NULL;
+    }
+
+  ar = g_ptr_array_new_with_free_func (g_object_unref);
+
+  end = g_sequence_get_end_iter (reducer->sequence);
 
-  if (reducer->max_results <= g_sequence_get_length (reducer->sequence))
+  for (iter = g_sequence_get_begin_iter (reducer->sequence);
+       iter != end;
+       iter = g_sequence_iter_next (iter))
     {
-      GSequenceIter *iter;
-      IdeSearchResult *lowest;
-
-      /* Remove lowest score */
-      iter = g_sequence_get_begin_iter (reducer->sequence);
-      lowest = g_sequence_get (iter);
-      ide_search_context_remove_result (reducer->context, reducer->provider, lowest);
-      g_sequence_remove (iter);
+      IdeSearchResult *result = g_sequence_get (iter);
+      g_ptr_array_add (ar, g_object_ref (result));
     }
 
+  g_sequence_free (reducer->sequence);
+
+  reducer->sequence = NULL;
+  reducer->max_results = 0;
+  reducer->count = 0;
+
+  return ar;
+}
+
+/**
+ * ide_search_reducer_take:
+ * @self: an #IdeSearchReducer
+ * @result: (transfer full): an #IdeSearchResult
+ *
+ * Like ide_search_reducer_push() but takes ownership of @result by
+ * stealing the reference.
+ */
+void
+ide_search_reducer_take (IdeSearchReducer *reducer,
+                         IdeSearchResult  *result)
+{
+  g_assert (reducer != NULL);
+  g_assert (IDE_IS_SEARCH_RESULT (result));
+
+  if (reducer->count == reducer->max_results)
+    /* Remove lowest score */
+    g_sequence_remove (g_sequence_get_begin_iter (reducer->sequence));
+  else
+    reducer->count++;
+
   g_sequence_insert_sorted (reducer->sequence,
-                            g_object_ref (result),
+                            result,
                             (GCompareDataFunc)ide_search_result_compare,
                             NULL);
-  ide_search_context_add_result (reducer->context, reducer->provider, result);
+}
+
+void
+ide_search_reducer_push (IdeSearchReducer *reducer,
+                         IdeSearchResult  *result)
+{
+  g_assert (reducer != NULL);
+  g_assert (IDE_IS_SEARCH_RESULT (result));
+
+  ide_search_reducer_take (reducer, g_object_ref (result));
 }
 
 gboolean
@@ -81,12 +125,12 @@ ide_search_reducer_accepts (IdeSearchReducer *reducer,
 
   g_return_val_if_fail (reducer, FALSE);
 
-  if (g_sequence_get_length (reducer->sequence) < reducer->max_results)
+  if (reducer->count < reducer->max_results)
     return TRUE;
 
   iter = g_sequence_get_begin_iter (reducer->sequence);
 
-  if (iter)
+  if (iter != NULL)
     {
       IdeSearchResult *result;
 
diff --git a/libide/search/ide-search-reducer.h b/libide/search/ide-search-reducer.h
index e67b689..a38a852 100644
--- a/libide/search/ide-search-reducer.h
+++ b/libide/search/ide-search-reducer.h
@@ -25,22 +25,22 @@ G_BEGIN_DECLS
 
 typedef struct
 {
-  IdeSearchContext  *context;
-  IdeSearchProvider *provider;
-  GSequence         *sequence;
-  gsize              max_results;
-  gsize              count;
+  GSequence *sequence;
+  gsize      max_results;
+  gsize      count;
 } IdeSearchReducer;
 
-void     ide_search_reducer_init    (IdeSearchReducer  *reducer,
-                                     IdeSearchContext  *context,
-                                     IdeSearchProvider *provider,
-                                     gsize              max_results);
-gboolean ide_search_reducer_accepts (IdeSearchReducer  *reducer,
-                                     gfloat             score);
-void     ide_search_reducer_push    (IdeSearchReducer  *reducer,
-                                     IdeSearchResult   *result);
-void     ide_search_reducer_destroy (IdeSearchReducer  *reducer);
+void       ide_search_reducer_init    (IdeSearchReducer  *reducer,
+                                       gsize              max_results);
+gboolean   ide_search_reducer_accepts (IdeSearchReducer  *reducer,
+                                       gfloat             score);
+void       ide_search_reducer_take    (IdeSearchReducer  *reducer,
+                                       IdeSearchResult   *result);
+void       ide_search_reducer_push    (IdeSearchReducer  *reducer,
+                                       IdeSearchResult   *result);
+void       ide_search_reducer_destroy (IdeSearchReducer  *reducer);
+GPtrArray *ide_search_reducer_free    (IdeSearchReducer  *reducer,
+                                       gboolean           free_results);
 
 G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (IdeSearchReducer, ide_search_reducer_destroy)
 
diff --git a/libide/search/ide-search-result.c b/libide/search/ide-search-result.c
index 1fed0a2..ab8cfa8 100644
--- a/libide/search/ide-search-result.c
+++ b/libide/search/ide-search-result.c
@@ -1,6 +1,6 @@
 /* ide-search-result.c
  *
- * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ * Copyright (C) 2017 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
@@ -16,304 +16,206 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <glib/gi18n.h>
+#define G_LOG_DOMAIN "ide-search-result"
 
-#include "ide-context.h"
 #include "ide-search-result.h"
 
 typedef struct
 {
-  IdeSearchProvider *provider;
-  gchar             *title;
-  gchar             *subtitle;
-  gfloat             score;
+  gfloat score;
+  guint  priority;
 } IdeSearchResultPrivate;
 
-G_DEFINE_TYPE_WITH_PRIVATE (IdeSearchResult, ide_search_result, IDE_TYPE_OBJECT)
-
 enum {
   PROP_0,
-  PROP_PROVIDER,
   PROP_SCORE,
-  PROP_SUBTITLE,
-  PROP_TITLE,
-  LAST_PROP
+  PROP_PRIORITY,
+  N_PROPS
 };
 
-static GParamSpec *properties [LAST_PROP];
-
-IdeSearchResult *
-ide_search_result_new (IdeSearchProvider *provider,
-                       const gchar       *title,
-                       const gchar       *subtitle,
-                       gfloat             score)
-{
-  IdeSearchResult *self;
-  IdeContext *context;
-
-  g_return_val_if_fail (IDE_IS_SEARCH_PROVIDER (provider), NULL);
+G_DEFINE_TYPE_WITH_PRIVATE (IdeSearchResult, ide_search_result, DZL_TYPE_SUGGESTION)
 
-  context = ide_object_get_context (IDE_OBJECT (provider));
+static GParamSpec *properties [N_PROPS];
 
-  self = g_object_new (IDE_TYPE_SEARCH_RESULT,
-                       "context", context,
-                       "provider", provider,
-                       "score", score,
-                       "subtitle", subtitle,
-                       "title", title,
-                       NULL);
-
-  return self;
-}
-
-/**
- * ide_search_result_get_provider:
- * @result: A #IdeSearchResult.
- *
- * Gets the provider that created the search result.
- *
- * Returns: (transfer none): An #IdeSearchProvider.
- */
-IdeSearchProvider *
-ide_search_result_get_provider (IdeSearchResult *self)
+static void
+ide_search_result_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
 {
-  IdeSearchResultPrivate *priv = ide_search_result_get_instance_private (self);
+  IdeSearchResult *self = IDE_SEARCH_RESULT (object);
 
-  g_return_val_if_fail (IDE_IS_SEARCH_RESULT (self), NULL);
+  switch (prop_id)
+    {
+    case PROP_SCORE:
+      g_value_set_float (value, ide_search_result_get_score (self));
+      break;
 
-  return priv->provider;
+    case PROP_PRIORITY:
+      g_value_set_int (value, ide_search_result_get_priority (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
 }
 
 static void
-ide_search_result_set_provider (IdeSearchResult   *self,
-                                IdeSearchProvider *provider)
+ide_search_result_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
 {
-  IdeSearchResultPrivate *priv = ide_search_result_get_instance_private (self);
+  IdeSearchResult *self = IDE_SEARCH_RESULT (object);
 
-  g_return_if_fail (IDE_IS_SEARCH_RESULT (self));
-  g_return_if_fail (!provider || IDE_IS_SEARCH_PROVIDER (provider));
+  switch (prop_id)
+    {
+    case PROP_SCORE:
+      ide_search_result_set_score (self, g_value_get_float (value));
+      break;
 
-  g_set_object (&priv->provider, provider);
+    case PROP_PRIORITY:
+      ide_search_result_set_priority (self, g_value_get_int (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
 }
 
-const gchar *
-ide_search_result_get_title (IdeSearchResult *self)
+static void
+ide_search_result_class_init (IdeSearchResultClass *klass)
 {
-  IdeSearchResultPrivate *priv = ide_search_result_get_instance_private (self);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-  g_return_val_if_fail (IDE_IS_SEARCH_RESULT (self), NULL);
+  object_class->get_property = ide_search_result_get_property;
+  object_class->set_property = ide_search_result_set_property;
 
-  return priv->title;
+  properties [PROP_SCORE] =
+    g_param_spec_float ("score",
+                        "Score",
+                        "The score of the result",
+                        -G_MINFLOAT,
+                        G_MAXFLOAT,
+                        0.0f,
+                        (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_PRIORITY] =
+    g_param_spec_int ("priority",
+                      "Priority",
+                      "The priority of search result group",
+                      G_MININT,
+                      G_MAXINT,
+                      0,
+                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
 }
 
-const gchar *
-ide_search_result_get_subtitle (IdeSearchResult *self)
+static void
+ide_search_result_init (IdeSearchResult *self)
 {
-  IdeSearchResultPrivate *priv = ide_search_result_get_instance_private (self);
-
-  g_return_val_if_fail (IDE_IS_SEARCH_RESULT (self), NULL);
-
-  return priv->subtitle;
 }
 
-gfloat
-ide_search_result_get_score (IdeSearchResult *self)
+IdeSearchResult *
+ide_search_result_new (void)
 {
-  IdeSearchResultPrivate *priv = ide_search_result_get_instance_private (self);
-
-  g_return_val_if_fail (IDE_IS_SEARCH_RESULT (self), 0.0);
-
-  return priv->score;
+  return g_object_new (IDE_TYPE_SEARCH_RESULT, NULL);
 }
 
-static void
-ide_search_result_set_title (IdeSearchResult *self,
-                             const gchar     *title)
+gint
+ide_search_result_compare (gconstpointer a,
+                           gconstpointer b)
 {
-  IdeSearchResultPrivate *priv = ide_search_result_get_instance_private (self);
+  IdeSearchResult *ra = (IdeSearchResult *)a;
+  IdeSearchResult *rb = (IdeSearchResult *)b;
+  IdeSearchResultPrivate *priva = ide_search_result_get_instance_private (ra);
+  IdeSearchResultPrivate *privb = ide_search_result_get_instance_private (rb);
+  gint ret;
 
-  g_return_if_fail (IDE_IS_SEARCH_RESULT (self));
+  ret = priva->priority - privb->priority;
 
-  if (priv->title != title)
+  if (ret == 0)
     {
-      g_free (priv->title);
-      priv->title = g_strdup (title);
+      if (priva->score < privb->score)
+        priva->score = -1;
+      else if (priva->score > privb->score)
+        priva->score = 1;
     }
+
+  return ret;
 }
 
-static void
-ide_search_result_set_subtitle (IdeSearchResult *self,
-                                const gchar     *subtitle)
+gfloat
+ide_search_result_get_score (IdeSearchResult *self)
 {
   IdeSearchResultPrivate *priv = ide_search_result_get_instance_private (self);
 
-  g_return_if_fail (IDE_IS_SEARCH_RESULT (self));
+  g_return_val_if_fail (IDE_IS_SEARCH_RESULT (self), 0.0f);
 
-  if (priv->subtitle != subtitle)
-    {
-      g_free (priv->subtitle);
-      priv->subtitle = g_strdup (subtitle);
-    }
+  return priv->score;
 }
 
-static void
+void
 ide_search_result_set_score (IdeSearchResult *self,
                              gfloat           score)
 {
   IdeSearchResultPrivate *priv = ide_search_result_get_instance_private (self);
 
   g_return_if_fail (IDE_IS_SEARCH_RESULT (self));
-  g_return_if_fail (score >= 0.0);
-  g_return_if_fail (score <= 1.0);
 
-  priv->score = score;
+  if (priv->score != score)
+    {
+      priv->score = score;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SCORE]);
+    }
 }
 
 gint
-ide_search_result_compare (const IdeSearchResult *a,
-                           const IdeSearchResult *b)
-{
-  gfloat fa;
-  gfloat fb;
-
-  g_return_val_if_fail (IDE_IS_SEARCH_RESULT ((IdeSearchResult*)a), 0);
-  g_return_val_if_fail (IDE_IS_SEARCH_RESULT ((IdeSearchResult*)b), 0);
-
-  fa = ide_search_result_get_score ((IdeSearchResult *)a);
-  fb = ide_search_result_get_score ((IdeSearchResult *)b);
-
-  if (fa < fb)
-    return -1;
-  else if (fa > fb)
-    return 1;
-  else
-    return 0;
-}
-
-static void
-ide_search_result_finalize (GObject *object)
+ide_search_result_get_priority (IdeSearchResult *self)
 {
-  IdeSearchResult *self = (IdeSearchResult *)object;
   IdeSearchResultPrivate *priv = ide_search_result_get_instance_private (self);
 
-  g_clear_object (&priv->provider);
-  g_clear_pointer (&priv->title, g_free);
-  g_clear_pointer (&priv->subtitle, g_free);
+  g_return_val_if_fail (IDE_IS_SEARCH_RESULT (self), 0.0);
 
-  G_OBJECT_CLASS (ide_search_result_parent_class)->finalize (object);
+  return priv->priority;
 }
 
-static void
-ide_search_result_get_property (GObject    *object,
-                                guint       prop_id,
-                                GValue     *value,
-                                GParamSpec *pspec)
+void
+ide_search_result_set_priority (IdeSearchResult *self,
+                                gint             priority)
 {
-  IdeSearchResult *self = IDE_SEARCH_RESULT (object);
-
-  switch (prop_id)
-    {
-    case PROP_PROVIDER:
-      g_value_set_object (value, ide_search_result_get_provider (self));
-      break;
-
-    case PROP_TITLE:
-      g_value_set_string (value, ide_search_result_get_title (self));
-      break;
-
-    case PROP_SUBTITLE:
-      g_value_set_string (value, ide_search_result_get_subtitle (self));
-      break;
-
-    case PROP_SCORE:
-      g_value_set_float (value, ide_search_result_get_score (self));
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
-}
+  IdeSearchResultPrivate *priv = ide_search_result_get_instance_private (self);
 
-static void
-ide_search_result_set_property (GObject      *object,
-                                guint         prop_id,
-                                const GValue *value,
-                                GParamSpec   *pspec)
-{
-  IdeSearchResult *self = IDE_SEARCH_RESULT (object);
+  g_return_if_fail (IDE_IS_SEARCH_RESULT (self));
 
-  switch (prop_id)
+  if (priv->priority != priority)
     {
-    case PROP_PROVIDER:
-      ide_search_result_set_provider (self, g_value_get_object (value));
-      break;
-
-    case PROP_TITLE:
-      ide_search_result_set_title (self, g_value_get_string (value));
-      break;
-
-    case PROP_SUBTITLE:
-      ide_search_result_set_subtitle (self, g_value_get_string (value));
-      break;
-
-    case PROP_SCORE:
-      ide_search_result_set_score (self, g_value_get_float (value));
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      priv->priority = priority;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PRIORITY]);
     }
 }
 
-static void
-ide_search_result_class_init (IdeSearchResultClass *klass)
+/**
+ * ide_search_result_get_source_location:
+ * @self: a #IdeSearchResult
+ *
+ * Gets the file associated with the search result if any.
+ *
+ * Many search providers ultimately just open a file, so this may
+ * be used in lieu of handling the activate signal.
+ *
+ * Returns: (transfer full): An #IdeUri
+ */
+IdeSourceLocation *
+ide_search_result_get_source_location (IdeSearchResult *self)
 {
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-  object_class->finalize = ide_search_result_finalize;
-  object_class->get_property = ide_search_result_get_property;
-  object_class->set_property = ide_search_result_set_property;
+  g_return_val_if_fail (IDE_IS_SEARCH_RESULT (self), NULL);
 
-  properties [PROP_PROVIDER] =
-    g_param_spec_object ("provider",
-                         "Provider",
-                         "The Search Provider",
-                         IDE_TYPE_SEARCH_PROVIDER,
-                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
-  properties [PROP_TITLE] =
-    g_param_spec_string ("title",
-                         "Title",
-                         "The title of the search result.",
-                         NULL,
-                         (G_PARAM_READWRITE |
-                          G_PARAM_CONSTRUCT_ONLY |
-                          G_PARAM_STATIC_STRINGS));
-
-  properties [PROP_SUBTITLE] =
-    g_param_spec_string ("subtitle",
-                         "Subtitle",
-                         "The subtitle of the search result.",
-                         NULL,
-                         (G_PARAM_READWRITE |
-                          G_PARAM_CONSTRUCT_ONLY |
-                          G_PARAM_STATIC_STRINGS));
+  if (IDE_SEARCH_RESULT_GET_CLASS (self)->get_source_location != NULL)
+    return IDE_SEARCH_RESULT_GET_CLASS (self)->get_source_location (self);
 
-  properties [PROP_SCORE] =
-    g_param_spec_float ("score",
-                        "Score",
-                        "The score of the search result.",
-                        0.0,
-                        1.0,
-                        0.0,
-                        (G_PARAM_READWRITE |
-                         G_PARAM_CONSTRUCT_ONLY |
-                         G_PARAM_STATIC_STRINGS));
-
-  g_object_class_install_properties (object_class, LAST_PROP, properties);
-}
+  g_print ("nope\n");
 
-static void
-ide_search_result_init (IdeSearchResult *self)
-{
+  return NULL;
 }
diff --git a/libide/search/ide-search-result.h b/libide/search/ide-search-result.h
index 7a69e3b..21a7c18 100644
--- a/libide/search/ide-search-result.h
+++ b/libide/search/ide-search-result.h
@@ -1,6 +1,6 @@
 /* ide-search-result.h
  *
- * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ * Copyright (C) 2017 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
@@ -19,34 +19,43 @@
 #ifndef IDE_SEARCH_RESULT_H
 #define IDE_SEARCH_RESULT_H
 
-#include "ide-object.h"
+#include <gio/gio.h>
+#include <dazzle.h>
 
-#include "ide-search-provider.h"
+#include "diagnostics/ide-source-location.h"
 
 G_BEGIN_DECLS
 
 #define IDE_TYPE_SEARCH_RESULT (ide_search_result_get_type())
 
-G_DECLARE_DERIVABLE_TYPE (IdeSearchResult, ide_search_result, IDE, SEARCH_RESULT, IdeObject)
+G_DECLARE_DERIVABLE_TYPE (IdeSearchResult, ide_search_result, IDE, SEARCH_RESULT, DzlSuggestion)
 
 struct _IdeSearchResultClass
 {
-  IdeObjectClass parent;
-
-  void (*activate) (IdeSearchResult *result);
+  DzlSuggestionClass parent_class;
+
+  IdeSourceLocation *(*get_source_location) (IdeSearchResult *self);
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
 };
 
-IdeSearchResult   *ide_search_result_new          (IdeSearchProvider     *provider,
-                                                   const gchar           *title,
-                                                   const gchar           *subtitle,
-                                                   gfloat                 score);
-IdeSearchProvider *ide_search_result_get_provider (IdeSearchResult       *result);
-gfloat             ide_search_result_get_score    (IdeSearchResult       *result);
-const gchar       *ide_search_result_get_title    (IdeSearchResult       *result);
-const gchar       *ide_search_result_get_subtitle (IdeSearchResult       *result);
-gint               ide_search_result_compare      (const IdeSearchResult *a,
-                                                   const IdeSearchResult *b);
-void               ide_search_result_activate     (IdeSearchResult       *result);
+IdeSearchResult   *ide_search_result_new                 (void);
+IdeSourceLocation *ide_search_result_get_source_location (IdeSearchResult       *self);
+gint               ide_search_result_compare             (gconstpointer          a,
+                                                          gconstpointer          b);
+gint               ide_search_result_get_priority        (IdeSearchResult       *self);
+void               ide_search_result_set_priority        (IdeSearchResult       *self,
+                                                          gint                   priority);
+gfloat             ide_search_result_get_score           (IdeSearchResult       *self);
+void               ide_search_result_set_score           (IdeSearchResult       *self,
+                                                          gfloat                 score);
 
 G_END_DECLS
 
diff --git a/libide/workbench/ide-workbench-header-bar.c b/libide/workbench/ide-workbench-header-bar.c
index 7e9e64d..4a62703 100644
--- a/libide/workbench/ide-workbench-header-bar.c
+++ b/libide/workbench/ide-workbench-header-bar.c
@@ -21,7 +21,7 @@
 #include <dazzle.h>
 
 #include "application/ide-application.h"
-#include "search/ide-omni-search-entry.h"
+#include "search/ide-search-entry.h"
 #include "util/ide-gtk.h"
 #include "workbench/ide-perspective.h"
 #include "workbench/ide-workbench.h"
@@ -29,11 +29,11 @@
 
 typedef struct
 {
-  GtkMenuButton      *menu_button;
-  DzlPriorityBox     *right_box;
-  DzlPriorityBox     *left_box;
-  IdeOmniBar         *omni_bar;
-  IdeOmniSearchEntry *search_entry;
+  GtkMenuButton   *menu_button;
+  DzlPriorityBox  *right_box;
+  DzlPriorityBox  *left_box;
+  IdeOmniBar      *omni_bar;
+  IdeSearchEntry  *search_entry;
 } IdeWorkbenchHeaderBarPrivate;
 
 static void buildable_iface_init (GtkBuildableIface *iface);
@@ -59,6 +59,8 @@ ide_workbench_header_bar_class_init (IdeWorkbenchHeaderBarClass *klass)
   gtk_widget_class_bind_template_child_private (widget_class, IdeWorkbenchHeaderBar, omni_bar);
   gtk_widget_class_bind_template_child_private (widget_class, IdeWorkbenchHeaderBar, right_box);
   gtk_widget_class_bind_template_child_private (widget_class, IdeWorkbenchHeaderBar, search_entry);
+
+  g_type_ensure (IDE_TYPE_SEARCH_ENTRY);
 }
 
 static void
diff --git a/libide/workbench/ide-workbench-header-bar.ui b/libide/workbench/ide-workbench-header-bar.ui
index 1f83171..8705244 100644
--- a/libide/workbench/ide-workbench-header-bar.ui
+++ b/libide/workbench/ide-workbench-header-bar.ui
@@ -68,7 +68,7 @@
           </packing>
         </child>
         <child>
-          <object class="IdeOmniSearchEntry" id="search_entry">
+          <object class="IdeSearchEntry" id="search_entry">
             <property name="max-width-chars">30</property>
             <property name="placeholder-text" translatable="yes">Press Ctrl+. to search</property>
             <property name="visible">true</property>
diff --git a/plugins/file-search/gb-file-search-index.c b/plugins/file-search/gb-file-search-index.c
index 44d5384..c3301c9 100644
--- a/plugins/file-search/gb-file-search-index.c
+++ b/plugins/file-search/gb-file-search-index.c
@@ -261,6 +261,8 @@ gb_file_search_index_build_async (GbFileSearchIndex   *self,
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
   task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, gb_file_search_index_build_async);
+  g_task_set_priority (task, G_PRIORITY_LOW);
 
   if (self->root_directory == NULL)
     {
@@ -289,31 +291,27 @@ gb_file_search_index_build_finish (GbFileSearchIndex  *self,
   return g_task_propagate_boolean (task, error);
 }
 
-void
+GPtrArray *
 gb_file_search_index_populate (GbFileSearchIndex *self,
-                               IdeSearchContext  *context,
-                               IdeSearchProvider *provider,
-                               const gchar       *query)
+                               const gchar       *query,
+                               gsize              max_results)
 {
-  g_autoptr(GArray) ar = NULL;
   g_auto(IdeSearchReducer) reducer = { 0 };
   g_autoptr(GString) delimited = NULL;
+  g_autoptr(GArray) ar = NULL;
   const gchar *iter = query;
-  IdeContext *icontext;
-  gsize max_matches;
+  IdeContext *context;
   gsize i;
 
-  g_return_if_fail (GB_IS_FILE_SEARCH_INDEX (self));
-  g_return_if_fail (IDE_IS_SEARCH_CONTEXT (context));
-  g_return_if_fail (IDE_IS_SEARCH_PROVIDER (provider));
-  g_return_if_fail (query != NULL);
+  g_return_val_if_fail (GB_IS_FILE_SEARCH_INDEX (self), NULL);
+  g_return_val_if_fail (query != NULL, NULL);
 
   if (self->fuzzy == NULL)
-    return;
+    return g_ptr_array_new_with_free_func (g_object_unref);
 
-  icontext = ide_object_get_context (IDE_OBJECT (provider));
-  max_matches = ide_search_context_get_max_results (context);
-  ide_search_reducer_init (&reducer, context, provider, max_matches);
+  context = ide_object_get_context (IDE_OBJECT (self));
+
+  ide_search_reducer_init (&reducer, max_results);
 
   delimited = g_string_new (NULL);
 
@@ -325,13 +323,11 @@ gb_file_search_index_populate (GbFileSearchIndex *self,
         g_string_append_unichar (delimited, ch);
     }
 
-  ar = dzl_fuzzy_mutable_index_match (self->fuzzy, delimited->str, max_matches);
+  ar = dzl_fuzzy_mutable_index_match (self->fuzzy, delimited->str, max_results);
 
   for (i = 0; i < ar->len; i++)
     {
-      const DzlFuzzyMutableIndexMatch *match;
-
-      match = &g_array_index (ar, DzlFuzzyMutableIndexMatch, i);
+      const DzlFuzzyMutableIndexMatch *match = &g_array_index (ar, DzlFuzzyMutableIndexMatch, i);
 
       if (ide_search_reducer_accepts (&reducer, match->score))
         {
@@ -339,16 +335,18 @@ gb_file_search_index_populate (GbFileSearchIndex *self,
           g_autofree gchar *markup = NULL;
 
           markup = dzl_fuzzy_highlight (match->key, delimited->str, FALSE);
+
           result = g_object_new (GB_TYPE_FILE_SEARCH_RESULT,
-                                 "context", icontext,
-                                 "provider", provider,
+                                 "context", context,
                                  "score", match->score,
                                  "title", markup,
                                  "path", match->key,
                                  NULL);
-          ide_search_reducer_push (&reducer, IDE_SEARCH_RESULT (result));
+          ide_search_reducer_take (&reducer, g_steal_pointer (&result));
         }
     }
+
+  return ide_search_reducer_free (&reducer, FALSE);
 }
 
 gboolean
diff --git a/plugins/file-search/gb-file-search-index.h b/plugins/file-search/gb-file-search-index.h
index 6f1924a..5384b18 100644
--- a/plugins/file-search/gb-file-search-index.h
+++ b/plugins/file-search/gb-file-search-index.h
@@ -27,23 +27,22 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (GbFileSearchIndex, gb_file_search_index, GB, FILE_SEARCH_INDEX, IdeObject)
 
-void     gb_file_search_index_populate     (GbFileSearchIndex    *self,
-                                            IdeSearchContext     *context,
-                                            IdeSearchProvider    *provider,
-                                            const gchar          *query);
-void     gb_file_search_index_build_async  (GbFileSearchIndex    *self,
-                                            GCancellable         *cancellable,
-                                            GAsyncReadyCallback   callback,
-                                            gpointer              user_data);
-gboolean gb_file_search_index_build_finish (GbFileSearchIndex    *self,
-                                            GAsyncResult         *result,
-                                            GError              **error);
-gboolean gb_file_search_index_contains     (GbFileSearchIndex    *self,
-                                            const gchar          *relative_path);
-void     gb_file_search_index_insert       (GbFileSearchIndex    *self,
-                                            const gchar          *relative_path);
-void     gb_file_search_index_remove       (GbFileSearchIndex    *self,
-                                            const gchar          *relative_path);
+GPtrArray *gb_file_search_index_populate     (GbFileSearchIndex    *self,
+                                              const gchar          *query,
+                                              gsize                 max_results);
+void       gb_file_search_index_build_async  (GbFileSearchIndex    *self,
+                                              GCancellable         *cancellable,
+                                              GAsyncReadyCallback   callback,
+                                              gpointer              user_data);
+gboolean   gb_file_search_index_build_finish (GbFileSearchIndex    *self,
+                                              GAsyncResult         *result,
+                                              GError              **error);
+gboolean   gb_file_search_index_contains     (GbFileSearchIndex    *self,
+                                              const gchar          *relative_path);
+void       gb_file_search_index_insert       (GbFileSearchIndex    *self,
+                                              const gchar          *relative_path);
+void       gb_file_search_index_remove       (GbFileSearchIndex    *self,
+                                              const gchar          *relative_path);
 
 G_END_DECLS
 
diff --git a/plugins/file-search/gb-file-search-provider.c b/plugins/file-search/gb-file-search-provider.c
index 7cc142c..e4b5659 100644
--- a/plugins/file-search/gb-file-search-provider.c
+++ b/plugins/file-search/gb-file-search-provider.c
@@ -35,33 +35,45 @@ G_DEFINE_DYNAMIC_TYPE_EXTENDED (GbFileSearchProvider,
                                 gb_file_search_provider,
                                 IDE_TYPE_OBJECT,
                                 0,
-                                G_IMPLEMENT_INTERFACE (IDE_TYPE_SEARCH_PROVIDER,
-                                                       search_provider_iface_init))
-
-static const gchar *
-gb_file_search_provider_get_verb (IdeSearchProvider *provider)
-{
-  return _("Switch To");
-}
+                                G_IMPLEMENT_INTERFACE (IDE_TYPE_SEARCH_PROVIDER, search_provider_iface_init))
 
 static void
-gb_file_search_provider_populate (IdeSearchProvider *provider,
-                                  IdeSearchContext  *context,
-                                  const gchar       *search_terms,
-                                  gsize              max_results,
-                                  GCancellable      *cancellable)
+gb_file_search_provider_search_async (IdeSearchProvider   *provider,
+                                      const gchar         *search_terms,
+                                      guint                max_results,
+                                      GCancellable        *cancellable,
+                                      GAsyncReadyCallback  callback,
+                                      gpointer             user_data)
 {
   GbFileSearchProvider *self = (GbFileSearchProvider *)provider;
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GPtrArray) results = NULL;
 
-  g_assert (IDE_IS_SEARCH_PROVIDER (provider));
-  g_assert (IDE_IS_SEARCH_CONTEXT (context));
+  g_assert (GB_IS_FILE_SEARCH_PROVIDER (self));
   g_assert (search_terms != NULL);
   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
 
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, gb_file_search_provider_search_async);
+  g_task_set_priority (task, G_PRIORITY_LOW);
+
   if (self->index != NULL)
-    gb_file_search_index_populate (self->index, context, provider, search_terms);
+    results = gb_file_search_index_populate (self->index, search_terms, max_results);
+  else
+    results = g_ptr_array_new_with_free_func (g_object_unref);
+
+  g_task_return_pointer (task, g_steal_pointer (&results), (GDestroyNotify)g_ptr_array_unref);
+}
+
+static GPtrArray *
+gb_file_search_provider_search_finish (IdeSearchProvider  *provider,
+                                       GAsyncResult       *result,
+                                       GError            **error)
+{
+  g_assert (GB_IS_FILE_SEARCH_PROVIDER (provider));
+  g_assert (G_IS_TASK (result));
 
-  ide_search_context_provider_completed (context, provider);
+  return g_task_propagate_pointer (G_TASK (result), error);
 }
 
 static void
@@ -167,20 +179,7 @@ gb_file_search_provider_build_cb (GObject      *object,
   g_set_object (&self->index, index);
 }
 
-static GtkWidget *
-gb_file_search_provider_create_row (IdeSearchProvider *provider,
-                                    IdeSearchResult   *result)
-{
-  g_assert (IDE_IS_SEARCH_PROVIDER (provider));
-  g_assert (IDE_IS_SEARCH_RESULT (result));
-
-  return g_object_new (IDE_TYPE_OMNI_SEARCH_ROW,
-                       "icon-name", "text-x-generic-symbolic",
-                       "result", result,
-                       "visible", TRUE,
-                       NULL);
-}
-
+#if 0
 static void
 gb_file_search_provider_activate (IdeSearchProvider *provider,
                                   GtkWidget         *row,
@@ -218,12 +217,7 @@ gb_file_search_provider_activate (IdeSearchProvider *provider,
                                       NULL);
     }
 }
-
-static gint
-gb_file_search_provider_get_priority (IdeSearchProvider *provider)
-{
-  return 0;
-}
+#endif
 
 static void
 gb_file_search_provider_vcs_changed_cb (GbFileSearchProvider *self,
@@ -342,11 +336,8 @@ gb_file_search_provider_init (GbFileSearchProvider *self)
 static void
 search_provider_iface_init (IdeSearchProviderInterface *iface)
 {
-  iface->populate = gb_file_search_provider_populate;
-  iface->get_verb = gb_file_search_provider_get_verb;
-  iface->create_row = gb_file_search_provider_create_row;
-  iface->activate = gb_file_search_provider_activate;
-  iface->get_priority = gb_file_search_provider_get_priority;
+  iface->search_async = gb_file_search_provider_search_async;
+  iface->search_finish = gb_file_search_provider_search_finish;
 }
 
 void
diff --git a/plugins/file-search/gb-file-search-result.c b/plugins/file-search/gb-file-search-result.c
index 986d034..0cde0e0 100644
--- a/plugins/file-search/gb-file-search-result.c
+++ b/plugins/file-search/gb-file-search-result.c
@@ -22,26 +22,49 @@
 
 struct _GbFileSearchResult
 {
-  IdeSearchResult parent_instance;
-  gchar *path;
+  IdeSearchResult  parent_instance;
+
+  IdeContext      *context;
+  gchar           *path;
 };
 
 G_DEFINE_TYPE (GbFileSearchResult, gb_file_search_result, IDE_TYPE_SEARCH_RESULT)
 
 enum {
   PROP_0,
+  PROP_CONTEXT,
   PROP_PATH,
   LAST_PROP
 };
 
 static GParamSpec *properties [LAST_PROP];
 
+static IdeSourceLocation *
+gb_file_search_result_get_source_location (IdeSearchResult *result)
+{
+  GbFileSearchResult *self = (GbFileSearchResult *)result;
+  g_autoptr(GFile) file = NULL;
+  g_autoptr(IdeFile) ifile = NULL;
+  IdeVcs *vcs;
+  GFile *workdir;
+
+  g_return_val_if_fail (GB_IS_FILE_SEARCH_RESULT (self), NULL);
+
+  vcs = ide_context_get_vcs (self->context);
+  workdir = ide_vcs_get_working_directory (vcs);
+  file = g_file_get_child (workdir, self->path);
+  ifile = ide_file_new (self->context, file);
+
+  return ide_source_location_new (ifile, 0, 0, 0);
+}
+
 static void
 gb_file_search_result_finalize (GObject *object)
 {
   GbFileSearchResult *self = (GbFileSearchResult *)object;
 
-  g_free (self->path);
+  ide_clear_weak_pointer (&self->context);
+  g_clear_pointer (&self->path, g_free);
 
   G_OBJECT_CLASS (gb_file_search_result_parent_class)->finalize (object);
 }
@@ -75,6 +98,10 @@ gb_file_search_result_set_property (GObject      *object,
 
   switch (prop_id)
     {
+    case PROP_CONTEXT:
+      ide_set_weak_pointer (&self->context, g_value_get_object (value));
+      break;
+
     case PROP_PATH:
       self->path = g_value_dup_string (value);
       break;
@@ -88,11 +115,21 @@ static void
 gb_file_search_result_class_init (GbFileSearchResultClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  IdeSearchResultClass *result_class = IDE_SEARCH_RESULT_CLASS (klass);
 
   object_class->finalize = gb_file_search_result_finalize;
   object_class->get_property = gb_file_search_result_get_property;
   object_class->set_property = gb_file_search_result_set_property;
 
+  result_class->get_source_location = gb_file_search_result_get_source_location;
+
+  properties [PROP_CONTEXT] =
+    g_param_spec_object ("context",
+                         "Context",
+                         "The context for the result",
+                         IDE_TYPE_CONTEXT,
+                         (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
   properties [PROP_PATH] =
     g_param_spec_string ("path",
                          "Path",


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