[gnome-builder/wip/chergert/clang] clang: start refactoring completion for clang



commit 0650540405c6f4a0a69199d5bc1a969b1b664cb7
Author: Christian Hergert <chergert redhat com>
Date:   Wed May 2 01:42:50 2018 -0700

    clang: start refactoring completion for clang
    
    This is not exactly efficient yet, but it re-arranges things in a way that
    will make it easier for us to port to the new completion engine when that
    lands.
    
    We should try to make this faster before we land the branch, because it
    will probably be a week or two while we finish up the new completion
    engine (which should render most of the runtime work unnecesary).

 src/plugins/clang/gnome-builder-clang.c           |   5 +
 src/plugins/clang/ide-clang-completion-item.c     |  53 +-
 src/plugins/clang/ide-clang-completion-item.h     |  19 +-
 src/plugins/clang/ide-clang-completion-provider.c | 596 ++--------------
 src/plugins/clang/ide-clang-completion-provider.h |   2 -
 src/plugins/clang/ide-clang-proposals.c           | 819 ++++++++++++++++++++++
 src/plugins/clang/ide-clang-proposals.h           |  44 ++
 src/plugins/clang/ide-clang.c                     |  13 +-
 src/plugins/clang/meson.build                     |   8 +-
 9 files changed, 982 insertions(+), 577 deletions(-)
---
diff --git a/src/plugins/clang/gnome-builder-clang.c b/src/plugins/clang/gnome-builder-clang.c
index 17a5b88b5..12b4658ed 100644
--- a/src/plugins/clang/gnome-builder-clang.c
+++ b/src/plugins/clang/gnome-builder-clang.c
@@ -632,6 +632,11 @@ handle_complete_cb (IdeClang     *clang,
     client_op_error (op, error);
   else
     client_op_reply (op, ret);
+
+  if (ret)
+    g_printerr ("  Completion: %lu Results\n", g_variant_n_children (ret));
+  else
+    g_printerr ("  Completion: %s\n", error->message);
 }
 
 static void
diff --git a/src/plugins/clang/ide-clang-completion-item.c b/src/plugins/clang/ide-clang-completion-item.c
index 095ddc9c8..555bbf6ed 100644
--- a/src/plugins/clang/ide-clang-completion-item.c
+++ b/src/plugins/clang/ide-clang-completion-item.c
@@ -128,7 +128,7 @@ ide_clang_completion_item_lazy_init (IdeClangCompletionItem *self)
 
   g_variant_iter_init (&iter, chunks);
 
-  while (g_variant_iter_loop (&iter, "a{sv}", &chunk))
+  while ((chunk = g_variant_iter_next_value (&iter)))
     {
       g_autofree gchar *escaped = NULL;
       const gchar *text;
@@ -188,6 +188,8 @@ ide_clang_completion_item_lazy_init (IdeClangCompletionItem *self)
         default:
           break;
         }
+
+      g_variant_unref (chunk);
     }
 
   self->markup = g_string_free (g_steal_pointer (&markup), FALSE);
@@ -437,60 +439,27 @@ ide_clang_completion_item_get_snippet (IdeClangCompletionItem *self,
   return ide_clang_completion_item_create_snippet (self, file_settings);
 }
 
-/**
- * ide_clang_completion_item_get_typed_text:
- * @self: An #IdeClangCompletionItem.
- *
- * Gets the text that would be expected to be typed to insert this completion
- * item into the text editor.
- *
- * Returns: A string which should not be modified or freed.
- */
-const gchar *
-ide_clang_completion_item_get_typed_text (IdeClangCompletionItem *self)
-{
-  if G_UNLIKELY (self->typed_text == NULL)
-    {
-      g_autoptr(GVariant) variant = ide_clang_completion_item_get_result (self);
-      g_autoptr(GVariant) chunks = g_variant_lookup_value (variant, "chunks", G_VARIANT_TYPE ("aa{sv}"));
-      GVariantIter iter;
-      GVariant *value;
-
-      g_variant_iter_init (&iter, chunks);
-
-      while ((value = g_variant_iter_next_value (&iter)))
-        {
-          enum CXCompletionChunkKind kind;
-
-          if (g_variant_lookup (value, "kind", "u", &kind) && kind == CXCompletionChunk_TypedText)
-            {
-              g_variant_lookup (value, "text", "&s", self->typed_text);
-              break;
-            }
-        }
-
-      if (self->typed_text == NULL)
-        self->typed_text = "";
-    }
-
-  return self->typed_text;
-}
-
 /**
  * ide_clang_completion_item_new:
  * @variant: the toplevel variant of all results
  * @index: the index of the item
+ * @typed_text: pointer to typed texted within @variant
  *
+ * The @typed_text parameter is not copied, it is expected to be valid
+ * string found within @variant (and therefore associated with its
+ * life-cycle).
  */
 IdeClangCompletionItem *
-ide_clang_completion_item_new (GVariant *variant,
-                               guint     index)
+ide_clang_completion_item_new (GVariant    *variant,
+                               guint        index,
+                               const gchar *typed_text)
 {
   IdeClangCompletionItem *ret;
 
   ret = g_object_new (IDE_TYPE_CLANG_COMPLETION_ITEM, NULL);
   ret->results = g_variant_ref (variant);
   ret->index = index;
+  ret->typed_text = typed_text;
 
   return ret;
 }
diff --git a/src/plugins/clang/ide-clang-completion-item.h b/src/plugins/clang/ide-clang-completion-item.h
index 0c757f058..8b6adc856 100644
--- a/src/plugins/clang/ide-clang-completion-item.h
+++ b/src/plugins/clang/ide-clang-completion-item.h
@@ -40,19 +40,24 @@ struct _IdeClangCompletionItem
   const gchar      *icon_name;
   gchar            *markup;
   GVariant         *results;
-  gchar            *typed_text;
+  const gchar      *typed_text;
 };
 
 static inline GVariant *
 ide_clang_completion_item_get_result (const IdeClangCompletionItem *self)
 {
-  return g_variant_get_child_value (self->results, self->index);
+  g_autoptr(GVariant) child = g_variant_get_child_value (self->results, self->index);
+
+  if (g_variant_is_of_type (child, G_VARIANT_TYPE_VARIANT))
+    return g_variant_get_variant (child);
+
+  return g_steal_pointer (&child);
 }
 
-IdeClangCompletionItem *ide_clang_completion_item_new            (GVariant               *results,
-                                                                  guint                   index);
-IdeSourceSnippet       *ide_clang_completion_item_get_snippet    (IdeClangCompletionItem *self,
-                                                                  IdeFileSettings        *file_settings);
-const gchar            *ide_clang_completion_item_get_typed_text (IdeClangCompletionItem *self);
+IdeClangCompletionItem *ide_clang_completion_item_new         (GVariant               *results,
+                                                               guint                   index,
+                                                               const gchar            *typed_text);
+IdeSourceSnippet       *ide_clang_completion_item_get_snippet (IdeClangCompletionItem *self,
+                                                               IdeFileSettings        *file_settings);
 
 G_END_DECLS
diff --git a/src/plugins/clang/ide-clang-completion-provider.c 
b/src/plugins/clang/ide-clang-completion-provider.c
index e13f7a182..446bbded3 100644
--- a/src/plugins/clang/ide-clang-completion-provider.c
+++ b/src/plugins/clang/ide-clang-completion-provider.c
@@ -23,24 +23,18 @@
 #include "ide-clang-client.h"
 #include "ide-clang-completion-item.h"
 #include "ide-clang-completion-provider.h"
