[gnome-builder] html: add very simple html autocompletion



commit a2a758a95905415db34ef4af424081e94edafd06
Author: Christian Hergert <christian hergert me>
Date:   Thu Jan 1 00:27:25 2015 -0800

    html: add very simple html autocompletion
    
    Someone should take this and run with it. In particular, we should
    import all the fancy w3c stuff (or load it dynamically from a dtd).
    Additionally, we should import various CSS stuff so we can complete
    those properly.
    
    Also, when detecting CSS, we always suggest css key names. We should
    determine when we are in a CSS value, and limit the results to values
    that are available for that key.
    
    We should do the same for HTML elements by only showing valid attribute
    names.

 src/editor/gb-source-view.c            |   45 ++++-
 src/gnome-builder.mk                   |    2 +
 src/html/gb-html-completion-provider.c |  393 ++++++++++++++++++++++++++++++++
 src/html/gb-html-completion-provider.h |   56 +++++
 4 files changed, 493 insertions(+), 3 deletions(-)
---
diff --git a/src/editor/gb-source-view.c b/src/editor/gb-source-view.c
index 56ad1cb..032e8a2 100644
--- a/src/editor/gb-source-view.c
+++ b/src/editor/gb-source-view.c
@@ -31,6 +31,7 @@
 #include "gb-cairo.h"
 #include "gb-editor-document.h"
 #include "gb-gtk.h"
+#include "gb-html-completion-provider.h"
 #include "gb-log.h"
 #include "gb-pango.h"
 #include "gb-source-auto-indenter.h"
@@ -50,6 +51,7 @@ struct _GbSourceViewPrivate
   GbSourceSearchHighlighter   *search_highlighter;
   GtkTextBuffer               *buffer;
   GbSourceAutoIndenter        *auto_indenter;
+  GtkSourceCompletionProvider *html_provider;
   GtkSourceCompletionProvider *snippets_provider;
   GtkSourceCompletionProvider *words_provider;
   GbSourceVim                 *vim;
@@ -1142,7 +1144,7 @@ gb_source_view_reload_auto_indenter (GbSourceView *view)
         auto_indenter = gb_source_auto_indenter_c_new ();
       else if (g_str_equal (lang_id, "python"))
         auto_indenter = gb_source_auto_indenter_python_new ();
-      else if (g_str_equal (lang_id, "xml"))
+      else if (g_str_equal (lang_id, "xml") || g_str_equal (lang_id, "html"))
         auto_indenter = gb_source_auto_indenter_xml_new ();
     }
 
@@ -1158,6 +1160,42 @@ gb_source_view_reload_auto_indenter (GbSourceView *view)
 }
 
 static void
+gb_source_view_reload_providers (GbSourceView *view)
+{
+  GtkSourceCompletion *completion;
+  GtkSourceLanguage *language;
+  GtkTextBuffer *buffer;
+  const gchar *lang_id = NULL;
+
+  g_return_if_fail (GB_IS_SOURCE_VIEW (view));
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+  language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (buffer));
+  completion = gtk_source_view_get_completion (GTK_SOURCE_VIEW (view));
+
+  if (language)
+    lang_id = gtk_source_language_get_id (language);
+
+  if (view->priv->html_provider)
+    {
+      gtk_source_completion_remove_provider (completion,
+                                             view->priv->html_provider,
+                                             NULL);
+      g_clear_object (&view->priv->html_provider);
+    }
+
+  if (g_strcmp0 (lang_id, "html") == 0)
+    {
+      view->priv->html_provider = gb_html_completion_provider_new ();
+      gtk_source_completion_add_provider (completion,
+                                          view->priv->html_provider,
+                                          NULL);
+    }
+
+  gb_source_view_reload_snippets (view);
+}
+
+static void
 on_language_set (GtkSourceBuffer *buffer,
                  GParamSpec      *pspec,
                  GbSourceView    *view)
@@ -1167,7 +1205,7 @@ on_language_set (GtkSourceBuffer *buffer,
 
   gb_source_view_disconnect_settings (view);
   gb_source_view_reload_auto_indenter (view);
-  gb_source_view_reload_snippets (view);
+  gb_source_view_reload_providers (view);
   gb_source_view_connect_settings (view);
 }
 
@@ -1264,7 +1302,7 @@ gb_source_view_notify_buffer (GObject    *object,
           GTK_TEXT_BUFFER (buffer));
 
       gb_source_view_reload_auto_indenter (view);
-      gb_source_view_reload_snippets (view);
+      gb_source_view_reload_providers (view);
 
       gb_source_view_connect_settings (view);
     }
