[gnome-builder] libide/search: wrap search engine results in IdeSearchResults



commit 5fa89d9766650b91db8318807f1bcdb0f144e535
Author: Christian Hergert <chergert redhat com>
Date:   Fri Jul 15 17:03:43 2022 -0700

    libide/search: wrap search engine results in IdeSearchResults
    
    This will allow us to do followup filtering without having to requery
    the individual search providers. That's useful as currently we have to
    inflate all the search results anyway, and so we'd like to avoid doing
    that on most keypresses.
    
    Instead, the popover (in a followup commit) will be able to simply
    refilter the previous set and only if it fails, discard the result.

 src/libide/gui/ide-search-popover.c            |   6 +-
 src/libide/search/ide-search-engine.c          |  42 ++++--
 src/libide/search/ide-search-engine.h          |  33 ++---
 src/libide/search/ide-search-result.h          |   6 +-
 src/libide/search/ide-search-results-private.h |  30 +++++
 src/libide/search/ide-search-results.c         | 173 +++++++++++++++++++++++++
 src/libide/search/ide-search-results.h         |  40 ++++++
 src/libide/search/libide-search.h              |   1 +
 src/libide/search/meson.build                  |   2 +
 9 files changed, 299 insertions(+), 34 deletions(-)
---
diff --git a/src/libide/gui/ide-search-popover.c b/src/libide/gui/ide-search-popover.c
index 69093c62d..aed346b63 100644
--- a/src/libide/gui/ide-search-popover.c
+++ b/src/libide/gui/ide-search-popover.c
@@ -117,7 +117,7 @@ ide_search_popover_search_cb (GObject      *object,
 {
   IdeSearchEngine *search_engine = (IdeSearchEngine *)object;
   g_autoptr(IdeSearchPopover) self = user_data;
-  g_autoptr(GListModel) results = NULL;
+  g_autoptr(IdeSearchResults) results = NULL;
   g_autoptr(GError) error = NULL;
 
   IDE_ENTRY;
@@ -132,13 +132,13 @@ ide_search_popover_search_cb (GObject      *object,
   if (error != NULL)
     g_debug ("Search failed: %s", error->message);
 
-  gtk_single_selection_set_model (self->selection, results);
+  gtk_single_selection_set_model (self->selection, G_LIST_MODEL (results));
 
   if (self->activate_after_search)
     {
       self->activate_after_search = FALSE;
 
-      if (results != NULL && g_list_model_get_n_items (results) > 0)
+      if (results != NULL && g_list_model_get_n_items (G_LIST_MODEL (results)) > 0)
         {
           IdeSearchResult *selected = gtk_single_selection_get_selected_item (self->selection);
 
diff --git a/src/libide/search/ide-search-engine.c b/src/libide/search/ide-search-engine.c
index 580a35f27..83237587c 100644
--- a/src/libide/search/ide-search-engine.c
+++ b/src/libide/search/ide-search-engine.c
@@ -30,6 +30,7 @@
 #include "ide-search-engine.h"
 #include "ide-search-provider.h"
 #include "ide-search-result.h"
+#include "ide-search-results-private.h"
 
 #define DEFAULT_MAX_RESULTS 50
 
@@ -43,11 +44,11 @@ struct _IdeSearchEngine
 
 typedef struct
 {
-  IdeTask       *task;
-  gchar         *query;
-  GListStore    *store;
-  guint          outstanding;
-  guint          max_results;
+  IdeTask    *task;
+  char       *query;
+  GListStore *store;
+  guint       outstanding;
+  guint       max_results;
 } Request;
 
 enum {
@@ -258,11 +259,14 @@ cleanup:
   r->outstanding--;
 
   if (r->outstanding == 0)
-    ide_task_return_pointer (task,
-                             g_object_new (GTK_TYPE_FLATTEN_LIST_MODEL,
-                                           "model", r->store,
-                                           NULL),
-                             g_object_unref);
+    {
+      g_autoptr(GtkFlattenListModel) flatten = NULL;
+
+      flatten = gtk_flatten_list_model_new (G_LIST_MODEL (g_steal_pointer (&r->store)));
+      ide_task_return_pointer (task,
+                               _ide_search_results_new (G_LIST_MODEL (flatten), r->query),
+                               g_object_unref);
+    }
 }
 
 static void
@@ -322,7 +326,7 @@ ide_search_engine_search_foreach_custom_provider (gpointer data,
 
 void
 ide_search_engine_search_async (IdeSearchEngine     *self,
-                                const gchar         *query,
+                                const char          *query,
                                 guint                max_results,
                                 GCancellable        *cancellable,
                                 GAsyncReadyCallback  callback,
@@ -372,19 +376,29 @@ ide_search_engine_search_async (IdeSearchEngine     *self,
  *
  * Completes an asynchronous request to ide_search_engine_search_async().
  *
- * The result is a #GListModel of #IdeSearchResult when successful.
+ * The result is a #GListModel of #IdeSearchResult when successful. The type
+ * is #IdeSearchResults which allows you to do additional filtering on the
+ * result set instead of querying providers again.
  *
  * Returns: (transfer full): a #GListModel of #IdeSearchResult items.
  */
-GListModel *
+IdeSearchResults *
 ide_search_engine_search_finish (IdeSearchEngine  *self,
                                  GAsyncResult     *result,
                                  GError          **error)
 {
+  IdeSearchResults *ret;
+
+  IDE_ENTRY;
+
   g_return_val_if_fail (IDE_IS_SEARCH_ENGINE (self), NULL);
   g_return_val_if_fail (IDE_IS_TASK (result), NULL);
 
-  return ide_task_propagate_pointer (IDE_TASK (result), error);
+  ret = ide_task_propagate_pointer (IDE_TASK (result), error);
+
+  g_return_val_if_fail (!ret || IDE_IS_SEARCH_RESULTS (ret), NULL);
+
+  IDE_RETURN (ret);
 }
 
 /**
diff --git a/src/libide/search/ide-search-engine.h b/src/libide/search/ide-search-engine.h
index 7e513b01e..477918e8a 100644
--- a/src/libide/search/ide-search-engine.h
+++ b/src/libide/search/ide-search-engine.h
@@ -25,7 +25,9 @@
 #endif
 
 #include <libide-core.h>
+
 #include "ide-search-provider.h"
+#include "ide-search-results.h"
 
 G_BEGIN_DECLS
 
@@ -33,26 +35,27 @@ G_BEGIN_DECLS
 
 IDE_AVAILABLE_IN_ALL
 G_DECLARE_FINAL_TYPE (IdeSearchEngine, ide_search_engine, IDE, SEARCH_ENGINE, IdeObject)
+
 IDE_AVAILABLE_IN_ALL
-IdeSearchEngine *ide_search_engine_new             (void);
+IdeSearchEngine  *ide_search_engine_new             (void);
 IDE_AVAILABLE_IN_ALL
-gboolean         ide_search_engine_get_busy        (IdeSearchEngine      *self);
+gboolean          ide_search_engine_get_busy        (IdeSearchEngine      *self);
 IDE_AVAILABLE_IN_ALL
-void             ide_search_engine_search_async    (IdeSearchEngine      *self,
-                                                    const gchar          *query,
-                                                    guint                 max_results,
-                                                    GCancellable         *cancellable,
-                                                    GAsyncReadyCallback   callback,
-                                                    gpointer              user_data);
+void              ide_search_engine_search_async    (IdeSearchEngine      *self,
+                                                     const char           *query,
+                                                     guint                 max_results,
+                                                     GCancellable         *cancellable,
+                                                     GAsyncReadyCallback   callback,
+                                                     gpointer              user_data);
 IDE_AVAILABLE_IN_ALL
-GListModel      *ide_search_engine_search_finish   (IdeSearchEngine      *self,
-                                                    GAsyncResult         *result,
-                                                    GError              **error);
+IdeSearchResults *ide_search_engine_search_finish   (IdeSearchEngine      *self,
+                                                     GAsyncResult         *result,
+                                                     GError              **error);
 IDE_AVAILABLE_IN_ALL
-void             ide_search_engine_add_provider    (IdeSearchEngine      *self,
-                                                    IdeSearchProvider    *provider);
+void              ide_search_engine_add_provider    (IdeSearchEngine      *self,
+                                                     IdeSearchProvider    *provider);
 IDE_AVAILABLE_IN_ALL
-void             ide_search_engine_remove_provider (IdeSearchEngine      *self,
-                                                    IdeSearchProvider    *provider);
+void              ide_search_engine_remove_provider (IdeSearchEngine      *self,
+                                                     IdeSearchProvider    *provider);
 
 G_END_DECLS
diff --git a/src/libide/search/ide-search-result.h b/src/libide/search/ide-search-result.h
index b33e92dea..681a052a9 100644
--- a/src/libide/search/ide-search-result.h
+++ b/src/libide/search/ide-search-result.h
@@ -39,8 +39,10 @@ struct _IdeSearchResultClass
 {
   GObjectClass parent_class;
 
-  void (*activate) (IdeSearchResult *self,
-                    GtkWidget       *last_focus);
+  gboolean (*matches)  (IdeSearchResult *self,
+                        const char      *query);
+  void     (*activate) (IdeSearchResult *self,
+                        GtkWidget       *last_focus);
 };
 
 IDE_AVAILABLE_IN_ALL
diff --git a/src/libide/search/ide-search-results-private.h b/src/libide/search/ide-search-results-private.h
new file mode 100644
index 000000000..dd19016b9
--- /dev/null
+++ b/src/libide/search/ide-search-results-private.h
@@ -0,0 +1,30 @@
+/* ide-search-results-private.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "ide-search-results.h"
+
+G_BEGIN_DECLS
+
+IdeSearchResults *_ide_search_results_new (GListModel *model,
+                                           const char *query);
+
+G_END_DECLS
diff --git a/src/libide/search/ide-search-results.c b/src/libide/search/ide-search-results.c
new file mode 100644
index 000000000..d609a015e
--- /dev/null
+++ b/src/libide/search/ide-search-results.c
@@ -0,0 +1,173 @@
+/* ide-search-results.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-search-results"
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#include "ide-search-result.h"
+#include "ide-search-results-private.h"
+
+struct _IdeSearchResults
+{
+  GObject              parent_instance;
+
+  /* The model doing our filtering, we proxy through to it */
+  GtkFilterListModel  *filter_model;
+  GtkCustomFilter     *filter;
+
+  /* The current refiltered word, >= @query with matching prefix */
+  char                *refilter;
+
+  /* The original query and it's length */
+  char                *query;
+  gsize                query_len;
+};
+
+static GType
+ide_search_results_get_item_type (GListModel *model)
+{
+  return IDE_TYPE_SEARCH_RESULT;
+}
+
+static guint
+ide_search_results_get_n_items (GListModel *model)
+{
+  return g_list_model_get_n_items (G_LIST_MODEL (IDE_SEARCH_RESULTS (model)->filter_model));
+}
+
+static gpointer
+ide_search_results_get_item (GListModel *model,
+                             guint       position)
+{
+  return g_list_model_get_item (G_LIST_MODEL (IDE_SEARCH_RESULTS (model)->filter_model), position);
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+  iface->get_item_type = ide_search_results_get_item_type;
+  iface->get_n_items = ide_search_results_get_n_items;
+  iface->get_item = ide_search_results_get_item;
+}
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (IdeSearchResults, ide_search_results, G_TYPE_OBJECT,
+                               G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+
+static void
+ide_search_results_dispose (GObject *object)
+{
+  IdeSearchResults *self = (IdeSearchResults *)object;
+
+  if (self->filter_model != NULL)
+    {
+      gtk_filter_list_model_set_filter (self->filter_model, NULL);
+      g_clear_object (&self->filter_model);
+    }
+
+  g_clear_object (&self->filter);
+
+  g_clear_pointer (&self->query, g_free);
+  g_clear_pointer (&self->refilter, g_free);
+
+  G_OBJECT_CLASS (ide_search_results_parent_class)->dispose (object);
+}
+
+static void
+ide_search_results_class_init (IdeSearchResultsClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = ide_search_results_dispose;
+}
+
+static void
+ide_search_results_init (IdeSearchResults *self)
+{
+}
+
+static gboolean
+ide_search_results_filter (gpointer item,
+                           gpointer user_data)
+{
+  IdeSearchResult *result = item;
+  IdeSearchResults *self = user_data;
+
+  return IDE_SEARCH_RESULT_GET_CLASS (result)->matches (result, self->refilter);
+}
+
+IdeSearchResults *
+_ide_search_results_new (GListModel *model,
+                         const char *query)
+{
+  IdeSearchResults *self;
+
+  g_return_val_if_fail (G_IS_LIST_MODEL (model), NULL);
+  g_return_val_if_fail (query != NULL, NULL);
+
+  self = g_object_new (IDE_TYPE_SEARCH_RESULTS, NULL);
+  self->query = g_strdup (query);
+  self->query_len = strlen (query);
+  self->refilter = g_strdup (query);
+  self->filter = gtk_custom_filter_new (ide_search_results_filter, self, NULL);
+  self->filter_model = gtk_filter_list_model_new (g_object_ref (model), NULL);
+
+  gtk_filter_list_model_set_incremental (self->filter_model, TRUE);
+
+  g_signal_connect_object (self->filter_model,
+                           "items-changed",
+                           G_CALLBACK (g_list_model_items_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  return self;
+}
+
+gboolean
+ide_search_results_refilter (IdeSearchResults *self,
+                             const char       *query)
+{
+  g_autofree char *old_query = NULL;
+
+  g_return_val_if_fail (IDE_IS_SEARCH_RESULTS (self), FALSE);
+  g_return_val_if_fail (query != NULL, FALSE);
+
+  /* Make sure we have the prefix of the original search */
+  if (memcmp (self->query, query, self->query_len) != 0)
+    return FALSE;
+
+  /* Swap the filtering query */
+  old_query = g_steal_pointer (&self->refilter);
+  self->refilter = g_strdup (query);
+
+  /* Notify of changes, and only update the portion not matched */
+  if (g_str_has_prefix (query, old_query))
+    gtk_filter_changed (GTK_FILTER (self->filter), GTK_FILTER_CHANGE_MORE_STRICT);
+  else
+    gtk_filter_changed (GTK_FILTER (self->filter), GTK_FILTER_CHANGE_LESS_STRICT);
+
+  /* Attach filter if we haven't yet */
+  if (gtk_filter_list_model_get_filter (self->filter_model) == NULL)
+    gtk_filter_list_model_set_filter (self->filter_model, GTK_FILTER (self->filter));
+
+  return TRUE;
+}
diff --git a/src/libide/search/ide-search-results.h b/src/libide/search/ide-search-results.h
new file mode 100644
index 000000000..54fd8a97a
--- /dev/null
+++ b/src/libide/search/ide-search-results.h
@@ -0,0 +1,40 @@
+/* ide-search-results.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_SEARCH_INSIDE) && !defined (IDE_SEARCH_COMPILATION)
+# error "Only <libide-search.h> can be included directly."
+#endif
+
+#include <libide-core.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_SEARCH_RESULTS (ide_search_results_get_type())
+
+IDE_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (IdeSearchResults, ide_search_results, IDE, SEARCH_RESULTS, GObject)
+
+IDE_AVAILABLE_IN_ALL
+gboolean ide_search_results_refilter (IdeSearchResults *self,
+                                      const char       *query);
+
+G_END_DECLS
diff --git a/src/libide/search/libide-search.h b/src/libide/search/libide-search.h
index 23d9b11b3..6c4e8270b 100644
--- a/src/libide/search/libide-search.h
+++ b/src/libide/search/libide-search.h
@@ -34,4 +34,5 @@
 # include "ide-search-provider.h"
 # include "ide-search-reducer.h"
 # include "ide-search-result.h"
+# include "ide-search-results.h"
 #undef IDE_SEARCH_INSIDE
diff --git a/src/libide/search/meson.build b/src/libide/search/meson.build
index 551701aaf..de7ee7f56 100644
--- a/src/libide/search/meson.build
+++ b/src/libide/search/meson.build
@@ -17,6 +17,7 @@ libide_search_public_headers = [
   'ide-search-provider.h',
   'ide-search-reducer.h',
   'ide-search-result.h',
+  'ide-search-results.h',
   'libide-search.h',
 ]
 
@@ -37,6 +38,7 @@ libide_search_public_sources = [
   'ide-search-provider.c',
   'ide-search-reducer.c',
   'ide-search-result.c',
+  'ide-search-results.c',
 ]
 
 libide_search_private_sources = [


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