+#include "ide-clang-proposals.h"
+
+#include "sourceview/ide-text-iter.h"
 
 struct _IdeClangCompletionProvider
 {
-  IdeObject      parent_instance;
-
-  GSettings     *settings;
-  gchar         *last_line;
-  GPtrArray     *last_results;
-  gchar         *last_query;
+  IdeObject parent_instance;
   /*
-   * As an optimization, the linked list for result nodes are
-   * embedded in the IdeClangCompletionItem structures and we
-   * do not allocate them. This is the pointer to the first item
-   * in the result set that matches our query. It is not allocated
-   * and do not tree to free it or perform g_list_*() operations
-   * upon it.
+   * We keep a copy of settings so that we can ignore completion requests
+   * if the user has specifically disabled completion.
    */
-  GList         *head;
+  GSettings *settings;
   /*
    * We save a weak pointer to the view that performed the request
    * so that we can push a snippet onto the view instead of inserting
@@ -48,25 +42,22 @@ struct _IdeClangCompletionProvider
    */
   IdeSourceView *view;
   /*
-   * The saved offset used when generating results. This is our position
-   * where we moved past all the junk to a stop character (as required
-   * by clang).
+   * The proposals helper that manages generating results.
    */
-  guint stop_line;
-  guint stop_line_offset;
+  IdeClangProposals *proposals;
 };
 
 typedef struct
 {
   IdeClangCompletionProvider *self;
   GtkSourceCompletionContext *context;
-  IdeFile *file;
-  GCancellable *cancellable;
-  gchar *line;
-  gchar *query;
-} IdeClangCompletionState;
+  GCancellable               *cancellable;
+} Populate;
+
+static void completion_provider_iface_init (GtkSourceCompletionProviderIface *);
+static void populate_free                   (Populate *);
 
-static void completion_provider_iface_init (GtkSourceCompletionProviderIface *iface);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (Populate, populate_free);
 
 G_DEFINE_TYPE_EXTENDED (IdeClangCompletionProvider,
                         ide_clang_completion_provider,
@@ -77,44 +68,12 @@ G_DEFINE_TYPE_EXTENDED (IdeClangCompletionProvider,
                         G_IMPLEMENT_INTERFACE (IDE_TYPE_COMPLETION_PROVIDER, NULL))
 
 static void
-ide_clang_completion_state_free (IdeClangCompletionState *state)
+populate_free (Populate *p)
 {
-  g_clear_object (&state->self);
-  g_clear_object (&state->cancellable);
-  g_clear_object (&state->context);
-  g_clear_object (&state->file);
-  g_clear_pointer (&state->line, g_free);
-  g_clear_pointer (&state->query, g_free);
-  g_slice_free (IdeClangCompletionState, state);
-}
-
-G_DEFINE_AUTOPTR_CLEANUP_FUNC (IdeClangCompletionState, ide_clang_completion_state_free)
-
-static gint
-sort_by_priority (gconstpointer a,
-                  gconstpointer b)
-{
-  const IdeClangCompletionItem *itema = (const IdeClangCompletionItem *)a;
-  const IdeClangCompletionItem *itemb = (const IdeClangCompletionItem *)b;
-
-  if (itema->priority < itemb->priority)
-    return -1;
-  else if (itema->priority > itemb->priority)
-    return 1;
-
-  /* If the item is in the result set here, we should have a valid
-   * typed_text field because we already scored the completion item.
-   */
-
-  return g_strcmp0 (itema->typed_text, itemb->typed_text);
-}
-
-static void
-ide_clang_completion_provider_sort_by_priority (IdeClangCompletionProvider *self)
-{
-  g_assert (IDE_IS_CLANG_COMPLETION_PROVIDER (self));
-
-  self->head = g_list_sort (self->head, sort_by_priority);
+  g_clear_object (&p->self);
+  g_clear_object (&p->context);
+  g_clear_object (&p->cancellable);
+  g_slice_free (Populate, p);
 }
 
 static gchar *
@@ -135,9 +94,10 @@ ide_clang_completion_provider_match (GtkSourceCompletionProvider *provider,
 {
   IdeClangCompletionProvider *self = (IdeClangCompletionProvider *)provider;
   GtkSourceCompletionActivation activation;
+  g_autofree gchar *word = NULL;
+  GtkTextIter iter;
   GtkTextBuffer *buffer;
   IdeFile *file;
-  GtkTextIter iter;
 
   g_return_val_if_fail (IDE_IS_CLANG_COMPLETION_PROVIDER (self), FALSE);
   g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context), FALSE);
@@ -155,337 +115,42 @@ ide_clang_completion_provider_match (GtkSourceCompletionProvider *provider,
     return FALSE;
 
   activation = gtk_source_completion_context_get_activation (context);
+  if (activation == GTK_SOURCE_COMPLETION_ACTIVATION_USER_REQUESTED)
+    return TRUE;
 
-  if (activation == GTK_SOURCE_COMPLETION_ACTIVATION_INTERACTIVE)
-    {
-      gunichar ch;
-
-      /* avoid auto completion while in comments, strings, etc */
-      if (ide_completion_provider_context_in_comment_or_string (context))
-        return FALSE;
-
-      if (gtk_text_iter_starts_line (&iter))
-        return FALSE;
-
-      gtk_text_iter_backward_char (&iter);
-
-      ch = gtk_text_iter_get_char (&iter);
-
-      if (!g_unichar_isalnum (ch) && ch != '_')
-        return FALSE;
-    }
+  if (!(word = _ide_text_iter_current_symbol (&iter, NULL)))
+    return FALSE;
 
   return TRUE;
 }
 
-static gboolean
-ide_clang_completion_provider_can_replay (IdeClangCompletionProvider *self,
-                                          const gchar                *line)
-{
-  const gchar *suffix;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_CLANG_COMPLETION_PROVIDER (self));
-
-  if (self->last_results == NULL)
-    IDE_RETURN (FALSE);
-
-  if (line == NULL || *line == '\0' || self->last_line == NULL)
-    IDE_RETURN (FALSE);
-
-  if (!g_str_has_prefix (line, self->last_line))
-    IDE_RETURN (FALSE);
-
-  suffix = line + strlen (self->last_line);
-
-  IDE_TRACE_MSG ("Checking \"%s\" for invalidating characters", suffix);
-
-  for (; *suffix; suffix = g_utf8_next_char (suffix))
-    {
-      gunichar ch = g_utf8_get_char (suffix);
-      if (!g_unichar_isalnum (ch) && (ch != '_'))
-        {
-          IDE_TRACE_MSG ("contains invaliding characters");
-          IDE_RETURN (FALSE);
-        }
-    }
-
-  IDE_RETURN (TRUE);
-}
-
-static void
-ide_clang_completion_provider_save_results (IdeClangCompletionProvider *self,
-                                            GPtrArray                  *results,
-                                            const gchar                *line,
-                                            const gchar                *query)
-{
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_CLANG_COMPLETION_PROVIDER (self));
-
-  g_clear_pointer (&self->last_results, g_ptr_array_unref);
-  g_clear_pointer (&self->last_line, g_free);
-  g_clear_pointer (&self->last_query, g_free);
-  self->head = NULL;
-
-  if (query && !*query)
-    query = NULL;
-
-  if (results != NULL)
-    {
-      self->last_line = g_strdup (line);
-      self->last_query = g_strdup (query);
-      self->last_results = g_ptr_array_ref (results);
-      if (results->len > 0)
-        {
-          IdeClangCompletionItem *head = g_ptr_array_index (results, 0);
-          self->head = &head->link;
-        }
-    }
-
-  IDE_EXIT;
-}
-
-static void
-ide_clang_completion_provider_update_links (IdeClangCompletionProvider *self,
-                                            GPtrArray                  *results)
-{
-  IdeClangCompletionItem *item;
-  IdeClangCompletionItem *next;
-  IdeClangCompletionItem *prev;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_CLANG_COMPLETION_PROVIDER (self));
-  g_assert (results != NULL);
-
-  if G_UNLIKELY (results->len == 0)
-    {
-      self->head = NULL;
-      IDE_EXIT;
-    }
-
-  /* Unrolling loops for the gentoo crowd */
-
-  item = g_ptr_array_index (results, 0);
-  item->link.prev = NULL;
-  item->link.next = (results->len == 1) ? NULL : &((IdeClangCompletionItem*)g_ptr_array_index (results, 
1))->link;
-
-  self->head = &item->link;
-
-  prev = item;
-
-  for (guint i = 1; i < (results->len - 1); i++)
-    {
-      item = g_ptr_array_index (results, i);
-      next = g_ptr_array_index (results, i + 1);
-
-      item->link.prev = &prev->link;
-      item->link.next = &next->link;
-
-      prev = item;
-    }
-
-  if (results->len > 1)
-    {
-      item = g_ptr_array_index (results, results->len - 1);
-      item->link.prev = &((IdeClangCompletionItem*)g_ptr_array_index (results, results->len-2))->link;
-      item->link.next = NULL;
-    }
-
-  IDE_EXIT;
-}
-
-static void
-ide_clang_completion_provider_refilter (IdeClangCompletionProvider *self,
-                                        GPtrArray                  *results,
-                                        const gchar                *query)
-{
-  g_autofree gchar *lower = NULL;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_CLANG_COMPLETION_PROVIDER (self));
-  g_assert (results != NULL);
-  g_assert (query != NULL);
-
-  if (results->len == 0)
-    IDE_EXIT;
-
-  IDE_TRACE_MSG ("Filtering with query \"%s\"", query);
-
-  /*
-   * By traversing the linked list nodes instead of the array, we allow
-   * ourselves to avoid rechecking items we already know filtered.
-   * We do need to be mindful of this in case the user backspaced
-   * and our list is no longer a continual "deep dive" of matched items.
-   */
-  if ((self->last_query != NULL) && g_str_has_prefix (query, self->last_query))
-    ide_clang_completion_provider_update_links (self, results);
-
-  lower = g_utf8_casefold (query, -1);
-
-  if (!g_str_is_ascii (lower))
-    {
-      g_warning ("Item filtering requires ascii input.");
-      IDE_EXIT;
-    }
-
-  for (GList *iter = self->head; iter; iter = iter->next)
-    {
-      IdeClangCompletionItem *item = iter->data;
-      const gchar *typed_text;
-      guint priority;
-
-      typed_text = ide_clang_completion_item_get_typed_text (item);
-
-      if (!ide_completion_item_fuzzy_match (typed_text, lower, &priority))
-        {
-          if (iter->prev != NULL)
-            iter->prev->next = iter->next;
-          else
-            self->head = iter->next;
-
-          if (iter->next != NULL)
-            iter->next->prev = iter->prev;
-
-          continue;
-        }
-
-      /* Save the generated priority for further sorting */
-      item->priority = priority;
-    }
-
-  g_free (self->last_query);
-  self->last_query = g_strdup (query);
-
-  IDE_EXIT;
-}
-
 static void
