[gnome-builder] html: add very simple html autocompletion
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] html: add very simple html autocompletion
- Date: Thu, 1 Jan 2015 08:34:08 +0000 (UTC)
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 (©))
+ {
+ gunichar ch;
+
+ ch = gtk_text_iter_get_char (©);
+
+ if (ch == '/')
+ {
+ gtk_text_iter_backward_char (©);
+ ch = gtk_text_iter_get_char (©);
+ 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 (©))
+ return FALSE;
+
+ do
+ {
+ gunichar ch;
+
+ if (gtk_text_iter_ends_line (©))
+ return FALSE;
+
+ ch = gtk_text_iter_get_char (©);
+
+ if (ch == looking_for)
+ {
+ gtk_text_iter_backward_char (©);
+ return (gtk_text_iter_get_char (©) == '=');
+ }
+ }
+ while (gtk_text_iter_backward_char (©));
+
+ 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 (©, "='",
+ GTK_TEXT_SEARCH_TEXT_ONLY,
+ &match_start, &match_end,
+ NULL) ||
+ gtk_text_iter_backward_search (©, "=\"",
+ 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 (©))
+ {
+ ch = gtk_text_iter_get_char (©);
+
+ if (ch == '/')
+ {
+ GtkTextIter copy2 = copy;
+
+ gtk_text_iter_backward_char (©2);
+ if (gtk_text_iter_get_char (©2) == '<')
+ 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]