@@ -2178,6 +2216,7 @@ gb_source_view_finalize (GObject *object)
   g_clear_pointer (&priv->snippets, g_queue_free);
   g_clear_object (&priv->search_highlighter);
   g_clear_object (&priv->auto_indenter);
+  g_clear_object (&priv->html_provider);
   g_clear_object (&priv->snippets_provider);
   g_clear_object (&priv->words_provider);
   g_clear_object (&priv->vim);
diff --git a/src/gnome-builder.mk b/src/gnome-builder.mk
index 49d32f1..76574ed 100644
--- a/src/gnome-builder.mk
+++ b/src/gnome-builder.mk
@@ -119,6 +119,8 @@ libgnome_builder_la_SOURCES = \
        src/git/gb-git-search-provider.h \
        src/git/gb-git-search-result.c \
        src/git/gb-git-search-result.h \
+       src/html/gb-html-completion-provider.c \
+       src/html/gb-html-completion-provider.h \
        src/html/gb-html-document.c \
        src/html/gb-html-document.h \
        src/html/gb-html-view.c \
diff --git a/src/html/gb-html-completion-provider.c b/src/html/gb-html-completion-provider.c
new file mode 100644
index 0000000..0d9a7cd
--- /dev/null
+++ b/src/html/gb-html-completion-provider.c
@@ -0,0 +1,393 @@
+/* gb-html-completion-provider.c
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+
+#include "gb-html-completion-provider.h"
+#include "trie.h"
+
+static Trie *attributes;
+static Trie *css_styles;
+static Trie *elements;
+
+enum {
+  MODE_NONE,
+  MODE_ELEMENT_START,
+  MODE_ELEMENT_END,
+  MODE_ATTRIBUTE_NAME,
+  MODE_ATTRIBUTE_VALUE,
+  MODE_CSS,
+};
+
+typedef struct
+{
+  GList *results;
+} SearchState;
+
+static void completion_provider_init (GtkSourceCompletionProviderIface *);
+
+G_DEFINE_TYPE_EXTENDED (GbHtmlCompletionProvider,
+                        gb_html_completion_provider,
+                        G_TYPE_OBJECT,
+                        0,
+                        G_IMPLEMENT_INTERFACE (GTK_SOURCE_TYPE_COMPLETION_PROVIDER,
+                                               completion_provider_init))
+
+GtkSourceCompletionProvider *
+gb_html_completion_provider_new (void)
+{
+  return g_object_new (GB_TYPE_HTML_COMPLETION_PROVIDER, NULL);
+}
+
+static gchar *
+get_word (GtkSourceCompletionContext *context)
+{
+  GtkTextIter word_start;
+  GtkTextIter iter;
+  gchar *word = NULL;
+
+  g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context), NULL);
+
+  if (gtk_source_completion_context_get_iter (context, &iter))
+    {
+      word_start = iter;
+
+      do {
+        gunichar ch;
+
+        if (!gtk_text_iter_backward_char (&word_start))
+          break;
+
+        ch = gtk_text_iter_get_char (&word_start);
+
+        if (g_unichar_isalnum (ch) || ch == '_')
+          continue;
+
+        gtk_text_iter_forward_char (&word_start);
+        break;
+
+      } while (TRUE);
+
+      word = gtk_text_iter_get_slice (&word_start, &iter);
+    }
+
+  return word;
+}
+
+static gboolean
+in_element (const GtkTextIter *iter)
+{
+  GtkTextIter copy = *iter;
+
+  /*
+   * this is a stupidly simple algorithm. walk backwards, until we reach either
+   * <, >, or start of buffer.
+   */
+
+  while (gtk_text_iter_backward_char (&copy))
+    {
+      gunichar ch;
+
+      ch = gtk_text_iter_get_char (&copy);
+
+      if (ch == '/')
+        {
+          gtk_text_iter_backward_char (&copy);
+          ch = gtk_text_iter_get_char (&copy);
+          if (ch == '<')
+            return MODE_ELEMENT_END;
+        }
+
+      if (ch == '>')
+        return FALSE;
+      else if (ch == '<')
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+in_attribute_value (const GtkTextIter *iter,
+                    gunichar           looking_for)
+{
+  GtkTextIter copy = *iter;
+
+  if (!gtk_text_iter_backward_char (&copy))
+    return FALSE;
+
+  do
+    {
+      gunichar ch;
+
+      if (gtk_text_iter_ends_line (&copy))
+        return FALSE;
+
+      ch = gtk_text_iter_get_char (&copy);
+
+      if (ch == looking_for)
+        {
+          gtk_text_iter_backward_char (&copy);
+          return (gtk_text_iter_get_char (&copy) == '=');
+        }
+    }
+  while (gtk_text_iter_backward_char (&copy));
+
+  return FALSE;
+}
+
+static gboolean
+in_attribute_named (const GtkTextIter *iter,
+                    const gchar       *name)
+{
+  GtkTextIter copy = *iter;
+  GtkTextIter match_start;
+  GtkTextIter match_end;
+  gboolean ret = FALSE;
+
+  if (gtk_text_iter_backward_search (&copy, "='",
+                                     GTK_TEXT_SEARCH_TEXT_ONLY,
+                                     &match_start, &match_end,
+                                     NULL) ||
+      gtk_text_iter_backward_search (&copy, "=\"",
+                                     GTK_TEXT_SEARCH_TEXT_ONLY,
+                                     &match_start, &match_end,
+                                     NULL))
+    {
+      GtkTextIter word_begin = match_start;
+      gchar *word;
+
+      gtk_text_iter_backward_chars (&word_begin, strlen (name));
+      word = gtk_text_iter_get_slice (&word_begin, &match_start);
+      ret = (g_strcmp0 (word, name) == 0);
+      g_free (word);
+    }
+
+  return ret;
+}
+
+static gint
+get_mode (GtkSourceCompletionContext *context)
+{
+  GtkTextIter iter;
+  GtkTextIter back;
+
+  g_return_val_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context), -1);
+
+  gtk_source_completion_context_get_iter (context, &iter);
+
+  /*
+   * Ignore the = after attribute name.
+   */
+  back = iter;
+  gtk_text_iter_backward_char (&back);
+  if (gtk_text_iter_get_char (&back) == '=')
+    return MODE_NONE;
+
+  /*
+   * Check for various state inside of element start (<).
+   */
+  if (in_element (&iter))
+    {
+      GtkTextIter copy = iter;
+      gunichar ch;
+
+      /*
+       * If there are no spaces until we reach <, then we are in element name.
+       */
+      while (gtk_text_iter_backward_char (&copy))
+        {
+          ch = gtk_text_iter_get_char (&copy);
+
+          if (ch == '/')
+            {
+              GtkTextIter copy2 = copy;
+
+              gtk_text_iter_backward_char (&copy2);
+              if (gtk_text_iter_get_char (&copy2) == '<')
+                return MODE_ELEMENT_END;
+            }
+
+          if (ch == '<')
+            return MODE_ELEMENT_START;
+
+          if (g_unichar_isalnum (ch))
+            continue;
+
+          break;
+        }
+
+      /*
+       * Now check to see if we are in an attribute value.
+       */
+      if (in_attribute_value (&iter, '"') || in_attribute_value (&iter, '\''))
+        {
+          /*
+           * If the attribute name is style, then we are in CSS.
+           */
+          if (in_attribute_named (&iter, "style"))
+            return MODE_CSS;
+
+          return MODE_ATTRIBUTE_VALUE;
+        }
+
+      /*
+       * Not in attribute value, but in element (and not the name). Must be
+       * attribute name. But only say so if we have moved past ' or ".
+       */
+      ch = gtk_text_iter_get_char (&back);
+      if (ch != '\'' && ch != '"')
+        return MODE_ATTRIBUTE_NAME;
+    }
+
+  return MODE_NONE;
+}
+
+static gboolean
+traverse_cb (Trie        *trie,
+             const gchar *key,
+             gpointer     value,
+             gpointer     user_data)
+{
+  SearchState *state = user_data;
+  GtkSourceCompletionItem *item;
+
+  g_return_val_if_fail (trie, FALSE);
+  g_return_val_if_fail (state, FALSE);
+
+  item = g_object_new (GTK_SOURCE_TYPE_COMPLETION_ITEM,
+                       "text", key,
+                       "label", key,
+                       NULL);
+
+  state->results = g_list_prepend (state->results, item);
+
+  return FALSE;
+}
+
+static void
+gb_html_completion_provider_populate (GtkSourceCompletionProvider *provider,
+                                      GtkSourceCompletionContext  *context)
+{
+  SearchState state = { 0 };
+  Trie *trie = NULL;
+  gchar *word;
+  gint mode;
+
+  g_return_if_fail (GB_IS_HTML_COMPLETION_PROVIDER (provider));
+  g_return_if_fail (GTK_SOURCE_IS_COMPLETION_CONTEXT (context));
+
+  mode = get_mode (context);
+  word = get_word (context);
+
+  switch (mode)
+    {
+    case MODE_NONE:
+      break;
+
+    case MODE_ELEMENT_END:
+    case MODE_ELEMENT_START:
+      trie = elements;
+      break;
+
+    case MODE_ATTRIBUTE_NAME:
+      trie = attributes;
+      break;
+
+    case MODE_CSS:
+      trie = css_styles;
+      break;
+
+    case MODE_ATTRIBUTE_VALUE:
+      break;
+
+    default:
+      break;
+    }
+
+  if (trie && word)
+    {
+      trie_traverse (trie, word, G_PRE_ORDER, G_TRAVERSE_LEAVES, -1,
+                     traverse_cb, &state);
+      state.results = g_list_reverse (state.results);
+    }
+
+  gtk_source_completion_context_add_proposals (context, provider,
+                                               state.results, TRUE);
+
+  g_list_foreach (state.results, (GFunc)g_object_unref, NULL);
+  g_list_free (state.results);
+
+  g_free (word);
+}
+
+static GdkPixbuf *
+gb_html_completion_provider_get_icon (GtkSourceCompletionProvider *provider)
+{
+  return NULL;
+}
+
+static void
+gb_html_completion_provider_class_init (GbHtmlCompletionProviderClass *klass)
+{
+  elements = trie_new (NULL);
+  attributes = trie_new (NULL);
+  css_styles = trie_new (NULL);
+
+#define ADD_STRING(dict, str) trie_insert(dict,str,str)
+
+  /*
+   * TODO: We should determine what are valid attributes for given elements
+   *       and only provide those based upon the completion context.
+   */
+
+  ADD_STRING (elements, "a");
+  ADD_STRING (elements, "body");
+  ADD_STRING (elements, "div");
+  ADD_STRING (elements, "head");
+  ADD_STRING (elements, "html");
+  ADD_STRING (elements, "li");
+  ADD_STRING (elements, "ol");
+  ADD_STRING (elements, "p");
+  ADD_STRING (elements, "table");
+  ADD_STRING (elements, "title");
+  ADD_STRING (elements, "ul");
+
+  ADD_STRING (attributes, "style");
+  ADD_STRING (attributes, "href");
+
+  ADD_STRING (css_styles, "border");
+  ADD_STRING (css_styles, "background");
+  ADD_STRING (css_styles, "background-image");
+  ADD_STRING (css_styles, "background-color");
+  ADD_STRING (css_styles, "text-align");
+
+#undef ADD_STRING
+}
+
+static void
+gb_html_completion_provider_init (GbHtmlCompletionProvider *self)
+{
+}
+
+static void
+completion_provider_init (GtkSourceCompletionProviderIface *iface)
+{
+  iface->get_icon = gb_html_completion_provider_get_icon;
+  iface->populate = gb_html_completion_provider_populate;
+}
diff --git a/src/html/gb-html-completion-provider.h b/src/html/gb-html-completion-provider.h
new file mode 100644
index 0000000..e987aba
--- /dev/null
+++ b/src/html/gb-html-completion-provider.h
@@ -0,0 +1,56 @@
+/* gb-html-completion-provider.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GB_HTML_COMPLETION_PROVIDER_H
+#define GB_HTML_COMPLETION_PROVIDER_H
+
+#include <gtksourceview/gtksource.h>
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_HTML_COMPLETION_PROVIDER            (gb_html_completion_provider_get_type())
+#define GB_HTML_COMPLETION_PROVIDER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GB_TYPE_HTML_COMPLETION_PROVIDER, GbHtmlCompletionProvider))
+#define GB_HTML_COMPLETION_PROVIDER_CONST(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GB_TYPE_HTML_COMPLETION_PROVIDER, GbHtmlCompletionProvider const))
+#define GB_HTML_COMPLETION_PROVIDER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  
GB_TYPE_HTML_COMPLETION_PROVIDER, GbHtmlCompletionProviderClass))
+#define GB_IS_HTML_COMPLETION_PROVIDER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GB_TYPE_HTML_COMPLETION_PROVIDER))
+#define GB_IS_HTML_COMPLETION_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  
GB_TYPE_HTML_COMPLETION_PROVIDER))
+#define GB_HTML_COMPLETION_PROVIDER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  
GB_TYPE_HTML_COMPLETION_PROVIDER, GbHtmlCompletionProviderClass))
+
+typedef struct _GbHtmlCompletionProvider        GbHtmlCompletionProvider;
+typedef struct _GbHtmlCompletionProviderClass   GbHtmlCompletionProviderClass;
+typedef struct _GbHtmlCompletionProviderPrivate GbHtmlCompletionProviderPrivate;
+
+struct _GbHtmlCompletionProvider
+{
+  GObject parent;
+
+  /*< private >*/
+  GbHtmlCompletionProviderPrivate *priv;
+};
+
+struct _GbHtmlCompletionProviderClass
+{
+  GObjectClass parent;
+};
+
+GType                        gb_html_completion_provider_get_type (void);
+GtkSourceCompletionProvider *gb_html_completion_provider_new      (void);
+
+G_END_DECLS
+
+#endif /* GB_HTML_COMPLETION_PROVIDER_H */


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