-ide_clang_completion_provider_complete_cb (GObject      *object,
+ide_clang_completion_provider_populate_cb (GObject      *object,
                                            GAsyncResult *result,
                                            gpointer      user_data)
 {
-  IdeClangClient *client = (IdeClangClient *)object;
-  g_autoptr(IdeClangCompletionState) state = user_data;
-  g_autoptr(GPtrArray) results = NULL;
-  g_autoptr(GVariant) vresults = NULL;
+  IdeClangProposals *proposals = (IdeClangProposals *)object;
+  g_autoptr(Populate) state = user_data;
   g_autoptr(GError) error = NULL;
-  guint n_items;
+  const GList *results = NULL;
 
   IDE_ENTRY;
 
-  g_assert (IDE_IS_CLANG_CLIENT (client));
+  g_assert (IDE_IS_CLANG_PROPOSALS (proposals));
+  g_assert (G_IS_ASYNC_RESULT (result));
   g_assert (state != NULL);
   g_assert (IDE_IS_CLANG_COMPLETION_PROVIDER (state->self));
   g_assert (G_IS_CANCELLABLE (state->cancellable));
-  g_assert (IDE_IS_FILE (state->file));
   g_assert (GTK_SOURCE_IS_COMPLETION_CONTEXT (state->context));
 
-  if (!(vresults = ide_clang_client_complete_finish (client, result, &error)))
-    {
-      g_debug ("%s", error->message);
-      if (!g_cancellable_is_cancelled (state->cancellable))
-        gtk_source_completion_context_add_proposals (state->context,
-                                                     GTK_SOURCE_COMPLETION_PROVIDER (state->self),
-                                                     NULL, TRUE);
-      IDE_EXIT;
-    }
-
-  n_items = (guint)g_variant_n_children (vresults);
-  results = g_ptr_array_new_full (n_items, g_object_unref);
-
-  for (guint i = 0; i < n_items; i++)
-    g_ptr_array_add (results, ide_clang_completion_item_new (vresults, i));
-
-  ide_clang_completion_provider_save_results (state->self, results, state->line, state->query);
-  ide_clang_completion_provider_update_links (state->self, results);
+  if (ide_clang_proposals_populate_finish (proposals, result, NULL))
+    results = ide_clang_proposals_get_list (proposals);
 
   if (!g_cancellable_is_cancelled (state->cancellable))
-    {
-      if (results->len > 0)
-        {
-          if (state->query && *state->query)
-            ide_clang_completion_provider_refilter (state->self, results, state->query);
-          ide_clang_completion_provider_sort_by_priority (state->self);
-          IDE_TRACE_MSG ("%d results returned from clang", results->len);
-          gtk_source_completion_context_add_proposals (state->context,
-                                                       GTK_SOURCE_COMPLETION_PROVIDER (state->self),
-                                                       state->self->head, TRUE);
-        }
-      else
-        {
-          IDE_TRACE_MSG ("No results returned from clang");
-          gtk_source_completion_context_add_proposals (state->context,
-                                                       GTK_SOURCE_COMPLETION_PROVIDER (state->self),
-                                                       NULL, TRUE);
-        }
-    }
-  else
-    {
-      IDE_TRACE_MSG ("Ignoring completions due to cancellation");
-    }
-
-  IDE_EXIT;
-}
-
-static void
-ide_clang_completion_provider_build_flags_cb (GObject      *object,
-                                              GAsyncResult *result,
-                                              gpointer      user_data)
-{
-  IdeBuildSystem *build_system = (IdeBuildSystem *)object;
-  g_autoptr(IdeClangCompletionState) state = user_data;
-  g_autoptr(GError) error = NULL;
-  g_auto(GStrv) flags = NULL;
-  IdeClangClient *client;
-  IdeContext *context;
-  GCancellable *cancellable;
-  GFile *file;
-  GtkTextIter iter;
-  guint line;
-  guint column;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_BUILD_SYSTEM (build_system));
-  g_assert (state != NULL);
-  g_assert (IDE_IS_CLANG_COMPLETION_PROVIDER (state->self));
-  g_assert (G_IS_CANCELLABLE (state->cancellable));
-  g_assert (IDE_IS_FILE (state->file));
-  g_assert (GTK_SOURCE_IS_COMPLETION_CONTEXT (state->context));
-
-  flags = ide_build_system_get_build_flags_finish (build_system, result, NULL);
-
-  /*
-   * It's like we are racing with our future self, which coallesced the
-   * compile request into a single delayed query. Use the cancellable
-   * to detect if we were cancelled and short-circuit early.
-   */
-  if (g_cancellable_is_cancelled (state->cancellable))
-    IDE_EXIT;
-
-  context = ide_object_get_context (IDE_OBJECT (build_system));
-  client = ide_context_get_service_typed (context, IDE_TYPE_CLANG_CLIENT);
-  file = ide_file_get_file (state->file);
-  cancellable = state->cancellable;
-
-  gtk_source_completion_context_get_iter (state->context, &iter);
-
-  line = gtk_text_iter_get_line (&iter) + 1;
-  column = gtk_text_iter_get_line_offset (&iter) + 1;
-
-  ide_clang_client_complete_async (client,
-                                   file,
-                                   (const gchar * const *)flags,
-                                   line,
-                                   column,
-                                   cancellable,
-                                   ide_clang_completion_provider_complete_cb,
-                                   g_steal_pointer (&state));
-
+    gtk_source_completion_context_add_proposals (state->context,
+                                                 GTK_SOURCE_COMPLETION_PROVIDER (state->self),
+                                                 (GList *)results,
+                                                 TRUE);
   IDE_EXIT;
 }
 
@@ -494,17 +159,11 @@ ide_clang_completion_provider_populate (GtkSourceCompletionProvider *provider,
                                         GtkSourceCompletionContext  *context)
 {
   IdeClangCompletionProvider *self = (IdeClangCompletionProvider *)provider;
-  IdeClangCompletionState *state;
   GtkSourceCompletionActivation activation;
   g_autoptr(GtkSourceCompletion) completion = NULL;
-  g_autofree gchar *line = NULL;
-  g_autofree gchar *prefix = NULL;
-  GtkTextIter begin;
+  Populate *state;
   GtkTextIter iter;
-  GtkTextIter stop;
-  GtkTextBuffer *buffer;
-  IdeBuildSystem *build_system;
-  gunichar ch;
+  gboolean user_requested;
 
   IDE_ENTRY;
 
@@ -512,127 +171,42 @@ ide_clang_completion_provider_populate (GtkSourceCompletionProvider *provider,
   g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context));
 
   activation = gtk_source_completion_context_get_activation (context);
+  user_requested = activation == GTK_SOURCE_COMPLETION_ACTIVATION_USER_REQUESTED;
 
   if (!gtk_source_completion_context_get_iter (context, &iter))
     IDE_GOTO (failure);
 
-  buffer = gtk_text_iter_get_buffer (&iter);
-
-  /* Get the line text up to the insertion mark */
-  begin = iter;
-  gtk_text_iter_set_line_offset (&begin, 0);
-  line = gtk_text_iter_get_slice (&begin, &iter);
-
-  /* Move to the just inserted character */
-  stop = iter;
-  if (!gtk_text_iter_starts_line (&stop))
-    gtk_text_iter_backward_char (&stop);
-
-  if (activation == GTK_SOURCE_COMPLETION_ACTIVATION_INTERACTIVE)
-    {
-      if (gtk_text_iter_get_char (&stop) == ';')
-        IDE_GOTO (failure);
-    }
-
-  /*
-   * Walk backwards to locate the first character after a stop character.
-   * A stop character is anything that can't go in a function/type.
-   */
-  while (!gtk_text_iter_starts_line (&stop) &&
-         (ch = gtk_text_iter_get_char (&stop)) &&
-         (g_unichar_isalnum (ch) || (ch == '_')) &&
-         gtk_text_iter_backward_char (&stop))
-    { /* Do nothing */ }
-
-  ch = gtk_text_iter_get_char (&stop);
-
-  /* Move forward if we moved past the last matching char */
-  if (!g_unichar_isalnum (ch) &&
-      (ch != '_') &&
-      (gtk_text_iter_compare (&stop, &iter) < 0))
-    gtk_text_iter_forward_char (&stop);
+  g_object_get (context,
+                "completion", &completion,
+                NULL);
 
-  self->stop_line = gtk_text_iter_get_line (&stop);
-  self->stop_line_offset = gtk_text_iter_get_line_offset (&stop);
-
-  prefix = g_strstrip (gtk_text_iter_get_slice (&stop, &iter));
-
-  /*
-   * We might be able to reuse the results from our previous query if
-   * the buffer is sufficiently similar. If so, possibly just rearrange
-   * some things and redisplay those results.
-   *
-   * However, we always want to perform a new query if ctrl+space was
-   * pressed.
-   */
-  if ((activation != GTK_SOURCE_COMPLETION_ACTIVATION_USER_REQUESTED) &&
-      ide_clang_completion_provider_can_replay (self, line))
-    {
-      IDE_PROBE;
-
-      /*
-       * Filter the items that no longer match our query.
-       * We save a little state so that we can optimize further
-       * passes of this operation by traversing the already filtered
-       * linked list instead of all items.
-       */
-      ide_clang_completion_provider_refilter (self, self->last_results, prefix);
-      ide_clang_completion_provider_sort_by_priority (self);
-      gtk_source_completion_context_add_proposals (context, provider, self->head, TRUE);
-
-      IDE_EXIT;
-    }
+  self->view = IDE_SOURCE_VIEW (gtk_source_completion_get_view (completion));
 
-#if 0
-  /*
-   * If we are completed interactively, we only want to activate the clang
-   * completion provider if a translation unit is immediatley available.
-   * Otherwise, we delay the other completion providers from being visible
-   * until after this one has completed. Instead, we'll just queue the load
-   * of translation unit for a follow use.
-   */
-  if (activation == GTK_SOURCE_COMPLETION_ACTIVATION_INTERACTIVE)
+  if (self->proposals == NULL)
     {
-      IdeFile *file = ide_buffer_get_file (IDE_BUFFER (buffer));
+      IdeContext *ctx = ide_object_get_context (IDE_OBJECT (self));
+      IdeClangClient *client = ide_context_get_service_typed (ctx, IDE_TYPE_CLANG_CLIENT);
 
-      tu = ide_clang_service_get_cached_translation_unit (service, file);
-
-      if (tu == NULL)
-        {
-          ide_clang_service_get_translation_unit_async (service, file, 0, NULL, NULL, NULL);
-          gtk_source_completion_context_add_proposals (context, provider, NULL, TRUE);
-          IDE_EXIT;
-        }
+      self->proposals = ide_clang_proposals_new (client);
     }
-#endif
-
-  /* Save the view so we can push a snippet later */
-  g_object_get (context, "completion", &completion, NULL);
-  self->view = IDE_SOURCE_VIEW (gtk_source_completion_get_view (completion));
 
-  ide_buffer_sync_to_unsaved_files (IDE_BUFFER (buffer));
-
-  state = g_slice_new0 (IdeClangCompletionState);
+  state = g_slice_new0 (Populate);
   state->self = g_object_ref (self);
-  state->context = g_object_ref (context);
-  state->file = g_object_ref (ide_buffer_get_file (IDE_BUFFER (buffer)));
   state->cancellable = g_cancellable_new ();
-  state->query = prefix, prefix = NULL;
-  state->line = line, line = NULL;
+  state->context = g_object_ref (context);
 
-  g_signal_connect_object (context,
+  g_signal_connect_object (state->context,
                            "cancelled",
                            G_CALLBACK (g_cancellable_cancel),
                            state->cancellable,
                            G_CONNECT_SWAPPED);
 
-  build_system = ide_context_get_build_system (ide_object_get_context (IDE_OBJECT (self)));
-
-  ide_build_system_get_build_flags_async (build_system,
-                                          state->file,
-                                          state->cancellable,
-                                          ide_clang_completion_provider_build_flags_cb,
-                                          state);
+  ide_clang_proposals_populate_async (self->proposals,
+                                      &iter,
+                                      user_requested,
+                                      state->cancellable,
+                                      ide_clang_completion_provider_populate_cb,
+                                      state);
 
   IDE_EXIT;
 
@@ -642,41 +216,24 @@ failure:
   IDE_EXIT;
 }
 
-static gboolean
-get_start_iter (GtkSourceCompletionProvider *provider,
-                const GtkTextIter           *location,
-                GtkSourceCompletionProposal *proposal,
-                GtkTextIter                 *iter)
-{
-
-  IdeClangCompletionProvider *self = (IdeClangCompletionProvider *)provider;
-  GtkTextBuffer *buffer = gtk_text_iter_get_buffer (location);
-
-#if !GTK_CHECK_VERSION(3, 19, 0)
-# error "The following requires safety introduced in 3.19.x"
-#endif
-
-  gtk_text_buffer_get_iter_at_line_offset (buffer,
-                                           iter,
-                                           self->stop_line,
-                                           self->stop_line_offset);
-
-  if (gtk_text_iter_get_line (iter) != gtk_text_iter_get_line (location))
-    return FALSE;
-
-  return TRUE;
-}
-
 static gboolean
 ide_clang_completion_provider_get_start_iter (GtkSourceCompletionProvider *provider,
                                               GtkSourceCompletionContext  *context,
                                               GtkSourceCompletionProposal *proposal,
                                               GtkTextIter                 *iter)
 {
-  GtkTextIter location;
+  g_autofree gchar *word = NULL;
+  GtkTextIter begin;
+  GtkTextIter end;
+
+  if (gtk_source_completion_context_get_iter (context, &end) &&
+      (word = _ide_text_iter_current_symbol (&end, &begin)))
+    {
+      *iter = begin;
+      return TRUE;
+    }
 
-  gtk_source_completion_context_get_iter (context, &location);
-  return get_start_iter (provider, &location, proposal, iter);
+  return FALSE;
 }
 
 static gboolean
@@ -688,22 +245,23 @@ ide_clang_completion_provider_activate_proposal (GtkSourceCompletionProvider *pr
   IdeClangCompletionItem *item = (IdeClangCompletionItem *)proposal;
   g_autoptr(IdeSourceSnippet) snippet = NULL;
   IdeFileSettings *file_settings;
+  g_autofree gchar *word = NULL;
   GtkTextBuffer *buffer;
+  GtkTextIter begin;
   IdeFile *file;
-  GtkTextIter end;
 
   IDE_ENTRY;
 
   g_assert (IDE_IS_CLANG_COMPLETION_PROVIDER (self));
   g_assert (IDE_IS_CLANG_COMPLETION_ITEM (item));
 
-  if (!get_start_iter (provider, iter, proposal, &end))
+  if (!(word = _ide_text_iter_current_symbol (iter, &begin)))
     IDE_RETURN (FALSE);
 
   buffer = gtk_text_iter_get_buffer (iter);
   g_assert (IDE_IS_BUFFER (buffer));
 
-  gtk_text_buffer_delete (buffer, iter, &end);
+  gtk_text_buffer_delete (buffer, &begin, iter);
 
   file = ide_buffer_get_file (IDE_BUFFER (buffer));
   g_assert (IDE_IS_FILE (file));
@@ -717,7 +275,7 @@ ide_clang_completion_provider_activate_proposal (GtkSourceCompletionProvider *pr
   g_assert (IDE_IS_SOURCE_SNIPPET (snippet));
   g_assert (IDE_IS_SOURCE_VIEW (self->view));
 
-  ide_source_view_push_snippet (self->view, snippet, iter);
+  ide_source_view_push_snippet (self->view, snippet, &begin);
 
   /* ensure @iter is kept valid */
   gtk_text_buffer_get_iter_at_mark (buffer, iter, gtk_text_buffer_get_insert (buffer));
@@ -730,9 +288,7 @@ ide_clang_completion_provider_finalize (GObject *object)
 {
   IdeClangCompletionProvider *self = (IdeClangCompletionProvider *)object;
 
-  g_clear_pointer (&self->last_results, g_ptr_array_unref);
-  g_clear_pointer (&self->last_line, g_free);
-  g_clear_pointer (&self->last_query, g_free);
+  g_clear_object (&self->proposals);
   g_clear_object (&self->settings);
 
   G_OBJECT_CLASS (ide_clang_completion_provider_parent_class)->finalize (object);
@@ -760,7 +316,5 @@ completion_provider_iface_init (GtkSourceCompletionProviderIface *iface)
 static void
 ide_clang_completion_provider_init (IdeClangCompletionProvider *self)
 {
-  IDE_ENTRY;
   self->settings = g_settings_new ("org.gnome.builder.code-insight");
-  IDE_EXIT;
 }
diff --git a/src/plugins/clang/ide-clang-completion-provider.h 
b/src/plugins/clang/ide-clang-completion-provider.h
index 28db903ca..d6bb05999 100644
--- a/src/plugins/clang/ide-clang-completion-provider.h
+++ b/src/plugins/clang/ide-clang-completion-provider.h
@@ -18,8 +18,6 @@
 
 #pragma once
 
-#include <glib-object.h>
-#include <gtksourceview/gtksource.h>
 #include <ide.h>
 
 G_BEGIN_DECLS
diff --git a/src/plugins/clang/ide-clang-proposals.c b/src/plugins/clang/ide-clang-proposals.c
new file mode 100644
index 000000000..384cfef76
--- /dev/null
+++ b/src/plugins/clang/ide-clang-proposals.c
@@ -0,0 +1,819 @@
+/* ide-clang-proposals.c
+ *
+ * Copyright 2018 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-clang-proposals"
+
+#include "ide-clang-completion-item.h"
+#include "ide-clang-proposals.h"
+
+#include "sourceview/ide-text-iter.h"
+
+struct _IdeClangProposals
+{
+  GObject parent_instance;
+
+  /*
+   * @cancellable contains a GCancellable that we can use to cancel an
+   * in-flight completion request. This generally happens if we determine
+   * that we cannot re-use results from an in-flight request due to the
+   * user typing new characters that break the request.
+   */
+  GCancellable *cancellable;
+
+  /*
+   * @client is our wrapper around the subprocess providing clang services
+   * to this process. We will query it using ide_clang_client_complete_async()
+   * to get a new set of results. We try to avoid calling that too much and
+   * favor client-side filtering because the process is rather slow.
+   */
+  IdeClangClient *client;
+
+  /*
+   * Our current array of items that have been inflated to be filtered based
+   * on the currently typed word.
+   */
+  GPtrArray *items;
+
+  /*
+   * The word we are trying to filter. If we are waiting on a previous query
+   * to finish, this might change before the result has come back from clang.
+   * We post-filter requests based on the filter once we receive it.
+   */
+  gchar *filter;
+
+  /*
+   * The head of the filtered list.
+   */
+  GList *head;
+
+  /*
+   * @line is the line we last performed a completion request upon. We cannot
+   * reuse results that are on a different line or are not a continuation of
+   * the query that we previously made.
+   */
+  gint line;
+
+  /*
+   * @line_offset is the offset for the beginning of our previous query. This
+   * is generally the position of the first-character of the query. So if we
+   * start with a completion upon "gtk_|" (where | is the cursor), we would
+   * store the position of "g" so that determining if we can reuse the results
+   * is simply a check to ensure only symbol characters are used between the
+   * start position (g) and the new cursor location.
+   */
+  gint line_offset;
+
+  /*
+   * Each time we request a new query, we increment this monotonic integer.
+   * That allows us to check when getting the result to see if the query we
+   * made is still the most active.
+   *
+   * This is necessary because if we continue to type while we are trying to
+   * start a completion, we might want to continue using the previous query
+   * but post-process the results using our more specific filter.
+   */
+  guint query_id;
+
+  /*
+   * If we are currently performing a query, we can push tasks here to be
+   * completed when the results come in.
+   */
+  GQueue queued_tasks;
+};
+
+typedef struct
+{
+  IdeClangClient *client;
+  GFile          *file;
+  guint           line;
+  guint           column;
+  guint           query_id;
+} Query;
+
+enum {
+  PROP_0,
+  PROP_CLIENT,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (IdeClangProposals, ide_clang_proposals, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+query_free (gpointer data)
+{
+  Query *q = data;
+
+  g_clear_object (&q->client);
+  g_clear_object (&q->file);
+  g_slice_free (Query, q);
+}
+
+static inline gboolean
+is_symbol_char (gunichar ch)
+{
+  return g_unichar_isalnum (ch) || ch == '_';
+}
+
+static void
+ide_clang_proposals_finalize (GObject *object)
+{
+  IdeClangProposals *self = (IdeClangProposals *)object;
+
+  g_clear_object (&self->client);
+
+  G_OBJECT_CLASS (ide_clang_proposals_parent_class)->finalize (object);
+}
+
+static void
+ide_clang_proposals_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  IdeClangProposals *self = IDE_CLANG_PROPOSALS (object);
+
+  switch (prop_id)
+    {
+    case PROP_CLIENT:
+      g_value_set_object (value, self->client);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_clang_proposals_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  IdeClangProposals *self = IDE_CLANG_PROPOSALS (object);
+
+  switch (prop_id)
+    {
+    case PROP_CLIENT:
+      self->client = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_clang_proposals_class_init (IdeClangProposalsClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_clang_proposals_finalize;
+  object_class->get_property = ide_clang_proposals_get_property;
+  object_class->set_property = ide_clang_proposals_set_property;
+
+  properties [PROP_CLIENT] =
+    g_param_spec_object ("client",
+                         "Client",
+                         "The client to the clang worker process",
+                         IDE_TYPE_CLANG_CLIENT,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+  
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_clang_proposals_init (IdeClangProposals *self)
+{
+  self->line = -1;
+  self->line_offset = -1;
+}
+
+static void
+ide_clang_proposals_clear (IdeClangProposals *self)
+{
+  GList *list;
+
+  g_assert (IDE_IS_CLANG_CLIENT (self));
+
+  self->line = -1;
+  self->line_offset = -1;
+  self->head = NULL;
+
+  ide_clear_string (&self->filter);
+  g_clear_pointer (&self->items, g_ptr_array_unref);
+
+  list = g_steal_pointer (&self->queued_tasks.head);
+
+  self->queued_tasks.head = NULL;
+  self->queued_tasks.tail = NULL;
+  self->queued_tasks.length = 0;
+
+  for (const GList *iter = list; iter != NULL; iter = iter->next)
+    {
+      IdeTask *task = iter->data;
+
+      ide_task_return_new_error (task,
+                                 G_IO_ERROR,
+                                 G_IO_ERROR_CANCELLED,
+                                 "Completion request was invalidated");
+    }
+
+  g_list_free_full (list, g_object_unref);
+}
+
+IdeClangProposals *
+ide_clang_proposals_new (IdeClangClient *client)
+{
+  g_return_val_if_fail (IDE_IS_CLANG_CLIENT (client), NULL);
+
+  return g_object_new (IDE_TYPE_CLANG_PROPOSALS,
+                       "client", client,
+                       NULL);
+}
+
+IdeClangClient *
+ide_clang_proposals_get_client (IdeClangProposals *self)
+{
+  g_return_val_if_fail (IDE_IS_CLANG_PROPOSALS (self), NULL);
+
+  return self->client;
+}
+
+static void
+ide_clang_proposals_refilter_array (IdeClangProposals *self)
+{
+  IdeClangCompletionItem *prev = NULL;
+  GList *head = NULL;
+
+  g_assert (IDE_IS_CLANG_PROPOSALS (self));
+
+  /*
+   * This filter will look through all entries in the array. Compare to
+   * ide_clang_proposals_refilter_list() which only looks at items that have
+   * already been filtered and is therefore only usable when typing
+   * additional characters.
+   */
+
+  if (self->items == NULL || self->items->len == 0)
+    return;
+
+  if (self->filter == NULL || self->filter[0] == 0)
+    {
+      for (guint i = 0; i < self->items->len; i++)
+        {
+          IdeClangCompletionItem *item = g_ptr_array_index (self->items, i);
+
+          if (prev != NULL)
+            {
+              item->link.prev = &prev->link;
+              prev->link.next = &item->link;
+            }
+          else
+            {
+              head = &item->link;
+            }
+
+          prev = item;
+        }
+    }
+  else
+    {
+      g_autofree gchar *folded = g_utf8_casefold (self->filter, -1);
+
+      for (guint i = 0; i < self->items->len; i++)
+        {
+          IdeClangCompletionItem *item = g_ptr_array_index (self->items, i);
+
+          if (!ide_completion_item_fuzzy_match (item->typed_text, folded, &item->priority))
+            continue;
+
+          if (prev != NULL)
+            {
+              item->link.prev = &prev->link;
+              prev->link.next = &item->link;
+            }
+          else
+            {
+              head = &item->link;
+            }
+
+          prev = item;
+        }
+    }
+
+  if (prev != NULL)
+    prev->link.next = NULL;
+
+  self->head = head;
+}
+
+static void
+ide_clang_proposals_refilter_list (IdeClangProposals *self)
+{
+  g_autofree gchar *folded = NULL;
+  IdeClangCompletionItem *prev = NULL;
+  GList *head = NULL;
+
+  g_assert (IDE_IS_CLANG_PROPOSALS (self));
+
+  /*
+   * This function only looks at items that have been previously
+   * filtered. This is useful so that we look at less and less
+   * data upon each key-press.
+   */
+
+  if (self->filter == NULL || self->filter[0] == 0)
+    {
+      ide_clang_proposals_refilter_array (self);
+      return;
+    }
+
+  folded = g_utf8_casefold (self->filter, -1);
+
+  for (const GList *iter = self->head; iter != NULL; iter = iter->next)
+    {
+      IdeClangCompletionItem *item = iter->data;
+
+      if (!ide_completion_item_fuzzy_match (item->typed_text, folded, &item->priority))
+        continue;
+
+      if (prev != NULL)
+        {
+          item->link.prev = &prev->link;
+          prev->link.next = &item->link;
+        }
+      else
+        {
+          head = &item->link;
+        }
+
+      prev = item;
+    }
+
+  if (prev != NULL)
+    prev->link.next = NULL;
+
+  self->head = head;
+}
+
+static void
+ide_clang_proposals_flush (IdeClangProposals *self,
+                           GPtrArray         *items,
+                           const GError      *error)
+{
+  GList *list;
+
+  g_assert (IDE_IS_CLANG_PROPOSALS (self));
+  g_assert (items != NULL || error != NULL);
+
+  g_clear_pointer (&self->items, g_ptr_array_unref);
+
+  if (items != NULL)
+    {
+      self->items = g_ptr_array_ref (items);
+      ide_clang_proposals_refilter_array (self);
+    }
+
+  list = g_steal_pointer (&self->queued_tasks.head);
+
+  self->queued_tasks.head = NULL;
+  self->queued_tasks.tail = NULL;
+  self->queued_tasks.length = 0;
+
+  for (const GList *iter = list; iter != NULL; iter = iter->next)
+    {
+      IdeTask *task = iter->data;
+
+      if (error != NULL)
+        ide_task_return_error (task, g_error_copy (error));
+      else
+        ide_task_return_boolean (task, TRUE);
+    }
+
+  g_list_free_full (list, g_object_unref);
+}
+
+static void
+ide_clang_proposals_build_worker (IdeTask      *task,
+                                  gpointer      source_object,
+                                  gpointer      task_data,
+                                  GCancellable *cancellable)
+{
+  GVariant *variant = task_data;
+  g_autoptr(GPtrArray) ret = NULL;
+  GVariant *value;
+  GVariantIter iter;
+  guint n_children;
+  guint index = 0;
+
+  g_assert (IDE_IS_TASK (task));
+  g_assert (IDE_IS_CLANG_PROPOSALS (source_object));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  n_children = (guint)g_variant_n_children (variant);
+  ret = g_ptr_array_new_full (n_children, g_object_unref);
+
+  g_variant_iter_init (&iter, variant);
+
+  while ((value = g_variant_iter_next_value (&iter)))
+    {
+      const gchar *typed_text;
+
+      if (!g_variant_lookup (value, "keyword", "&s", &typed_text))
+        typed_text = NULL;
+
+      g_ptr_array_add (ret, ide_clang_completion_item_new (variant, index, typed_text));
+
+      g_variant_unref (value);
+      index++;
+    }
+
+  ide_task_return_pointer (task,
+                           g_steal_pointer (&ret),
+                           (GDestroyNotify)g_ptr_array_unref);
+}
+
+static void
+ide_clang_proposals_build_async (IdeClangProposals   *self,
+                                 GVariant            *results,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+  g_autoptr(IdeTask) task = NULL;
+
+  g_assert (IDE_IS_CLANG_PROPOSALS (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, ide_clang_proposals_build_async);
+  ide_task_set_task_data (task, g_variant_ref (results), (GDestroyNotify)g_variant_unref);
+  ide_task_run_in_thread (task, ide_clang_proposals_build_worker);
+}
+
+static GPtrArray *
+ide_clang_proposals_build_finish (IdeClangProposals  *self,
+                                  GAsyncResult       *result,
+                                  GError            **error)
+{
+  g_assert (IDE_IS_CLANG_PROPOSALS (self));
+  g_assert (IDE_IS_TASK (result));
+
+  return ide_task_propagate_pointer (IDE_TASK (result), error);
+}
+
+static void
+ide_clang_proposals_query_complete_cb (GObject      *object,
+                                       GAsyncResult *result,
+                                       gpointer      user_data)
+{
+  IdeClangClient *client = (IdeClangClient *)object;
+  g_autoptr(GVariant) variant = NULL;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IDE_IS_CLANG_CLIENT (client));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  variant = ide_clang_client_complete_finish (client, result, &error);
+
+  if (error != NULL)
+    ide_task_return_error (task, g_steal_pointer (&error));
+  else
+    ide_task_return_pointer (task,
+                             g_steal_pointer (&variant),
+                             (GDestroyNotify)g_variant_unref);
+}
+
+static void
+ide_clang_proposals_query_build_flags_cb (GObject      *object,
+                                          GAsyncResult *result,
+                                          gpointer      user_data)
+{
+  IdeBuildSystem *build_system = (IdeBuildSystem *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  g_auto(GStrv) flags = NULL;
+  GCancellable *cancellable;
+  Query *query;
+
+  g_assert (IDE_IS_BUILD_SYSTEM (build_system));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  flags = ide_build_system_get_build_flags_finish (build_system, result, &error);
+
+  if (ide_task_return_error_if_cancelled (task))
+    return;
+
+  query = ide_task_get_task_data (task);
+  cancellable = ide_task_get_cancellable (task);
+
+  g_assert (query != NULL);
+  g_assert (G_IS_FILE (query->file));
+  g_assert (IDE_IS_CLANG_CLIENT (query->client));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  ide_clang_client_complete_async (query->client,
+                                   query->file,
+                                   (const gchar * const *)flags,
+                                   query->line,
+                                   query->column,
+                                   cancellable,
+                                   ide_clang_proposals_query_complete_cb,
+                                   g_steal_pointer (&task));
+}
+
+static void
+ide_clang_proposals_query_async (IdeClangProposals   *self,
+                                 IdeFile             *file,
+                                 guint                line,
+                                 guint                column,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+  g_autoptr(IdeTask) task = NULL;
+  IdeBuildSystem *build_system;
+  IdeContext *context;
+  Query *q;
+
+  g_assert (IDE_IS_CLANG_PROPOSALS (self));
+  g_assert (IDE_IS_FILE (file));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  context = ide_object_get_context (IDE_OBJECT (self->client));
+  build_system = ide_context_get_build_system (context);
+
+  q = g_slice_new0 (Query);
+  q->client = g_object_ref (self->client);
+  q->file = g_object_ref (ide_file_get_file (file));
+  q->line = line;
+  q->column = column;
+  q->query_id = ++self->query_id;
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, ide_clang_proposals_query_async);
+  ide_task_set_task_data (task, q, query_free);
+
+  ide_build_system_get_build_flags_async (build_system,
+                                          file,
+                                          cancellable,
+                                          ide_clang_proposals_query_build_flags_cb,
+                                          g_steal_pointer (&task));
+}
+
+static GVariant *
+ide_clang_proposals_query_finish (IdeClangProposals  *self,
+                                  GAsyncResult       *result,
+                                  GError            **error)
+{
+  g_autoptr(GVariant) ret = NULL;
+
+  g_return_val_if_fail (IDE_IS_CLANG_PROPOSALS (self), NULL);
+  g_return_val_if_fail (IDE_IS_TASK (result), NULL);
+
+  if ((ret = ide_task_propagate_pointer (IDE_TASK (result), error)))
+    {
+      Query *query = ide_task_get_task_data (IDE_TASK (result));
+
+      if (query->query_id != self->query_id)
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_CANCELLED,
+                       "Query is no longer valid");
+          return NULL;
+        }
+    }
+
+  return g_steal_pointer (&ret);
+}
+
+static void
+build_results_cb (GObject      *object,
+                  GAsyncResult *result,
+                  gpointer      user_data)
+{
+  IdeClangProposals *self = (IdeClangProposals *)object;
+  g_autoptr(GPtrArray) items = NULL;
+  g_autoptr(GError) error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_CLANG_PROPOSALS (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (user_data == NULL);
+
+  if (!(items = ide_clang_proposals_build_finish (self, result, &error)))
+    ide_clang_proposals_flush (self, NULL, error);
+  else
+    ide_clang_proposals_flush (self, items, NULL);
+
+  IDE_EXIT;
+}
+
+static void
+query_subprocess_cb (GObject      *object,
+                     GAsyncResult *result,
+                     gpointer      user_data)
+{
+  IdeClangProposals *self = (IdeClangProposals *)object;
+  g_autoptr(GVariant) ret = NULL;
+  g_autoptr(GError) error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_CLANG_PROPOSALS (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (user_data == NULL);
+
+  /*
+   * Currently, to integrate with GtkSourceView we need to inflate an object
+   * for each result. However, since that can easily be on the order of 20,000
+   * results, we need to do as little work as possible. Therefore, the
+   * completion objects just get an index+variant which can be accessed in
+   * O(1) running time but more importantly, is delayed until accessed.
+   *
+   * This is basically just being implemented so that we can land the clang
+   * subprocess. As soon as that lands, we'll start replacing the completion
+   * engine and avoid having to create the objects altogether in favor of a
+   * GListModel based approach.
+   */
+
+  if (!(ret = ide_clang_proposals_query_finish (self, result, &error)))
+    ide_clang_proposals_flush (self, NULL, error);
+  else
+    ide_clang_proposals_build_async (self,
+                                     ret,
+                                     self->cancellable,
+                                     build_results_cb,
+                                     NULL);
+
+
+  IDE_EXIT;
+}
+
+void
+ide_clang_proposals_populate_async (IdeClangProposals   *self,
+                                    const GtkTextIter   *iter,
+                                    gboolean             user_requested,
+                                    GCancellable        *cancellable,
+                                    GAsyncReadyCallback  callback,
+                                    gpointer             user_data)
+{
+  g_autoptr(GCancellable) prev_cancellable = NULL;
+  g_autoptr(IdeTask) task = NULL;
+  g_autofree gchar *word = NULL;
+  g_autofree gchar *slice = NULL;
+  GtkTextBuffer *buffer;
+  GtkTextIter begin;
+  GtkTextIter previous;
+  IdeFile *file;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_CLANG_PROPOSALS (self));
+  g_return_if_fail (iter != NULL);
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  buffer = gtk_text_iter_get_buffer (iter);
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, ide_clang_proposals_populate_async);
+  ide_task_set_task_data (task, g_object_ref (buffer), g_object_unref);
+
+  begin = *iter;
+  word = _ide_text_iter_current_symbol (iter, &begin);
+
+  if (dzl_str_empty0 (word))
+    {
+      /*
+       * If we have nothing to complete, then we want to try to avoid doing any
+       * sort of work unless the user force-requested the completion.
+       */
+      if (!user_requested)
+        {
+          ide_clang_proposals_clear (self);
+          ide_task_return_boolean (task, TRUE);
+          IDE_EXIT;
+        }
+    }
+
+  if (self->line < 0 || self->line_offset < 0)
+    IDE_GOTO (query_client);
+
+  gtk_text_buffer_get_iter_at_line_offset (buffer, &previous, self->line, self->line_offset);
+
+  /*
+   * If we are not at the same starting position and any previous query, then
+   * we cannot reuse those results. We have to requery first.
+   */
+  if (!gtk_text_iter_equal (&previous, &begin))
+    IDE_GOTO (query_client);
+
+  /*
+   * At this point, we know we can refilter results. However, we may not have
+   * have received those yet from the subprocess. If that is the case, queue
+   * any follow-up work until that happens.
+   */
+  if (!g_queue_is_empty (&self->queued_tasks))
+    {
+      ide_take_string (&self->filter, g_steal_pointer (&word));
+      g_queue_push_tail (&self->queued_tasks, g_steal_pointer (&task));
+      IDE_EXIT;
+    }
+
+  /* Unlikely, but is this the exact same query as before? */
+  if (dzl_str_equal0 (self->filter, word))
+    {
+      ide_task_return_boolean (task, TRUE);
+      IDE_EXIT;
+    }
+
+  /*
+   * Now we know we have results and can refilter them. If the current word
+   * contains the previous word as a prefix, then we can simply refilter using
+   * the linked-list (rather than reset from the array).
+   */
+  if (self->filter == NULL || (word && g_str_has_prefix (word, self->filter)))
+    {
+      ide_take_string (&self->filter, g_steal_pointer (&word));
+      ide_clang_proposals_refilter_list (self);
+      ide_task_return_boolean (task, TRUE);
+      IDE_EXIT;
+    }
+
+  /*
+   * So we can reuse the results, but since the user backspaced we have to
+   * clear the linked list and update by walking the whole array.
+   */
+  ide_take_string (&self->filter, g_steal_pointer (&word));
+  ide_clang_proposals_refilter_array (self);
+  ide_task_return_boolean (task, TRUE);
+
+  IDE_EXIT;
+
+
+query_client:
+
+  file = ide_buffer_get_file (IDE_BUFFER (buffer));
+
+  prev_cancellable = g_steal_pointer (&self->cancellable);
+  self->cancellable = g_cancellable_new ();
+
+  self->line = gtk_text_iter_get_line (&begin);
+  self->line_offset = gtk_text_iter_get_line_offset (&begin);
+
+  g_queue_push_tail (&self->queued_tasks, g_steal_pointer (&task));
+
+  ide_clang_proposals_query_async (self,
+                                   file,
+                                   self->line + 1,
+                                   self->line_offset + 1,
+                                   self->cancellable,
+                                   query_subprocess_cb,
+                                   NULL);
+
+  g_cancellable_cancel (prev_cancellable);
+
+  IDE_EXIT;
+}
+
+gboolean
+ide_clang_proposals_populate_finish (IdeClangProposals  *self,
+                                     GAsyncResult       *result,
+                                     GError            **error)
+{
+  g_return_val_if_fail (IDE_IS_CLANG_PROPOSALS (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return ide_task_propagate_boolean (IDE_TASK (result), error);
+}
+
+const GList *
+ide_clang_proposals_get_list (IdeClangProposals *self)
+{
+  g_return_val_if_fail (IDE_IS_CLANG_PROPOSALS (self), NULL);
+
+  return self->head;
+}
diff --git a/src/plugins/clang/ide-clang-proposals.h b/src/plugins/clang/ide-clang-proposals.h
new file mode 100644
index 000000000..2da71eb85
--- /dev/null
+++ b/src/plugins/clang/ide-clang-proposals.h
@@ -0,0 +1,44 @@
+/* ide-clang-proposals.h
+ *
+ * Copyright 2018 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/>.
+ */
+
+#pragma once
+
+#include <ide.h>
+
+#include "ide-clang-client.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_CLANG_PROPOSALS (ide_clang_proposals_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeClangProposals, ide_clang_proposals, IDE, CLANG_PROPOSALS, GObject)
+
+IdeClangProposals *ide_clang_proposals_new             (IdeClangClient       *client);
+IdeClangClient    *ide_clang_proposals_get_client      (IdeClangProposals    *self);
+const GList       *ide_clang_proposals_get_list        (IdeClangProposals    *self);
+void               ide_clang_proposals_populate_async  (IdeClangProposals    *self,
+                                                        const GtkTextIter    *iter,
+                                                        gboolean              user_requested,
+                                                        GCancellable         *cancellable,
+                                                        GAsyncReadyCallback   callback,
+                                                        gpointer              user_data);
+gboolean           ide_clang_proposals_populate_finish (IdeClangProposals    *self,
+                                                        GAsyncResult         *result,
+                                                        GError              **error);
+
+G_END_DECLS
diff --git a/src/plugins/clang/ide-clang.c b/src/plugins/clang/ide-clang.c
index 68ed022c5..323c5114a 100644
--- a/src/plugins/clang/ide-clang.c
+++ b/src/plugins/clang/ide-clang.c
@@ -38,7 +38,7 @@
 #define PRIORITY_INDEX_FILE   (500)
 #define PRIORITY_HIGHLIGHT    (300)
 
-#if 0
+#if 1
 # define PROBE G_STMT_START { g_printerr ("PROBE: %s\n", G_STRFUNC); } G_STMT_END
 #else
 # define PROBE G_STMT_START { } G_STMT_END
@@ -1165,6 +1165,7 @@ ide_clang_build_completion (GVariantBuilder    *builder,
                             CXCompletionResult *result)
 {
   GVariantBuilder chunks_builder;
+  g_autofree gchar *typed_text = NULL;
   guint n_chunks;
 
   g_assert (builder != NULL);
@@ -1183,14 +1184,21 @@ ide_clang_build_completion (GVariantBuilder    *builder,
   for (guint i = 0; i < n_chunks; i++)
     {
       g_auto(CXString) str = clang_getCompletionChunkText (result->CompletionString, i);
+      const gchar *text = clang_getCString (str);
       guint kind = clang_getCompletionChunkKind (result->CompletionString, i);
 
+      if (kind == CXCompletionChunk_TypedText && typed_text == NULL)
+        typed_text = g_strdup (text);
+
       g_variant_builder_open (&chunks_builder, G_VARIANT_TYPE_VARDICT);
-      g_variant_builder_add_parsed (&chunks_builder, "{%s,<%s>}", "text", clang_getCString (str));
+      g_variant_builder_add_parsed (&chunks_builder, "{%s,<%s>}", "text", text);
       g_variant_builder_add_parsed (&chunks_builder, "{%s,<%u>}", "kind", kind);
       g_variant_builder_close (&chunks_builder);
     }
 
+  if (typed_text != NULL)
+    g_variant_builder_add_parsed (builder, "{%s,<%s>}", "keyword", typed_text);
+
   g_variant_builder_add (builder, "{sv}", "chunks", g_variant_builder_end (&chunks_builder));
 }
 
@@ -1299,6 +1307,7 @@ ide_clang_complete_async (IdeClang            *self,
   state->column = column;
 
   task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_check_cancellable (task, FALSE);
   ide_task_set_source_tag (task, ide_clang_complete_async);
   ide_task_set_kind (task, IDE_TASK_KIND_COMPILER);
   ide_task_set_task_data (task, state, complete_free);
diff --git a/src/plugins/clang/meson.build b/src/plugins/clang/meson.build
index 845741b34..2d3d4f57e 100644
--- a/src/plugins/clang/meson.build
+++ b/src/plugins/clang/meson.build
@@ -7,6 +7,9 @@ clang_resources = gnome.compile_resources(
 )                                           
 
 clang_sources = [
+  'clang-plugin.c',
+  'ide-clang-client.c',
+  'ide-clang-client.h',
   'ide-clang-code-index-entries.c',
   'ide-clang-code-index-entries.h',
   'ide-clang-code-indexer.c',
@@ -21,15 +24,14 @@ clang_sources = [
   'ide-clang-highlighter.h',
   'ide-clang-preferences-addin.c',
   'ide-clang-preferences-addin.h',
+  'ide-clang-proposals.c',
+  'ide-clang-proposals.h',
   'ide-clang-symbol-node.c',
   'ide-clang-symbol-node.h',
   'ide-clang-symbol-resolver.c',
   'ide-clang-symbol-resolver.h',
   'ide-clang-symbol-tree.c',
   'ide-clang-symbol-tree.h',
-  'ide-clang-client.c',
-  'ide-clang-client.h',
-  'clang-plugin.c',
 ]
 
 gnome_builder_clang_sources = [


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