[gnome-builder/wip/libide] libide: add snippets engine to LibIDE



commit f1c139d4c38aaf5141674c9b53ac4b1065a5367c
Author: Christian Hergert <christian hergert me>
Date:   Wed Feb 25 12:12:54 2015 -0800

    libide: add snippets engine to LibIDE
    
    This is a fairly substantial move to bring snippets into LibIDE. We need
    them in the same location as IdeSourceView, and they are probably generally
    useful anyway.
    
    A follow up commit will plumb these into the IdeContext and IdeSourceView.
    
    I've switched some of these to use the new style G_DECLARE* macros, and I'd
    like to see more of that. We also need to run through the headers and audit
    the includes. (Generally I only want ide-object.h/ide-types.h included in
    these files if possible).
    
    The completion provider and parsers are not public API, and therefore not
    exported into the Gir.

 libide/Makefile.am                              |   17 +
 libide/ide-source-snippet-chunk.c               |  355 ++++++++
 libide/ide-source-snippet-chunk.h               |   51 ++
 libide/ide-source-snippet-completion-item.c     |  182 ++++
 libide/ide-source-snippet-completion-item.h     |   61 ++
 libide/ide-source-snippet-completion-provider.c |  377 +++++++++
 libide/ide-source-snippet-completion-provider.h |   60 ++
 libide/ide-source-snippet-context.c             |  712 ++++++++++++++++
 libide/ide-source-snippet-context.h             |   51 ++
 libide/ide-source-snippet-parser.c              |  617 ++++++++++++++
 libide/ide-source-snippet-parser.h              |   60 ++
 libide/ide-source-snippet-private.h             |   57 ++
 libide/ide-source-snippet.c                     | 1009 +++++++++++++++++++++++
 libide/ide-source-snippet.h                     |   60 ++
 libide/ide-source-snippets-manager.c            |  251 ++++++
 libide/ide-source-snippets-manager.h            |   45 +
 libide/ide-source-snippets.c                    |  176 ++++
 libide/ide-source-snippets.h                    |   67 ++
 libide/ide-types.h                              |   10 +
 libide/ide.h                                    |    5 +
 po/POTFILES.in                                  |    6 +
 21 files changed, 4229 insertions(+), 0 deletions(-)
---
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 65e01bf..f4802e0 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -123,6 +123,16 @@ libide_1_0_la_public_sources = \
        libide/ide-source-location.h \
        libide/ide-source-range.c \
        libide/ide-source-range.h \
+       libide/ide-source-snippet-chunk.c \
+       libide/ide-source-snippet-chunk.h \
+       libide/ide-source-snippet-context.c \
+       libide/ide-source-snippet-context.h \
+       libide/ide-source-snippet.c \
+       libide/ide-source-snippet.h \
+       libide/ide-source-snippets-manager.c \
+       libide/ide-source-snippets-manager.h \
+       libide/ide-source-snippets.c \
+       libide/ide-source-snippets.h \
        libide/ide-source-view.c \
        libide/ide-source-view.h \
        libide/ide-symbol-resolver.c \
@@ -187,6 +197,13 @@ libide_1_0_la_SOURCES = \
        libide/ide-line-change-gutter-renderer.h \
        libide/ide-search-reducer.c \
        libide/ide-search-reducer.h \
+       libide/ide-source-snippet-completion-item.c \
+       libide/ide-source-snippet-completion-item.h \
+       libide/ide-source-snippet-completion-provider.c \
+       libide/ide-source-snippet-completion-provider.h \
+       libide/ide-source-snippet-parser.c \
+       libide/ide-source-snippet-parser.h \
+       libide/ide-source-snippet-private.h \
        libide/tasks/ide-load-directory-task.c \
        libide/tasks/ide-load-directory-task.h \
        libide/theatrics/ide-animation.c \
diff --git a/libide/ide-source-snippet-chunk.c b/libide/ide-source-snippet-chunk.c
new file mode 100644
index 0000000..b2ec336
--- /dev/null
+++ b/libide/ide-source-snippet-chunk.c
@@ -0,0 +1,355 @@
+/* ide-source-snippet-chunk.c
+ *
+ * Copyright (C) 2013 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 <glib/gi18n.h>
+
+#include "ide-source-snippet-chunk.h"
+#include "ide-source-snippet-context.h"
+
+struct _IdeSourceSnippetChunk
+{
+  GObject                  parent_instance;
+
+  IdeSourceSnippetContext *context;
+  guint                    context_changed_handler;
+  gint                     tab_stop;
+  gchar                   *spec;
+  gchar                   *text;
+  guint                    text_set : 1;
+};
+
+G_DEFINE_TYPE (IdeSourceSnippetChunk, ide_source_snippet_chunk, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_CONTEXT,
+  PROP_SPEC,
+  PROP_TAB_STOP,
+  PROP_TEXT,
+  PROP_TEXT_SET,
+  LAST_PROP
+};
+
+static GParamSpec *gParamSpecs[LAST_PROP];
+
+IdeSourceSnippetChunk *
+ide_source_snippet_chunk_new (void)
+{
+  return g_object_new (IDE_TYPE_SOURCE_SNIPPET_CHUNK, NULL);
+}
+
+/**
+ * ide_source_snippet_chunk_copy:
+ *
+ * Copies the source snippet.
+ *
+ * Returns: (transfer full): An #IdeSourceSnippetChunk.
+ */
+IdeSourceSnippetChunk *
+ide_source_snippet_chunk_copy (IdeSourceSnippetChunk *chunk)
+{
+  IdeSourceSnippetChunk *ret;
+
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET_CHUNK (chunk), NULL);
+
+  ret = g_object_new (IDE_TYPE_SOURCE_SNIPPET_CHUNK,
+                      "spec", chunk->spec,
+                      "tab-stop", chunk->tab_stop,
+                      NULL);
+
+  return ret;
+}
+
+static void
+on_context_changed (IdeSourceSnippetContext *context,
+                    IdeSourceSnippetChunk   *chunk)
+{
+  gchar *text;
+
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET_CHUNK (chunk));
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET_CONTEXT (context));
+
+  if (!chunk->text_set)
+    {
+      text = ide_source_snippet_context_expand (context, chunk->spec);
+      ide_source_snippet_chunk_set_text (chunk, text);
+      g_free (text);
+    }
+}
+
+/**
+ * ide_source_snippet_chunk_get_context:
+ *
+ * Gets the context for the snippet insertion.
+ *
+ * Returns: (transfer none): An #IdeSourceSnippetContext.
+ */
+IdeSourceSnippetContext *
+ide_source_snippet_chunk_get_context (IdeSourceSnippetChunk *chunk)
+{
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET_CHUNK (chunk), NULL);
+
+  return chunk->context;
+}
+
+void
+ide_source_snippet_chunk_set_context (IdeSourceSnippetChunk   *chunk,
+                                      IdeSourceSnippetContext *context)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET_CHUNK (chunk));
+  g_return_if_fail (!context || IDE_IS_SOURCE_SNIPPET_CONTEXT (context));
+
+  if (context != chunk->context)
+    {
+      if (chunk->context_changed_handler)
+        {
+          g_signal_handler_disconnect (chunk->context,
+                                       chunk->context_changed_handler);
+          chunk->context_changed_handler = 0;
+        }
+
+      g_clear_object (&chunk->context);
+
+      if (context)
+        {
+          chunk->context = context ? g_object_ref (context) : NULL;
+          chunk->context_changed_handler =
+            g_signal_connect_object (chunk->context,
+                                     "changed",
+                                     G_CALLBACK (on_context_changed),
+                                     chunk,
+                                     0);
+        }
+
+      g_object_notify_by_pspec (G_OBJECT (chunk), gParamSpecs[PROP_CONTEXT]);
+    }
+}
+
+const gchar *
+ide_source_snippet_chunk_get_spec (IdeSourceSnippetChunk *chunk)
+{
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET_CHUNK (chunk), NULL);
+  return chunk->spec;
+}
+
+void
+ide_source_snippet_chunk_set_spec (IdeSourceSnippetChunk *chunk,
+                                   const gchar           *spec)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET_CHUNK (chunk));
+
+  g_free (chunk->spec);
+  chunk->spec = g_strdup (spec);
+  g_object_notify_by_pspec (G_OBJECT (chunk), gParamSpecs[PROP_SPEC]);
+}
+
+gint
+ide_source_snippet_chunk_get_tab_stop (IdeSourceSnippetChunk *chunk)
+{
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET_CHUNK (chunk), 0);
+  return chunk->tab_stop;
+}
+
+void
+ide_source_snippet_chunk_set_tab_stop (IdeSourceSnippetChunk *chunk,
+                                       gint                   tab_stop)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET_CHUNK (chunk));
+  chunk->tab_stop = tab_stop;
+  g_object_notify_by_pspec (G_OBJECT (chunk), gParamSpecs[PROP_TAB_STOP]);
+}
+
+const gchar *
+ide_source_snippet_chunk_get_text (IdeSourceSnippetChunk *chunk)
+{
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET_CHUNK (chunk), NULL);
+  return chunk->text ? chunk->text : "";
+}
+
+void
+ide_source_snippet_chunk_set_text (IdeSourceSnippetChunk *chunk,
+                                   const gchar           *text)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET_CHUNK (chunk));
+
+  g_free (chunk->text);
+  chunk->text = g_strdup (text);
+  g_object_notify_by_pspec (G_OBJECT (chunk), gParamSpecs[PROP_TEXT]);
+}
+
+gboolean
+ide_source_snippet_chunk_get_text_set (IdeSourceSnippetChunk *chunk)
+{
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET_CHUNK (chunk), FALSE);
+  return chunk->text_set;
+}
+
+void
+ide_source_snippet_chunk_set_text_set (IdeSourceSnippetChunk *chunk,
+                                       gboolean               text_set)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET_CHUNK (chunk));
+  chunk->text_set = !!text_set;
+  g_object_notify_by_pspec (G_OBJECT (chunk), gParamSpecs[PROP_TEXT_SET]);
+}
+
+static void
+ide_source_snippet_chunk_finalize (GObject *object)
+{
+  IdeSourceSnippetChunk *chunk = (IdeSourceSnippetChunk *)object;
+
+  g_clear_pointer (&chunk->spec, g_free);
+  g_clear_pointer (&chunk->text, g_free);
+  g_clear_object (&chunk->context);
+
+  G_OBJECT_CLASS (ide_source_snippet_chunk_parent_class)->finalize (object);
+}
+
+static void
+ide_source_snippet_chunk_get_property (GObject    *object,
+                                       guint       prop_id,
+                                       GValue     *value,
+                                       GParamSpec *pspec)
+{
+  IdeSourceSnippetChunk *chunk = IDE_SOURCE_SNIPPET_CHUNK (object);
+
+  switch (prop_id)
+    {
+    case PROP_CONTEXT:
+      g_value_set_object (value, ide_source_snippet_chunk_get_context (chunk));
+      break;
+
+    case PROP_SPEC:
+      g_value_set_string (value, ide_source_snippet_chunk_get_spec (chunk));
+      break;
+
+    case PROP_TAB_STOP:
+      g_value_set_int (value, ide_source_snippet_chunk_get_tab_stop (chunk));
+      break;
+
+    case PROP_TEXT:
+      g_value_set_string (value, ide_source_snippet_chunk_get_text (chunk));
+      break;
+
+    case PROP_TEXT_SET:
+      g_value_set_boolean (value, ide_source_snippet_chunk_get_text_set (chunk));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_source_snippet_chunk_set_property (GObject      *object,
+                                       guint         prop_id,
+                                       const GValue *value,
+                                       GParamSpec   *pspec)
+{
+  IdeSourceSnippetChunk *chunk = IDE_SOURCE_SNIPPET_CHUNK (object);
+
+  switch (prop_id)
+    {
+    case PROP_CONTEXT:
+      ide_source_snippet_chunk_set_context (chunk, g_value_get_object (value));
+      break;
+
+    case PROP_TAB_STOP:
+      ide_source_snippet_chunk_set_tab_stop (chunk, g_value_get_int (value));
+      break;
+
+    case PROP_SPEC:
+      ide_source_snippet_chunk_set_spec (chunk, g_value_get_string (value));
+      break;
+
+    case PROP_TEXT:
+      ide_source_snippet_chunk_set_text (chunk, g_value_get_string (value));
+      break;
+
+    case PROP_TEXT_SET:
+      ide_source_snippet_chunk_set_text_set (chunk, g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_source_snippet_chunk_class_init (IdeSourceSnippetChunkClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_source_snippet_chunk_finalize;
+  object_class->get_property = ide_source_snippet_chunk_get_property;
+  object_class->set_property = ide_source_snippet_chunk_set_property;
+
+  gParamSpecs[PROP_CONTEXT] =
+    g_param_spec_object ("context",
+                         _("Context"),
+                         _("The snippet context."),
+                         IDE_TYPE_SOURCE_SNIPPET_CONTEXT,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_CONTEXT,
+                                   gParamSpecs[PROP_CONTEXT]);
+
+  gParamSpecs[PROP_SPEC] =
+    g_param_spec_string ("spec",
+                         _("Spec"),
+                         _("The specification to expand using the contxt."),
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_SPEC,
+                                   gParamSpecs[PROP_SPEC]);
+
+  gParamSpecs[PROP_TAB_STOP] =
+    g_param_spec_int ("tab-stop",
+                      _("Tab Stop"),
+                      _("The tab stop for the chunk."),
+                      -1,
+                      G_MAXINT,
+                      -1,
+                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_TAB_STOP,
+                                   gParamSpecs[PROP_TAB_STOP]);
+
+  gParamSpecs[PROP_TEXT] =
+    g_param_spec_string ("text",
+                         _("Text"),
+                         _("The text for the chunk."),
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_TEXT,
+                                   gParamSpecs[PROP_TEXT]);
+
+  gParamSpecs[PROP_TEXT_SET] =
+    g_param_spec_boolean ("text-set",
+                          _("Text Set"),
+                          _("If the text property has been manually set."),
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_TEXT_SET,
+                                   gParamSpecs[PROP_TEXT_SET]);
+}
+
+static void
+ide_source_snippet_chunk_init (IdeSourceSnippetChunk *chunk)
+{
+  chunk->tab_stop = -1;
+  chunk->spec = g_strdup ("");
+}
diff --git a/libide/ide-source-snippet-chunk.h b/libide/ide-source-snippet-chunk.h
new file mode 100644
index 0000000..1098b89
--- /dev/null
+++ b/libide/ide-source-snippet-chunk.h
@@ -0,0 +1,51 @@
+/* ide-source-snippet-chunk.h
+ *
+ * Copyright (C) 2013 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 IDE_SOURCE_SNIPPET_CHUNK_H
+#define IDE_SOURCE_SNIPPET_CHUNK_H
+
+#include "ide-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_SOURCE_SNIPPET_CHUNK (ide_source_snippet_chunk_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeSourceSnippetChunk, ide_source_snippet_chunk,
+                      IDE, SOURCE_SNIPPET_CHUNK, GObject)
+
+IdeSourceSnippetChunk   *ide_source_snippet_chunk_new          (void);
+IdeSourceSnippetChunk   *ide_source_snippet_chunk_copy         (IdeSourceSnippetChunk   *chunk);
+IdeSourceSnippetContext *ide_source_snippet_chunk_get_context  (IdeSourceSnippetChunk   *chunk);
+void                     ide_source_snippet_chunk_set_context  (IdeSourceSnippetChunk   *chunk,
+                                                                IdeSourceSnippetContext *context);
+const gchar             *ide_source_snippet_chunk_get_spec     (IdeSourceSnippetChunk   *chunk);
+void                     ide_source_snippet_chunk_set_spec     (IdeSourceSnippetChunk   *chunk,
+                                                                const gchar             *spec);
+gint                     ide_source_snippet_chunk_get_tab_stop (IdeSourceSnippetChunk   *chunk);
+void                     ide_source_snippet_chunk_set_tab_stop (IdeSourceSnippetChunk   *chunk,
+                                                                gint                    tab_stop);
+const gchar             *ide_source_snippet_chunk_get_text     (IdeSourceSnippetChunk   *chunk);
+void                     ide_source_snippet_chunk_set_text     (IdeSourceSnippetChunk   *chunk,
+                                                                const gchar             *text);
+gboolean                 ide_source_snippet_chunk_get_text_set (IdeSourceSnippetChunk   *chunk);
+void                     ide_source_snippet_chunk_set_text_set (IdeSourceSnippetChunk   *chunk,
+                                                                gboolean                text_set);
+
+G_END_DECLS
+
+#endif /* IDE_SOURCE_SNIPPET_CHUNK_H */
diff --git a/libide/ide-source-snippet-completion-item.c b/libide/ide-source-snippet-completion-item.c
new file mode 100644
index 0000000..7615e52
--- /dev/null
+++ b/libide/ide-source-snippet-completion-item.c
@@ -0,0 +1,182 @@
+/* ide-source-snippet-completion-item.c
+ *
+ * Copyright (C) 2013 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 <gio/gio.h>
+#include <glib/gi18n.h>
+
+#include "ide-source-snippet-completion-item.h"
+
+struct _IdeSourceSnippetCompletionItemPrivate
+{
+  IdeSourceSnippet *snippet;
+};
+
+enum {
+  PROP_0,
+  PROP_SNIPPET,
+  LAST_PROP
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+static void init_proposal_iface (GtkSourceCompletionProposalIface *iface);
+
+G_DEFINE_TYPE_EXTENDED (IdeSourceSnippetCompletionItem,
+                        ide_source_snippet_completion_item,
+                        G_TYPE_OBJECT,
+                        0,
+                        G_IMPLEMENT_INTERFACE (GTK_SOURCE_TYPE_COMPLETION_PROPOSAL,
+                                               init_proposal_iface)
+                        G_ADD_PRIVATE (IdeSourceSnippetCompletionItem))
+
+GtkSourceCompletionProposal *
+ide_source_snippet_completion_item_new (IdeSourceSnippet *snippet)
+{
+  g_return_val_if_fail (!snippet || IDE_IS_SOURCE_SNIPPET (snippet), NULL);
+
+  return g_object_new (IDE_TYPE_SOURCE_SNIPPET_COMPLETION_ITEM,
+                       "snippet", snippet,
+                       NULL);
+}
+
+IdeSourceSnippet *
+ide_source_snippet_completion_item_get_snippet (IdeSourceSnippetCompletionItem *item)
+{
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET_COMPLETION_ITEM (item), NULL);
+  return item->priv->snippet;
+}
+
+void
+ide_source_snippet_completion_item_set_snippet (IdeSourceSnippetCompletionItem *item,
+                                               IdeSourceSnippet               *snippet)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET_COMPLETION_ITEM (item));
+  g_clear_object (&item->priv->snippet);
+  item->priv->snippet = g_object_ref (snippet);
+}
+
+static void
+ide_source_snippet_completion_item_finalize (GObject *object)
+{
+  IdeSourceSnippetCompletionItemPrivate *priv;
+
+  priv = IDE_SOURCE_SNIPPET_COMPLETION_ITEM (object)->priv;
+
+  g_clear_object (&priv->snippet);
+
+  G_OBJECT_CLASS (ide_source_snippet_completion_item_parent_class)->finalize (object);
+}
+
+static void
+ide_source_snippet_completion_item_get_property (GObject    *object,
+                                                guint       prop_id,
+                                                GValue     *value,
+                                                GParamSpec *pspec)
+{
+  IdeSourceSnippetCompletionItem *item = IDE_SOURCE_SNIPPET_COMPLETION_ITEM (object);
+
+  switch (prop_id)
+    {
+    case PROP_SNIPPET:
+      g_value_set_object (value, ide_source_snippet_completion_item_get_snippet (item));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_source_snippet_completion_item_set_property (GObject      *object,
+                                                guint         prop_id,
+                                                const GValue *value,
+                                                GParamSpec   *pspec)
+{
+  IdeSourceSnippetCompletionItem *item = IDE_SOURCE_SNIPPET_COMPLETION_ITEM (object);
+
+  switch (prop_id)
+    {
+    case PROP_SNIPPET:
+      ide_source_snippet_completion_item_set_snippet (item, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_source_snippet_completion_item_class_init (IdeSourceSnippetCompletionItemClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_source_snippet_completion_item_finalize;
+  object_class->get_property = ide_source_snippet_completion_item_get_property;
+  object_class->set_property = ide_source_snippet_completion_item_set_property;
+
+  gParamSpecs[PROP_SNIPPET] =
+    g_param_spec_object ("snippet",
+                         _("Snippet"),
+                         _("The snippet to insert."),
+                         IDE_TYPE_SOURCE_SNIPPET,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_SNIPPET,
+                                   gParamSpecs[PROP_SNIPPET]);
+}
+
+static void
+ide_source_snippet_completion_item_init (IdeSourceSnippetCompletionItem *item)
+{
+  item->priv = ide_source_snippet_completion_item_get_instance_private (item);
+}
+
+static gchar *
+get_label (GtkSourceCompletionProposal *p)
+{
+  IdeSourceSnippetCompletionItem *item = IDE_SOURCE_SNIPPET_COMPLETION_ITEM (p);
+  const gchar *trigger = NULL;
+  const gchar *description = NULL;
+
+  if (item->priv->snippet)
+    {
+      trigger = ide_source_snippet_get_trigger (item->priv->snippet);
+      description = ide_source_snippet_get_description (item->priv->snippet);
+    }
+
+  if (description) 
+    return g_strdup_printf ("%s: %s", trigger, description);
+  else
+    return g_strdup(trigger);
+}
+
+static GdkPixbuf *
+get_icon (GtkSourceCompletionProposal *proposal)
+{
+  /*
+   * TODO: Load pixbufs from resources, assign based on completion type.
+   */
+
+  return NULL;
+}
+
+static void
+init_proposal_iface (GtkSourceCompletionProposalIface *iface)
+{
+  iface->get_label = get_label;
+  iface->get_icon = get_icon;
+}
diff --git a/libide/ide-source-snippet-completion-item.h b/libide/ide-source-snippet-completion-item.h
new file mode 100644
index 0000000..b71be81
--- /dev/null
+++ b/libide/ide-source-snippet-completion-item.h
@@ -0,0 +1,61 @@
+/* ide-source-snippet-completion-item.h
+ *
+ * Copyright (C) 2013 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 IDE_SOURCE_SNIPPET_COMPLETION_ITEM_H
+#define IDE_SOURCE_SNIPPET_COMPLETION_ITEM_H
+
+#include <gtksourceview/gtksourcecompletionproposal.h>
+
+#include "ide-source-snippet.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_SOURCE_SNIPPET_COMPLETION_ITEM            (ide_source_snippet_completion_item_get_type())
+#define IDE_SOURCE_SNIPPET_COMPLETION_ITEM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
IDE_TYPE_SOURCE_SNIPPET_COMPLETION_ITEM, IdeSourceSnippetCompletionItem))
+#define IDE_SOURCE_SNIPPET_COMPLETION_ITEM_CONST(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
IDE_TYPE_SOURCE_SNIPPET_COMPLETION_ITEM, IdeSourceSnippetCompletionItem const))
+#define IDE_SOURCE_SNIPPET_COMPLETION_ITEM_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  
IDE_TYPE_SOURCE_SNIPPET_COMPLETION_ITEM, IdeSourceSnippetCompletionItemClass))
+#define IDE_IS_SOURCE_SNIPPET_COMPLETION_ITEM(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
IDE_TYPE_SOURCE_SNIPPET_COMPLETION_ITEM))
+#define IDE_IS_SOURCE_SNIPPET_COMPLETION_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  
IDE_TYPE_SOURCE_SNIPPET_COMPLETION_ITEM))
+#define IDE_SOURCE_SNIPPET_COMPLETION_ITEM_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  
IDE_TYPE_SOURCE_SNIPPET_COMPLETION_ITEM, IdeSourceSnippetCompletionItemClass))
+
+typedef struct _IdeSourceSnippetCompletionItem        IdeSourceSnippetCompletionItem;
+typedef struct _IdeSourceSnippetCompletionItemClass   IdeSourceSnippetCompletionItemClass;
+typedef struct _IdeSourceSnippetCompletionItemPrivate IdeSourceSnippetCompletionItemPrivate;
+
+struct _IdeSourceSnippetCompletionItem
+{
+  GObject parent;
+
+  /*< private >*/
+  IdeSourceSnippetCompletionItemPrivate *priv;
+};
+
+struct _IdeSourceSnippetCompletionItemClass
+{
+  GObjectClass parent_class;
+};
+
+GType                        ide_source_snippet_completion_item_get_type    (void);
+GtkSourceCompletionProposal *ide_source_snippet_completion_item_new         (IdeSourceSnippet *snippet);
+IdeSourceSnippet             *ide_source_snippet_completion_item_get_snippet (IdeSourceSnippetCompletionItem 
*item);
+void                         ide_source_snippet_completion_item_set_snippet (IdeSourceSnippetCompletionItem 
*item,
+                                                                            IdeSourceSnippet               
*snippet);
+
+G_END_DECLS
+
+#endif /* IDE_SOURCE_SNIPPET_COMPLETION_ITEM_H */
diff --git a/libide/ide-source-snippet-completion-provider.c b/libide/ide-source-snippet-completion-provider.c
new file mode 100644
index 0000000..39ac1b8
--- /dev/null
+++ b/libide/ide-source-snippet-completion-provider.c
@@ -0,0 +1,377 @@
+/* ide-source-snippet-completion-provider.c
+ *
+ * Copyright (C) 2013 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 <glib/gi18n.h>
+#include <gtksourceview/gtksourcecompletionitem.h>
+
+#include "ide-source-snippet-completion-item.h"
+#include "ide-source-snippet-completion-provider.h"
+
+static void init_provider (GtkSourceCompletionProviderIface *iface);
+
+G_DEFINE_TYPE_EXTENDED (IdeSourceSnippetCompletionProvider,
+                        ide_source_snippet_completion_provider,
+                        G_TYPE_OBJECT,
+                        0,
+                        G_IMPLEMENT_INTERFACE (GTK_SOURCE_TYPE_COMPLETION_PROVIDER,
+                                               init_provider))
+
+struct _IdeSourceSnippetCompletionProviderPrivate
+{
+  IdeSourceView     *source_view;
+  IdeSourceSnippets *snippets;
+};
+
+typedef struct
+{
+  GtkSourceCompletionProvider *provider;
+  gchar                       *word;
+  GList                       *list;
+} SearchState;
+
+enum {
+  PROP_0,
+  PROP_SNIPPETS,
+  PROP_SOURCE_VIEW,
+  LAST_PROP
+};
+
+static GParamSpec *gParamSpecs[LAST_PROP];
+
+GtkSourceCompletionProvider *
+ide_source_snippet_completion_provider_new (IdeSourceView     *source_view,
+                                           IdeSourceSnippets *snippets)
+{
+  return g_object_new (IDE_TYPE_SOURCE_SNIPPET_COMPLETION_PROVIDER,
+                       "source-view", source_view,
+                       "snippets", snippets,
+                       NULL);
+}
+
+IdeSourceSnippets *
+ide_source_snippet_completion_provider_get_snippets (IdeSourceSnippetCompletionProvider *provider)
+{
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET_COMPLETION_PROVIDER (provider), NULL);
+
+  return provider->priv->snippets;
+}
+
+void
+ide_source_snippet_completion_provider_set_snippets (IdeSourceSnippetCompletionProvider *provider,
+                                                    IdeSourceSnippets                  *snippets)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET_COMPLETION_PROVIDER (provider));
+
+  g_clear_object (&provider->priv->snippets);
+  provider->priv->snippets = snippets ? g_object_ref (snippets) : NULL;
+  g_object_notify_by_pspec (G_OBJECT (provider), gParamSpecs[PROP_SNIPPETS]);
+}
+
+static void
+ide_source_snippet_completion_provider_finalize (GObject *object)
+{
+  IdeSourceSnippetCompletionProviderPrivate *priv;
+
+  priv = IDE_SOURCE_SNIPPET_COMPLETION_PROVIDER (object)->priv;
+
+  g_clear_object (&priv->snippets);
+
+  if (priv->source_view)
+    g_object_remove_weak_pointer (G_OBJECT (priv->source_view),
+                                  (gpointer *) &priv->source_view);
+
+  G_OBJECT_CLASS (ide_source_snippet_completion_provider_parent_class)->finalize (object);
+}
+
+static void
+ide_source_snippet_completion_provider_get_property (GObject    *object,
+                                                    guint       prop_id,
+                                                    GValue     *value,
+                                                    GParamSpec *pspec)
+{
+  IdeSourceSnippetCompletionProvider *provider = IDE_SOURCE_SNIPPET_COMPLETION_PROVIDER (object);
+
+  switch (prop_id)
+    {
+    case PROP_SOURCE_VIEW:
+      g_value_set_object (value, provider->priv->source_view);
+      break;
+
+    case PROP_SNIPPETS:
+      g_value_set_object (value, ide_source_snippet_completion_provider_get_snippets (provider));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_source_snippet_completion_provider_set_property (GObject      *object,
+                                                    guint         prop_id,
+                                                    const GValue *value,
+                                                    GParamSpec   *pspec)
+{
+  IdeSourceSnippetCompletionProvider *provider = IDE_SOURCE_SNIPPET_COMPLETION_PROVIDER (object);
+
+  switch (prop_id)
+    {
+    case PROP_SOURCE_VIEW:
+      if (provider->priv->source_view)
+        {
+          g_object_remove_weak_pointer (G_OBJECT (provider->priv->source_view),
+                                        (gpointer *) &provider->priv->source_view);
+          provider->priv->source_view = NULL;
+        }
+      if ((provider->priv->source_view = g_value_get_object (value)))
+        g_object_add_weak_pointer (G_OBJECT (provider->priv->source_view),
+                                   (gpointer *) &provider->priv->source_view);
+      break;
+
+    case PROP_SNIPPETS:
+      ide_source_snippet_completion_provider_set_snippets (provider, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_source_snippet_completion_provider_class_init (IdeSourceSnippetCompletionProviderClass *klass)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (klass);
+  object_class->finalize = ide_source_snippet_completion_provider_finalize;
+  object_class->get_property = ide_source_snippet_completion_provider_get_property;
+  object_class->set_property = ide_source_snippet_completion_provider_set_property;
+  g_type_class_add_private (object_class, sizeof (IdeSourceSnippetCompletionProviderPrivate));
+
+  gParamSpecs[PROP_SOURCE_VIEW] =
+    g_param_spec_object ("source-view",
+                         _("Source View"),
+                         _("The source view to insert snippet into."),
+                         IDE_TYPE_SOURCE_VIEW,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_SOURCE_VIEW,
+                                   gParamSpecs[PROP_SOURCE_VIEW]);
+
+  gParamSpecs[PROP_SNIPPETS] =
+    g_param_spec_object ("snippets",
+                         _("Snippets"),
+                         _("The snippets to complete with this provider."),
+                         IDE_TYPE_SOURCE_SNIPPETS,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_SNIPPETS,
+                                   gParamSpecs[PROP_SNIPPETS]);
+}
+
+static void
+ide_source_snippet_completion_provider_init (IdeSourceSnippetCompletionProvider *provider)
+{
+  provider->priv =
+    G_TYPE_INSTANCE_GET_PRIVATE (provider,
+                                 IDE_TYPE_SOURCE_SNIPPET_COMPLETION_PROVIDER,
+                                 IdeSourceSnippetCompletionProviderPrivate);
+}
+
+static gboolean
+stop_on_predicate (gunichar ch,
+                   gpointer data)
+{
+  switch (ch)
+    {
+    case '_':
+      return FALSE;
+
+    case ')':
+    case '(':
+    case '&':
+    case '*':
+    case '{':
+    case '}':
+    case ' ':
+    case '\t':
+    case '[':
+    case ']':
+    case '=':
+    case '"':
+    case '\'':
+      return TRUE;
+
+    default:
+      return !g_unichar_isalnum (ch);
+    }
+}
+
+static gchar *
+get_word (GtkSourceCompletionProvider *provider,
+          GtkTextIter                 *iter)
+{
+  GtkTextBuffer *buffer;
+  GtkTextIter end;
+
+  gtk_text_iter_assign (&end, iter);
+  buffer = gtk_text_iter_get_buffer (iter);
+
+  if (!gtk_text_iter_backward_find_char (iter, stop_on_predicate, NULL, NULL))
+    return gtk_text_buffer_get_text (buffer, iter, &end, TRUE);
+
+  gtk_text_iter_forward_char (iter);
+
+  return gtk_text_iter_get_text (iter, &end);
+}
+
+static GdkPixbuf *
+provider_get_icon (GtkSourceCompletionProvider *provider)
+{
+  return NULL;
+}
+
+static gint
+provider_get_interactive_delay (GtkSourceCompletionProvider *provider)
+{
+  return 0;
+}
+
+static gint
+provider_get_priority (GtkSourceCompletionProvider *provider)
+{
+  return 200;
+}
+
+static gchar *
+provider_get_name (GtkSourceCompletionProvider *provider)
+{
+  return g_strdup (_("Snippets"));
+}
+
+static void
+foreach_snippet (gpointer data,
+                 gpointer user_data)
+{
+  GtkSourceCompletionProposal *item;
+  IdeSourceSnippet *snippet = data;
+  SearchState *state = user_data;
+  const char *trigger;
+
+  trigger = ide_source_snippet_get_trigger (snippet);
+  if (!g_str_has_prefix (trigger, state->word))
+    return;
+
+  item = ide_source_snippet_completion_item_new (snippet);
+  state->list = g_list_append (state->list, item);
+}
+
+static void
+provider_populate (GtkSourceCompletionProvider *provider,
+                   GtkSourceCompletionContext  *context)
+{
+  IdeSourceSnippetCompletionProviderPrivate *priv;
+  SearchState state = { 0 };
+  GtkTextIter iter;
+
+  priv = IDE_SOURCE_SNIPPET_COMPLETION_PROVIDER (provider)->priv;
+
+  if (!priv->snippets)
+    {
+      gtk_source_completion_context_add_proposals (context, provider, NULL, TRUE);
+      return;
+    }
+
+  gtk_source_completion_context_get_iter (context, &iter);
+
+  state.list = NULL;
+  state.provider = provider;
+  state.word = get_word (provider, &iter);
+
+  if (state.word && *state.word)
+    ide_source_snippets_foreach (priv->snippets, state.word, foreach_snippet,
+                                &state);
+
+  /*
+   * XXX: GtkSourceView seems to be warning quite a bit inside here
+   *      right now about g_object_ref(). But ... it doesn't seem to be us?
+   */
+  gtk_source_completion_context_add_proposals (context, provider, state.list, TRUE);
+
+  g_list_foreach (state.list, (GFunc) g_object_unref, NULL);
+  g_list_free (state.list);
+  g_free (state.word);
+}
+
+static gboolean
+provider_activate_proposal (GtkSourceCompletionProvider *provider,
+                            GtkSourceCompletionProposal *proposal,
+                            GtkTextIter                 *iter)
+{
+  IdeSourceSnippetCompletionProviderPrivate *priv;
+  IdeSourceSnippetCompletionItem *item;
+  IdeSourceSnippet *snippet;
+  GtkTextBuffer *buffer;
+  GtkTextIter end;
+  gchar *word;
+
+  priv = IDE_SOURCE_SNIPPET_COMPLETION_PROVIDER (provider)->priv;
+
+  if (priv->source_view)
+    {
+      item = IDE_SOURCE_SNIPPET_COMPLETION_ITEM (proposal);
+      snippet = ide_source_snippet_completion_item_get_snippet (item);
+      if (snippet)
+        {
+          /*
+           * Fetching the word will move us back to the beginning of it.
+           */
+          gtk_text_iter_assign (&end, iter);
+          word = get_word (provider, iter);
+          g_free (word);
+
+          /*
+           * Now delete the current word since it will get overwritten
+           * by the insertion of the snippet.
+           */
+          buffer = gtk_text_iter_get_buffer (iter);
+          gtk_text_buffer_delete (buffer, iter, &end);
+
+          /*
+           * Now push snippet onto the snippet stack of the view.
+           */
+          snippet = ide_source_snippet_copy (snippet);
+          ide_source_view_push_snippet (IDE_SOURCE_VIEW (priv->source_view),
+                                       snippet);
+          g_object_unref (snippet);
+
+          return TRUE;
+        }
+    }
+
+  return FALSE;
+}
+
+static void
+init_provider (GtkSourceCompletionProviderIface *iface)
+{
+  iface->activate_proposal = provider_activate_proposal;
+  iface->get_icon = provider_get_icon;
+  iface->get_interactive_delay = provider_get_interactive_delay;
+  iface->get_name = provider_get_name;
+  iface->get_priority = provider_get_priority;
+  iface->populate = provider_populate;
+}
diff --git a/libide/ide-source-snippet-completion-provider.h b/libide/ide-source-snippet-completion-provider.h
new file mode 100644
index 0000000..73d5476
--- /dev/null
+++ b/libide/ide-source-snippet-completion-provider.h
@@ -0,0 +1,60 @@
+/* ide-source-snippet-completion-provider.h
+ *
+ * Copyright (C) 2013 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 IDE_SOURCE_SNIPPET_COMPLETION_PROVIDER_H
+#define IDE_SOURCE_SNIPPET_COMPLETION_PROVIDER_H
+
+#include <gtksourceview/gtksourcecompletionprovider.h>
+
+#include "ide-source-snippets.h"
+#include "ide-source-view.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_SOURCE_SNIPPET_COMPLETION_PROVIDER            
(ide_source_snippet_completion_provider_get_type())
+#define IDE_SOURCE_SNIPPET_COMPLETION_PROVIDER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
IDE_TYPE_SOURCE_SNIPPET_COMPLETION_PROVIDER, IdeSourceSnippetCompletionProvider))
+#define IDE_SOURCE_SNIPPET_COMPLETION_PROVIDER_CONST(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
IDE_TYPE_SOURCE_SNIPPET_COMPLETION_PROVIDER, IdeSourceSnippetCompletionProvider const))
+#define IDE_SOURCE_SNIPPET_COMPLETION_PROVIDER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  
IDE_TYPE_SOURCE_SNIPPET_COMPLETION_PROVIDER, IdeSourceSnippetCompletionProviderClass))
+#define IDE_IS_SOURCE_SNIPPET_COMPLETION_PROVIDER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
IDE_TYPE_SOURCE_SNIPPET_COMPLETION_PROVIDER))
+#define IDE_IS_SOURCE_SNIPPET_COMPLETION_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  
IDE_TYPE_SOURCE_SNIPPET_COMPLETION_PROVIDER))
+#define IDE_SOURCE_SNIPPET_COMPLETION_PROVIDER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  
IDE_TYPE_SOURCE_SNIPPET_COMPLETION_PROVIDER, IdeSourceSnippetCompletionProviderClass))
+
+typedef struct _IdeSourceSnippetCompletionProvider        IdeSourceSnippetCompletionProvider;
+typedef struct _IdeSourceSnippetCompletionProviderClass   IdeSourceSnippetCompletionProviderClass;
+typedef struct _IdeSourceSnippetCompletionProviderPrivate IdeSourceSnippetCompletionProviderPrivate;
+
+struct _IdeSourceSnippetCompletionProvider
+{
+  GObject parent;
+
+  /*< private >*/
+  IdeSourceSnippetCompletionProviderPrivate *priv;
+};
+
+struct _IdeSourceSnippetCompletionProviderClass
+{
+  GObjectClass parent_class;
+};
+
+GType                        ide_source_snippet_completion_provider_get_type (void);
+GtkSourceCompletionProvider *ide_source_snippet_completion_provider_new      (IdeSourceView     *source_view,
+                                                                             IdeSourceSnippets *snippets);
+
+G_END_DECLS
+
+#endif /* IDE_SOURCE_SNIPPET_COMPLETION_PROVIDER_H */
diff --git a/libide/ide-source-snippet-context.c b/libide/ide-source-snippet-context.c
new file mode 100644
index 0000000..f5102ba
--- /dev/null
+++ b/libide/ide-source-snippet-context.c
@@ -0,0 +1,712 @@
+/* ide-source-snippet-context.c
+ *
+ * Copyright (C) 2013 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 <errno.h>
+#include <glib/gi18n.h>
+#include <stdlib.h>
+
+#include "ide-source-snippet-context.h"
+
+/**
+ * SECTION:ide-source-snippet-context:
+ * @title: IdeSourceSnippetContext
+ * @short_description: Context for expanding #IdeSourceSnippetChunk<!-- -->'s.
+ *
+ * This class is currently used primary as a hashtable. However, the longer
+ * term goal is to have it hold onto a GjsContext as well as other languages
+ * so that #IdeSourceSnippetChunk<!-- -->'s can expand themselves by executing
+ * script within the context.
+ *
+ * The #IdeSourceSnippet will build the context and then expand each of the
+ * chunks during the insertion/edit phase.
+ */
+
+struct _IdeSourceSnippetContext
+{
+  GObject     parent_instance;
+
+  GHashTable *shared;
+  GHashTable *variables;
+  gchar      *line_prefix;
+  gint        tab_width;
+  guint       use_spaces : 1;
+};
+
+struct _IdeSourceSnippetContextClass
+{
+  GObjectClass parent;
+};
+
+G_DEFINE_TYPE (IdeSourceSnippetContext, ide_source_snippet_context, G_TYPE_OBJECT)
+
+enum {
+  CHANGED,
+  LAST_SIGNAL
+};
+
+typedef gchar *(*InputFilter) (const gchar *input);
+
+static GHashTable *gFilters;
+static guint gSignals[LAST_SIGNAL];
+
+IdeSourceSnippetContext *
+ide_source_snippet_context_new (void)
+{
+  return g_object_new (IDE_TYPE_SOURCE_SNIPPET_CONTEXT, NULL);
+}
+
+void
+ide_source_snippet_context_dump (IdeSourceSnippetContext *context)
+{
+  GHashTableIter iter;
+  gpointer key;
+  gpointer value;
+
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET_CONTEXT (context));
+
+  g_hash_table_iter_init (&iter, context->variables);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    g_print (" %s=%s\n", (gchar *) key, (gchar *) value);
+}
+
+void
+ide_source_snippet_context_clear_variables (IdeSourceSnippetContext *context)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET_CONTEXT (context));
+
+  g_hash_table_remove_all (context->variables);
+}
+
+void
+ide_source_snippet_context_add_variable (IdeSourceSnippetContext *context,
+                                        const gchar            *key,
+                                        const gchar            *value)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET_CONTEXT (context));
+  g_return_if_fail (key);
+
+  g_hash_table_replace (context->variables, g_strdup (key), g_strdup (value));
+}
+
+const gchar *
+ide_source_snippet_context_get_variable (IdeSourceSnippetContext *context,
+                                        const gchar            *key)
+{
+  const gchar *ret;
+
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET_CONTEXT (context), NULL);
+
+  if (!(ret = g_hash_table_lookup (context->variables, key)))
+    ret = g_hash_table_lookup (context->shared, key);
+
+  return ret;
+}
+
+static gchar *
+filter_lower (const gchar *input)
+{
+  return g_utf8_strdown (input, -1);
+}
+
+static gchar *
+filter_upper (const gchar *input)
+{
+  return g_utf8_strup (input, -1);
+}
+
+static gchar *
+filter_capitalize (const gchar *input)
+{
+  gunichar c;
+  GString *str;
+
+  c = g_utf8_get_char (input);
+
+  if (g_unichar_isupper (c))
+    return g_strdup (input);
+
+  str = g_string_new (NULL);
+  input = g_utf8_next_char (input);
+  g_string_append_unichar (str, g_unichar_toupper (c));
+  g_string_append (str, input);
+
+  return g_string_free (str, FALSE);
+}
+
+static gchar *
+filter_html (const gchar *input)
+{
+  gunichar c;
+  GString *str;
+
+  str = g_string_new (NULL);
+
+  for (; *input; input = g_utf8_next_char (input))
+    {
+      c = g_utf8_get_char (input);
+      switch (c)
+        {
+        case '<':
+          g_string_append_len (str, "&lt;", 4);
+          break;
+
+        case '>':
+          g_string_append_len (str, "&gt;", 4);
+          break;
+
+        default:
+          g_string_append_unichar (str, c);
+          break;
+        }
+    }
+
+  return g_string_free (str, FALSE);
+}
+
+static gchar *
+filter_camelize (const gchar *input)
+{
+  gboolean next_is_upper = TRUE;
+  gboolean skip = FALSE;
+  gunichar c;
+  GString *str;
+
+  if (!strchr (input, '_') && !strchr (input, ' ') && !strchr (input, '-'))
+    return filter_capitalize (input);
+
+  str = g_string_new (NULL);
+
+  for (; *input; input = g_utf8_next_char (input))
+    {
+      c = g_utf8_get_char (input);
+
+      switch (c)
+        {
+        case '_':
+        case '-':
+        case ' ':
+          next_is_upper = TRUE;
+          skip = TRUE;
+          break;
+
+        default:
+          break;
+        }
+
+      if (skip)
+        {
+          skip = FALSE;
+          continue;
+        }
+
+      if (next_is_upper)
+        {
+          c = g_unichar_toupper (c);
+          next_is_upper = FALSE;
+        }
+      else
+        c = g_unichar_tolower (c);
+
+      g_string_append_unichar (str, c);
+    }
+
+  return g_string_free (str, FALSE);
+}
+
+static gchar *
+filter_functify (const gchar *input)
+{
+  gunichar last = 0;
+  gunichar c;
+  gunichar n;
+  GString *str;
+
+  str = g_string_new (NULL);
+
+  for (; *input; input = g_utf8_next_char (input))
+    {
+      c = g_utf8_get_char (input);
+      n = g_utf8_get_char (g_utf8_next_char (input));
+
+      if (last)
+        {
+          if ((g_unichar_islower (last) && g_unichar_isupper (c)) ||
+              (g_unichar_isupper (c) && g_unichar_islower (n)))
+            g_string_append_c (str, '_');
+        }
+
+      if ((c == ' ') || (c == '-'))
+        c = '_';
+
+      g_string_append_unichar (str, g_unichar_tolower (c));
+
+      last = c;
+    }
+
+  return g_string_free (str, FALSE);
+}
+
+static gchar *
+filter_namespace (const gchar *input)
+{
+  gunichar last = 0;
+  gunichar c;
+  gunichar n;
+  GString *str;
+  gboolean first_is_lower = FALSE;
+
+  str = g_string_new (NULL);
+
+  for (; *input; input = g_utf8_next_char (input))
+    {
+      c = g_utf8_get_char (input);
+      n = g_utf8_get_char (g_utf8_next_char (input));
+
+      if (c == '_')
+        break;
+
+      if (last)
+        {
+          if ((g_unichar_islower (last) && g_unichar_isupper (c)) ||
+              (g_unichar_isupper (c) && g_unichar_islower (n)))
+            break;
+        }
+      else
+        first_is_lower = g_unichar_islower (c);
+
+      if ((c == ' ') || (c == '-'))
+        break;
+
+      g_string_append_unichar (str, c);
+
+      last = c;
+    }
+
+  if (first_is_lower)
+    {
+      gchar *ret;
+
+      ret = filter_capitalize (str->str);
+      g_string_free (str, TRUE);
+      return ret;
+    }
+
+  return g_string_free (str, FALSE);
+}
+
+static gchar *
+filter_class (const gchar *input)
+{
+  gchar *camel;
+  gchar *ns;
+  gchar *ret = NULL;
+
+  camel = filter_camelize (input);
+  ns = filter_namespace (input);
+
+  if (g_str_has_prefix (camel, ns))
+    ret = g_strdup (camel + strlen (ns));
+  else
+    {
+      ret = camel;
+      camel = NULL;
+    }
+
+  g_free (camel);
+  g_free (ns);
+
+  return ret;
+}
+
+static gchar *
+filter_instance (const gchar *input)
+{
+  const gchar *tmp;
+  gchar *funct = NULL;
+  gchar *ret;
+
+  if (!strchr (input, '_'))
+    {
+      funct = filter_functify (input);
+      input = funct;
+    }
+
+  if ((tmp = strrchr (input, '_')))
+    ret = g_strdup (tmp+1);
+  else
+    ret = g_strdup (input);
+
+  g_free (funct);
+
+  return ret;
+}
+
+static gchar *
+filter_space (const gchar *input)
+{
+  GString *str;
+
+  str = g_string_new (NULL);
+  for (; *input; input = g_utf8_next_char (input))
+    g_string_append_c (str, ' ');
+
+  return g_string_free (str, FALSE);
+}
+
+static gchar *
+filter_stripsuffix (const gchar *input)
+{
+  const gchar *endpos;
+
+  g_return_val_if_fail (input, NULL);
+
+  endpos = strrchr (input, '.');
+  if (endpos)
+    return g_strndup (input, (endpos - input));
+
+  return g_strdup (input);
+}
+
+static gchar *
+apply_filter (gchar       *input,
+              const gchar *filter)
+{
+  InputFilter filter_func;
+  gchar *tmp;
+
+  filter_func = g_hash_table_lookup (gFilters, filter);
+  if (filter_func)
+    {
+      tmp = input;
+      input = filter_func (input);
+      g_free (tmp);
+    }
+
+  return input;
+}
+
+static gchar *
+apply_filters (GString     *str,
+               const gchar *filters_list)
+{
+  gchar **filter_names;
+  gchar *input = g_string_free (str, FALSE);
+  gint i;
+
+  filter_names = g_strsplit (filters_list, "|", 0);
+
+  for (i = 0; filter_names[i]; i++)
+    input = apply_filter (input, filter_names[i]);
+
+  g_strfreev (filter_names);
+
+  return input;
+}
+
+static gchar *
+scan_forward (const gchar  *input,
+              const gchar **endpos,
+              gunichar      needle)
+{
+  const gchar *begin = input;
+
+  for (; *input; input = g_utf8_next_char (input))
+    {
+      gunichar c = g_utf8_get_char (input);
+
+      if (c == needle)
+        {
+          *endpos = input;
+          return g_strndup (begin, (input - begin));
+        }
+    }
+
+  *endpos = NULL;
+
+  return NULL;
+}
+
+gchar *
+ide_source_snippet_context_expand (IdeSourceSnippetContext *context,
+                                  const gchar            *input)
+{
+  const gchar *expand;
+  gunichar c;
+  gboolean is_dynamic;
+  GString *str;
+  gchar key[12];
+  glong n;
+  gint i;
+
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET_CONTEXT (context), NULL);
+  g_return_val_if_fail (input, NULL);
+
+  is_dynamic = (*input == '$');
+
+  str = g_string_new (NULL);
+
+  for (; *input; input = g_utf8_next_char (input))
+    {
+      c = g_utf8_get_char (input);
+      if (c == '\\')
+        {
+          input = g_utf8_next_char (input);
+          if (!*input)
+            break;
+          c = g_utf8_get_char (input);
+        }
+      else if (is_dynamic && c == '$')
+        {
+          input = g_utf8_next_char (input);
+          if (!*input)
+            break;
+          c = g_utf8_get_char (input);
+          if (g_unichar_isdigit (c))
+            {
+              errno = 0;
+              n = strtol (input, (gchar * *) &input, 10);
+              if (((n == LONG_MIN) || (n == LONG_MAX)) && errno == ERANGE)
+                break;
+              input--;
+              g_snprintf (key, sizeof key, "%ld", n);
+              key[sizeof key - 1] = '\0';
+              expand = ide_source_snippet_context_get_variable (context, key);
+              if (expand)
+                g_string_append (str, expand);
+              continue;
+            }
+          else
+            {
+              if (strchr (input, '|'))
+                {
+                  gchar *lkey;
+
+                  lkey = g_strndup (input, strchr (input, '|') - input);
+                  expand = ide_source_snippet_context_get_variable (context, lkey);
+                  if (expand)
+                    {
+                      g_string_append (str, expand);
+                      input = strchr (input, '|') - 1;
+                    }
+                  else
+                    input += strlen (input) - 1;
+                }
+              else
+                {
+                  expand = ide_source_snippet_context_get_variable (context, input);
+                  if (expand)
+                    g_string_append (str, expand);
+                  else
+                    {
+                      g_string_append_c (str, '$');
+                      g_string_append (str, input);
+                    }
+                  input += strlen (input) - 1;
+                }
+              continue;
+            }
+        }
+      else if (is_dynamic && c == '|')
+        return apply_filters (str, input + 1);
+      else if (c == '`')
+        {
+          const gchar *endpos = NULL;
+          gchar *slice;
+
+          slice = scan_forward (input + 1, &endpos, '`');
+
+          if (slice)
+            {
+              gchar *expanded;
+
+              input = endpos;
+
+              expanded = ide_source_snippet_context_expand (context, slice);
+
+              g_string_append (str, expanded);
+
+              g_free (expanded);
+              g_free (slice);
+
+              continue;
+            }
+        }
+      else if (c == '\t')
+        {
+          if (context->use_spaces)
+            for (i = 0; i < context->tab_width; i++)
+              g_string_append_c (str, ' ');
+
+          else
+            g_string_append_c (str, '\t');
+          continue;
+        }
+      else if (c == '\n')
+        {
+          g_string_append_c (str, '\n');
+          if (context->line_prefix)
+            g_string_append (str, context->line_prefix);
+          continue;
+        }
+      g_string_append_unichar (str, c);
+    }
+
+  return g_string_free (str, FALSE);
+}
+
+void
+ide_source_snippet_context_set_tab_width (IdeSourceSnippetContext *context,
+                                         gint                    tab_width)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET_CONTEXT (context));
+  context->tab_width = tab_width;
+}
+
+void
+ide_source_snippet_context_set_use_spaces (IdeSourceSnippetContext *context,
+                                          gboolean                use_spaces)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET_CONTEXT (context));
+  context->use_spaces = !!use_spaces;
+}
+
+void
+ide_source_snippet_context_set_line_prefix (IdeSourceSnippetContext *context,
+                                           const gchar            *line_prefix)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET_CONTEXT (context));
+  g_free (context->line_prefix);
+  context->line_prefix = g_strdup (line_prefix);
+}
+
+void
+ide_source_snippet_context_emit_changed (IdeSourceSnippetContext *context)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET_CONTEXT (context));
+  g_signal_emit (context, gSignals[CHANGED], 0);
+}
+
+static gchar *
+run_command (const gchar *command)
+{
+  GError *error = NULL;
+  gchar *output = NULL;
+  gchar **argv = NULL;
+  gint argc = 0;
+
+  if (!g_shell_parse_argv (command, &argc, &argv, NULL))
+    return NULL;
+
+  /*
+   * TODO: Run in project directory.
+   */
+
+  if (!g_spawn_sync (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &output, NULL, NULL, &error))
+    {
+      g_printerr ("%s\n", error->message);
+      g_error_free (error);
+    }
+
+  g_strfreev (argv);
+
+  return g_strstrip (output);
+}
+
+static void
+ide_source_snippet_context_finalize (GObject *object)
+{
+  IdeSourceSnippetContext *context = (IdeSourceSnippetContext *)object;
+
+  g_clear_pointer (&context->shared, (GDestroyNotify)g_hash_table_unref);
+  g_clear_pointer (&context->variables, (GDestroyNotify)g_hash_table_unref);
+
+  G_OBJECT_CLASS (ide_source_snippet_context_parent_class)->finalize (object);
+}
+
+static void
+ide_source_snippet_context_class_init (IdeSourceSnippetContextClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_source_snippet_context_finalize;
+
+  gSignals[CHANGED] = g_signal_new ("changed",
+                                    IDE_TYPE_SOURCE_SNIPPET_CONTEXT,
+                                    G_SIGNAL_RUN_FIRST,
+                                    0,
+                                    NULL,
+                                    NULL,
+                                    g_cclosure_marshal_VOID__VOID,
+                                    G_TYPE_NONE,
+                                    0);
+
+  gFilters = g_hash_table_new (g_str_hash, g_str_equal);
+  g_hash_table_insert (gFilters, (gpointer) "lower", filter_lower);
+  g_hash_table_insert (gFilters, (gpointer) "upper", filter_upper);
+  g_hash_table_insert (gFilters, (gpointer) "capitalize", filter_capitalize);
+  g_hash_table_insert (gFilters, (gpointer) "html", filter_html);
+  g_hash_table_insert (gFilters, (gpointer) "camelize", filter_camelize);
+  g_hash_table_insert (gFilters, (gpointer) "functify", filter_functify);
+  g_hash_table_insert (gFilters, (gpointer) "namespace", filter_namespace);
+  g_hash_table_insert (gFilters, (gpointer) "class", filter_class);
+  g_hash_table_insert (gFilters, (gpointer) "space", filter_space);
+  g_hash_table_insert (gFilters, (gpointer) "stripsuffix", filter_stripsuffix);
+  g_hash_table_insert (gFilters, (gpointer) "instance", filter_instance);
+}
+
+static void
+ide_source_snippet_context_init (IdeSourceSnippetContext *context)
+{
+  GDateTime *dt;
+  gchar *str;
+
+  context->variables = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+  context->shared = g_hash_table_new_full (g_str_hash,
+                                                 g_str_equal,
+                                                 g_free,
+                                                 g_free);
+
+#define ADD_VARIABLE(k, v) \
+  g_hash_table_insert (context->shared, g_strdup (k), g_strdup (v))
+
+  ADD_VARIABLE ("username", g_get_user_name ());
+  ADD_VARIABLE ("fullname", g_get_real_name ());
+  ADD_VARIABLE ("author", g_get_real_name ());
+
+  dt = g_date_time_new_now_local ();
+  str = g_date_time_format (dt, "%Y");
+  ADD_VARIABLE ("year", str);
+  g_free (str);
+  str = g_date_time_format (dt, "%b");
+  ADD_VARIABLE ("shortmonth", str);
+  g_free (str);
+  str = g_date_time_format (dt, "%d");
+  ADD_VARIABLE ("day", str);
+  g_free (str);
+  str = g_date_time_format (dt, "%a");
+  ADD_VARIABLE ("shortweekday", str);
+  g_free (str);
+  g_date_time_unref (dt);
+
+  str = run_command ("git config user.email");
+  ADD_VARIABLE ("email", str);
+  g_free (str);
+
+#undef ADD_VARIABLE
+}
diff --git a/libide/ide-source-snippet-context.h b/libide/ide-source-snippet-context.h
new file mode 100644
index 0000000..fe9752f
--- /dev/null
+++ b/libide/ide-source-snippet-context.h
@@ -0,0 +1,51 @@
+/* ide-source-snippet-context.h
+ *
+ * Copyright (C) 2013 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 IDE_SOURCE_SNIPPET_CONTEXT_H
+#define IDE_SOURCE_SNIPPET_CONTEXT_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_SOURCE_SNIPPET_CONTEXT (ide_source_snippet_context_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeSourceSnippetContext, ide_source_snippet_context,
+                      IDE, SOURCE_SNIPPET_CONTEXT, GObject)
+
+IdeSourceSnippetContext *ide_source_snippet_context_new             (void);
+void                     ide_source_snippet_context_emit_changed    (IdeSourceSnippetContext *context);
+void                     ide_source_snippet_context_clear_variables (IdeSourceSnippetContext *context);
+void                     ide_source_snippet_context_add_variable    (IdeSourceSnippetContext *context,
+                                                                     const gchar             *key,
+                                                                     const gchar             *value);
+const gchar             *ide_source_snippet_context_get_variable    (IdeSourceSnippetContext *context,
+                                                                     const gchar             *key);
+gchar                   *ide_source_snippet_context_expand          (IdeSourceSnippetContext *context,
+                                                                     const gchar             *input);
+void                     ide_source_snippet_context_set_tab_width   (IdeSourceSnippetContext *context,
+                                                                     gint                     tab_size);
+void                     ide_source_snippet_context_set_use_spaces  (IdeSourceSnippetContext *context,
+                                                                     gboolean                 use_spaces);
+void                     ide_source_snippet_context_set_line_prefix (IdeSourceSnippetContext *context,
+                                                                     const gchar             *line_prefix);
+void                     ide_source_snippet_context_dump            (IdeSourceSnippetContext *context);
+
+G_END_DECLS
+
+#endif /* IDE_SOURCE_SNIPPET_CONTEXT_H */
diff --git a/libide/ide-source-snippet-parser.c b/libide/ide-source-snippet-parser.c
new file mode 100644
index 0000000..c0c849f
--- /dev/null
+++ b/libide/ide-source-snippet-parser.c
@@ -0,0 +1,617 @@
+/* ide-source-snippet-parser.c
+ *
+ * Copyright (C) 2013 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 <errno.h>
+#include <glib/gi18n.h>
+#include <stdlib.h>
+
+#include "ide-source-snippet.h"
+#include "ide-source-snippet-chunk.h"
+#include "ide-source-snippet-parser.h"
+#include "ide-source-snippet-private.h"
+
+G_DEFINE_TYPE (IdeSourceSnippetParser, ide_source_snippet_parser, G_TYPE_OBJECT)
+
+struct _IdeSourceSnippetParserPrivate
+{
+  GList   *snippets;
+
+  gint     lineno;
+  GList   *chunks;
+  GList   *scope;
+  gchar   *cur_name;
+  gchar   *cur_desc;
+  GString *cur_text;
+};
+
+IdeSourceSnippetParser *
+ide_source_snippet_parser_new (void)
+{
+  return g_object_new (IDE_TYPE_SOURCE_SNIPPET_PARSER, NULL);
+}
+
+static void
+ide_source_snippet_parser_flush_chunk (IdeSourceSnippetParser *parser)
+{
+  IdeSourceSnippetParserPrivate *priv = parser->priv;
+  IdeSourceSnippetChunk *chunk;
+
+  if (priv->cur_text->len)
+    {
+      chunk = ide_source_snippet_chunk_new ();
+      ide_source_snippet_chunk_set_spec (chunk, priv->cur_text->str);
+      priv->chunks = g_list_append (priv->chunks, chunk);
+      g_string_truncate (priv->cur_text, 0);
+    }
+}
+
+static void
+ide_source_snippet_parser_store (IdeSourceSnippetParser *parser)
+{
+  IdeSourceSnippetParserPrivate *priv = parser->priv;
+  IdeSourceSnippet *snippet;
+  GList *scope_iter;
+  GList *chunck_iter;
+
+  ide_source_snippet_parser_flush_chunk (parser);
+  for (scope_iter = priv->scope; scope_iter; scope_iter = scope_iter->next)
+    {
+      snippet = ide_source_snippet_new (priv->cur_name, g_strdup(scope_iter->data));
+      ide_source_snippet_set_description(snippet, priv->cur_desc);
+
+      for (chunck_iter = priv->chunks; chunck_iter; chunck_iter = chunck_iter->next)
+        {
+        #if 0
+          g_printerr ("%s:  Tab: %02d  Link: %02d  Text: %s\n",
+                      parser->priv->cur_name,
+                      ide_source_snippet_chunk_get_tab_stop (chunck_iter->data),
+                      ide_source_snippet_chunk_get_linked_chunk (chunck_iter->data),
+                      ide_source_snippet_chunk_get_text (chunck_iter->data));
+        #endif
+          ide_source_snippet_add_chunk (snippet, chunck_iter->data);
+        }
+
+      priv->snippets = g_list_append (priv->snippets, snippet);
+    }
+}
+
+static void
+ide_source_snippet_parser_finish (IdeSourceSnippetParser *parser)
+{
+  IdeSourceSnippetParserPrivate *priv = parser->priv;
+
+  if (priv->cur_name)
+    {
+      ide_source_snippet_parser_store(parser);
+    }
+
+  g_clear_pointer (&priv->cur_name, g_free);
+
+  g_string_truncate (priv->cur_text, 0);
+
+  g_list_foreach (priv->chunks, (GFunc) g_object_unref, NULL);
+  g_list_free (priv->chunks);
+  priv->chunks = NULL;
+
+  g_list_free_full(priv->scope, g_free);
+  priv->scope = NULL;
+
+  g_free(priv->cur_desc);
+  priv->cur_desc = NULL;
+}
+
+static void
+ide_source_snippet_parser_do_part_simple (IdeSourceSnippetParser *parser,
+                                         const gchar           *line)
+{
+  g_string_append (parser->priv->cur_text, line);
+}
+
+static void
+ide_source_snippet_parser_do_part_n (IdeSourceSnippetParser *parser,
+                                    gint                   n,
+                                    const gchar           *inner)
+{
+  IdeSourceSnippetParserPrivate *priv = parser->priv;
+  IdeSourceSnippetChunk *chunk;
+
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET_PARSER (parser));
+  g_return_if_fail (n >= -1);
+  g_return_if_fail (inner);
+
+  chunk = ide_source_snippet_chunk_new ();
+  ide_source_snippet_chunk_set_spec (chunk, n ? inner : "");
+  ide_source_snippet_chunk_set_tab_stop (chunk, n);
+  priv->chunks = g_list_append (priv->chunks, chunk);
+}
+
+static void
+ide_source_snippet_parser_do_part_linked (IdeSourceSnippetParser *parser,
+                                         gint                   n)
+{
+  IdeSourceSnippetParserPrivate *priv = parser->priv;
+  IdeSourceSnippetChunk *chunk;
+  gchar text[12];
+
+  chunk = ide_source_snippet_chunk_new ();
+  if (n)
+    {
+      g_snprintf (text, sizeof text, "$%d", n);
+      text[sizeof text - 1] = '\0';
+      ide_source_snippet_chunk_set_spec (chunk, text);
+    }
+  else
+    {
+      ide_source_snippet_chunk_set_spec (chunk, "");
+      ide_source_snippet_chunk_set_tab_stop (chunk, 0);
+    }
+  priv->chunks = g_list_append (priv->chunks, chunk);
+}
+
+static void
+ide_source_snippet_parser_do_part_named (IdeSourceSnippetParser *parser,
+                                        const gchar           *name)
+{
+  IdeSourceSnippetParserPrivate *priv = parser->priv;
+  IdeSourceSnippetChunk *chunk;
+  gchar *spec;
+
+  chunk = ide_source_snippet_chunk_new ();
+  spec = g_strdup_printf ("$%s", name);
+  ide_source_snippet_chunk_set_spec (chunk, spec);
+  ide_source_snippet_chunk_set_tab_stop (chunk, -1);
+  priv->chunks = g_list_append (priv->chunks, chunk);
+  g_free (spec);
+}
+
+static gboolean
+parse_variable (const gchar  *line,
+                glong        *n,
+                gchar       **inner,
+                const gchar **endptr,
+                gchar       **name)
+{
+  gboolean has_inner = FALSE;
+  char *end = NULL;
+  gint brackets;
+  gint i;
+
+  *n = -1;
+  *inner = NULL;
+  *endptr = NULL;
+  *name = NULL;
+
+  g_assert (*line == '$');
+
+  line++;
+
+  *endptr = line;
+
+  if (!*line)
+    {
+      *endptr = NULL;
+      return FALSE;
+    }
+
+  if (*line == '{')
+    {
+      has_inner = TRUE;
+      line++;
+    }
+
+  if (g_ascii_isdigit (*line))
+    {
+      errno = 0;
+      *n = strtol (line, &end, 10);
+      if (((*n == LONG_MIN) || (*n == LONG_MAX)) && errno == ERANGE)
+        return FALSE;
+      else if (*n < 0)
+        return FALSE;
+      line = end;
+    }
+  else if (g_ascii_isalpha (*line))
+    {
+      const gchar *cur;
+
+      for (cur = line; *cur; cur++)
+        {
+          if (g_ascii_isalnum (*cur))
+            continue;
+          break;
+        }
+      *endptr = cur;
+      *name = g_strndup (line, cur - line);
+      *n = -2;
+      return TRUE;
+    }
+
+  if (has_inner)
+    {
+      if (*line == ':')
+        line++;
+
+      brackets = 1;
+
+      for (i = 0; line[i]; i++)
+        {
+          switch (line[i])
+            {
+            case '{':
+              brackets++;
+              break;
+
+            case '}':
+              brackets--;
+              break;
+
+            default:
+              break;
+            }
+
+          if (!brackets)
+            {
+              *inner = g_strndup (line, i);
+              *endptr = &line[i + 1];
+              return TRUE;
+            }
+        }
+
+      return FALSE;
+    }
+
+  *endptr = line;
+
+  return TRUE;
+}
+
+static void
+ide_source_snippet_parser_do_part (IdeSourceSnippetParser *parser,
+                                  const gchar           *line)
+{
+  const gchar *dollar;
+  gchar *str;
+  gchar *inner;
+  gchar *name;
+  glong n;
+
+  g_assert (line);
+  g_assert (*line == '\t');
+
+  line++;
+
+again:
+  if (!*line)
+    return;
+
+  if (!(dollar = strchr (line, '$')))
+    {
+      ide_source_snippet_parser_do_part_simple (parser, line);
+      return;
+    }
+
+  /*
+   * Parse up to the next $ as a simple.
+   * If it is $N or ${N} then it is a linked chunk w/o tabstop.
+   * If it is ${N:""} then it is a chunk w/ tabstop.
+   * If it is ${blah|upper} then it is a non-tab stop chunk performing
+   * some sort of of expansion.
+   */
+
+  g_assert (dollar >= line);
+
+  if (dollar != line)
+    {
+      str = g_strndup (line, (dollar - line));
+      ide_source_snippet_parser_do_part_simple (parser, str);
+      g_free (str);
+      line = dollar;
+    }
+
+parse_dollar:
+  inner = NULL;
+
+  if (!parse_variable (line, &n, &inner, &line, &name))
+    {
+      ide_source_snippet_parser_do_part_simple (parser, line);
+      return;
+    }
+
+#if 0
+  g_printerr ("Parse Variable: N=%d  inner=\"%s\"\n", n, inner);
+  g_printerr ("  Left over: \"%s\"\n", line);
+#endif
+
+  ide_source_snippet_parser_flush_chunk (parser);
+
+  if (inner)
+    {
+      ide_source_snippet_parser_do_part_n (parser, n, inner);
+      g_free (inner);
+      inner = NULL;
+    }
+  else if (n == -2 && name)
+    ide_source_snippet_parser_do_part_named (parser, name);
+  else
+    ide_source_snippet_parser_do_part_linked (parser, n);
+
+  g_free (name);
+
+  if (line)
+    {
+      if (*line == '$')
+        {
+          dollar = line;
+          goto parse_dollar;
+        }
+      else
+        goto again;
+    }
+}
+
+static void
+ide_source_snippet_parser_do_snippet (IdeSourceSnippetParser *parser,
+                                     const gchar           *line)
+{
+  parser->priv->cur_name = g_strstrip (g_strdup (&line[8]));
+}
+
+static void
+ide_source_snippet_parser_do_snippet_scope (IdeSourceSnippetParser *parser,
+                                           const gchar           *line)
+{
+  IdeSourceSnippetParserPrivate *priv = parser->priv;
+  gchar **scope_list;
+  GList *iter;
+  gint i;
+  gboolean add_scope;
+
+  scope_list = g_strsplit (&line[8], ",", -1);
+
+  for (i = 0; scope_list[i]; i++)
+    {
+      add_scope = TRUE;
+      for (iter = priv->scope; iter; iter = iter->next)
+        {
+          if (g_strcmp0(iter->data, scope_list[i]) == 0)
+              add_scope = FALSE;
+              break;
+        }
+
+      if (add_scope)
+        priv->scope = g_list_append(priv->scope,
+                                    g_strstrip (g_strdup (scope_list[i])));
+    }
+
+  g_strfreev(scope_list);
+}
+
+static void
+ide_source_snippet_parser_do_snippet_description (IdeSourceSnippetParser *parser,
+                                                 const gchar           *line)
+{
+  IdeSourceSnippetParserPrivate *priv = parser->priv;
+
+  if (priv->cur_desc)
+    {
+      g_free(priv->cur_desc);
+      priv->cur_desc = NULL;
+    }
+
+  priv->cur_desc = g_strstrip (g_strdup (&line[7]));
+}
+
+static void
+ide_source_snippet_parser_feed_line (IdeSourceSnippetParser *parser,
+                                    gchar                 *basename,
+                                    const gchar           *line)
+{
+  IdeSourceSnippetParserPrivate *priv = parser->priv;
+
+  g_assert (parser);
+  g_assert (basename);
+  g_assert (line);
+
+  priv->lineno++;
+
+  switch (*line)
+    {
+    case '\0':
+      if (priv->cur_name)
+        g_string_append_c (priv->cur_text, '\n');
+      break;
+
+    case '#':
+      break;
+
+    case '\t':
+      if (priv->cur_name)
+        {
+          GList *iter;
+          gboolean add_default_scope = TRUE;
+          for (iter = priv->scope; iter; iter = iter->next)
+            {
+              if (g_strcmp0(iter->data, basename) == 0)
+                {
+                  add_default_scope = FALSE;
+                  break;
+                }
+            }
+
+          if (add_default_scope)
+            priv->scope = g_list_append(priv->scope,
+                                        g_strstrip (g_strdup (basename)));
+
+          if (priv->cur_text->len || priv->chunks)
+            g_string_append_c (priv->cur_text, '\n');
+          ide_source_snippet_parser_do_part (parser, line);
+        }
+      break;
+
+    case 's':
+      if (g_str_has_prefix (line, "snippet"))
+        {
+          ide_source_snippet_parser_finish (parser);
+          ide_source_snippet_parser_do_snippet (parser, line);
+          break;
+        }
+
+    case '-':
+      if (priv->cur_text->len || priv->chunks)
+        {
+          ide_source_snippet_parser_store(parser);
+
+          g_string_truncate (priv->cur_text, 0);
+
+          g_list_foreach (priv->chunks, (GFunc) g_object_unref, NULL);
+          g_list_free (priv->chunks);
+          priv->chunks = NULL;
+
+          g_list_free_full(priv->scope, g_free);
+          priv->scope = NULL;
+        }
+
+      if (g_str_has_prefix(line, "- scope"))
+        {
+          ide_source_snippet_parser_do_snippet_scope (parser, line);
+          break;
+        }
+
+      if (g_str_has_prefix(line, "- desc"))
+        {
+          ide_source_snippet_parser_do_snippet_description (parser, line);
+          break;
+        }
+
+    /* Fall through */
+    default:
+      g_warning (_("Invalid snippet at line %d: %s"), priv->lineno, line);
+      break;
+    }
+}
+
+gboolean
+ide_source_snippet_parser_load_from_file (IdeSourceSnippetParser *parser,
+                                         GFile                 *file,
+                                         GError               **error)
+{
+  GFileInputStream *file_stream;
+  GDataInputStream *data_stream;
+  GError *local_error = NULL;
+  gchar *line;
+  gchar *basename = NULL;
+
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET_PARSER (parser), FALSE);
+  g_return_val_if_fail (G_IS_FILE (file), FALSE);
+
+  basename = g_file_get_basename (file);
+
+  if (basename)
+    {
+      if (strstr (basename, "."))
+        *strstr (basename, ".") = '\0';
+    }
+
+  file_stream = g_file_read (file, NULL, error);
+  if (!file_stream)
+    return FALSE;
+
+  data_stream = g_data_input_stream_new (G_INPUT_STREAM (file_stream));
+  g_object_unref (file_stream);
+
+again:
+  line = g_data_input_stream_read_line_utf8 (data_stream, NULL, NULL, &local_error);
+  if (line && local_error)
+    {
+      g_propagate_error (error, local_error);
+      return FALSE;
+    }
+  else if (line)
+    {
+      ide_source_snippet_parser_feed_line (parser, basename, line);
+      g_free (line);
+      goto again;
+    }
+
+  ide_source_snippet_parser_finish (parser);
+  g_free(basename);
+
+  return TRUE;
+}
+
+GList *
+ide_source_snippet_parser_get_snippets (IdeSourceSnippetParser *parser)
+{
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET_PARSER (parser), NULL);
+  return parser->priv->snippets;
+}
+
+static void
+ide_source_snippet_parser_finalize (GObject *object)
+{
+  IdeSourceSnippetParserPrivate *priv;
+
+  priv = IDE_SOURCE_SNIPPET_PARSER (object)->priv;
+
+  g_list_foreach (priv->snippets, (GFunc) g_object_unref, NULL);
+  g_list_free (priv->snippets);
+  priv->snippets = NULL;
+
+  g_list_foreach (priv->chunks, (GFunc) g_object_unref, NULL);
+  g_list_free (priv->chunks);
+  priv->chunks = NULL;
+
+  g_list_free_full(priv->scope, g_free);
+  priv->scope = NULL;
+
+  if (priv->cur_text)
+    g_string_free (priv->cur_text, TRUE);
+
+  g_free (priv->cur_name);
+  priv->cur_name = NULL;
+
+  if (priv->cur_desc)
+    {
+      g_free (priv->cur_desc);
+      priv->cur_desc = NULL;
+    }
+
+  G_OBJECT_CLASS (ide_source_snippet_parser_parent_class)->finalize (object);
+}
+
+static void
+ide_source_snippet_parser_class_init (IdeSourceSnippetParserClass *klass)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (klass);
+  object_class->finalize = ide_source_snippet_parser_finalize;
+  g_type_class_add_private (object_class,
+                            sizeof (IdeSourceSnippetParserPrivate));
+}
+
+static void
+ide_source_snippet_parser_init (IdeSourceSnippetParser *parser)
+{
+  parser->priv = G_TYPE_INSTANCE_GET_PRIVATE (parser,
+                                              IDE_TYPE_SOURCE_SNIPPET_PARSER,
+                                              IdeSourceSnippetParserPrivate);
+  parser->priv->lineno = -1;
+  parser->priv->cur_text = g_string_new (NULL);
+  parser->priv->scope = NULL;
+  parser->priv->cur_desc = NULL;
+}
diff --git a/libide/ide-source-snippet-parser.h b/libide/ide-source-snippet-parser.h
new file mode 100644
index 0000000..2e51287
--- /dev/null
+++ b/libide/ide-source-snippet-parser.h
@@ -0,0 +1,60 @@
+/* ide-source-snippet-parser.h
+ *
+ * Copyright (C) 2013 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 IDE_SOURCE_SNIPPET_PARSER_H
+#define IDE_SOURCE_SNIPPET_PARSER_H
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_SOURCE_SNIPPET_PARSER            (ide_source_snippet_parser_get_type())
+#define IDE_SOURCE_SNIPPET_PARSER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
IDE_TYPE_SOURCE_SNIPPET_PARSER, IdeSourceSnippetParser))
+#define IDE_SOURCE_SNIPPET_PARSER_CONST(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
IDE_TYPE_SOURCE_SNIPPET_PARSER, IdeSourceSnippetParser const))
+#define IDE_SOURCE_SNIPPET_PARSER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  
IDE_TYPE_SOURCE_SNIPPET_PARSER, IdeSourceSnippetParserClass))
+#define IDE_IS_SOURCE_SNIPPET_PARSER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
IDE_TYPE_SOURCE_SNIPPET_PARSER))
+#define IDE_IS_SOURCE_SNIPPET_PARSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  
IDE_TYPE_SOURCE_SNIPPET_PARSER))
+#define IDE_SOURCE_SNIPPET_PARSER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  
IDE_TYPE_SOURCE_SNIPPET_PARSER, IdeSourceSnippetParserClass))
+
+typedef struct _IdeSourceSnippetParser        IdeSourceSnippetParser;
+typedef struct _IdeSourceSnippetParserClass   IdeSourceSnippetParserClass;
+typedef struct _IdeSourceSnippetParserPrivate IdeSourceSnippetParserPrivate;
+
+struct _IdeSourceSnippetParser
+{
+  GObject parent;
+
+  /*< private >*/
+  IdeSourceSnippetParserPrivate *priv;
+};
+
+struct _IdeSourceSnippetParserClass
+{
+  GObjectClass parent_class;
+};
+
+GType                  ide_source_snippet_parser_get_type       (void);
+IdeSourceSnippetParser *ide_source_snippet_parser_new            (void);
+gboolean               ide_source_snippet_parser_load_from_file (IdeSourceSnippetParser  *parser,
+                                                                GFile                  *file,
+                                                                GError                **error);
+GList                 *ide_source_snippet_parser_get_snippets   (IdeSourceSnippetParser  *parser);
+
+G_END_DECLS
+
+#endif /* IDE_SOURCE_SNIPPET_PARSER_H */
diff --git a/libide/ide-source-snippet-private.h b/libide/ide-source-snippet-private.h
new file mode 100644
index 0000000..188b76a
--- /dev/null
+++ b/libide/ide-source-snippet-private.h
@@ -0,0 +1,57 @@
+/* ide-source-snippet-private.h
+ *
+ * Copyright (C) 2013 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 IDE_SOURCE_SNIPPET_PRIVATE_H
+#define IDE_SOURCE_SNIPPET_PRIVATE_H
+
+#include "ide-source-snippet.h"
+
+G_BEGIN_DECLS
+
+gboolean         ide_source_snippet_begin              (IdeSourceSnippet  *snippet,
+                                                       GtkTextBuffer    *buffer,
+                                                       GtkTextIter      *iter)    G_GNUC_INTERNAL;
+void             ide_source_snippet_pause              (IdeSourceSnippet  *snippet) G_GNUC_INTERNAL;
+void             ide_source_snippet_unpause            (IdeSourceSnippet  *snippet) G_GNUC_INTERNAL;
+void             ide_source_snippet_finish             (IdeSourceSnippet  *snippet) G_GNUC_INTERNAL;
+gboolean         ide_source_snippet_move_next          (IdeSourceSnippet  *snippet) G_GNUC_INTERNAL;
+gboolean         ide_source_snippet_move_previous      (IdeSourceSnippet  *snippet) G_GNUC_INTERNAL;
+void             ide_source_snippet_before_insert_text (IdeSourceSnippet  *snippet,
+                                                       GtkTextBuffer    *buffer,
+                                                       GtkTextIter      *iter,
+                                                       gchar            *text,
+                                                       gint              len)     G_GNUC_INTERNAL;
+void             ide_source_snippet_after_insert_text  (IdeSourceSnippet  *snippet,
+                                                       GtkTextBuffer    *buffer,
+                                                       GtkTextIter      *iter,
+                                                       gchar            *text,
+                                                       gint              len)     G_GNUC_INTERNAL;
+void             ide_source_snippet_before_delete_range (IdeSourceSnippet *snippet,
+                                                       GtkTextBuffer    *buffer,
+                                                       GtkTextIter      *begin,
+                                                       GtkTextIter      *end)     G_GNUC_INTERNAL;
+void             ide_source_snippet_after_delete_range (IdeSourceSnippet  *snippet,
+                                                       GtkTextBuffer    *buffer,
+                                                       GtkTextIter      *begin,
+                                                       GtkTextIter      *end)     G_GNUC_INTERNAL;
+gboolean         ide_source_snippet_insert_set         (IdeSourceSnippet  *snippet,
+                                                       GtkTextMark      *mark)    G_GNUC_INTERNAL;
+
+G_END_DECLS
+
+#endif /* IDE_SOURCE_SNIPPET_PRIVATE_H */
diff --git a/libide/ide-source-snippet.c b/libide/ide-source-snippet.c
new file mode 100644
index 0000000..6a2a106
--- /dev/null
+++ b/libide/ide-source-snippet.c
@@ -0,0 +1,1009 @@
+/* ide-source-snippet.c
+ *
+ * Copyright (C) 2013 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/>.
+ */
+
+#define G_LOG_DOMAIN "ide-source-snippet"
+
+#include <glib/gi18n.h>
+
+#include "ide-source-snippet.h"
+#include "ide-source-snippet-private.h"
+#include "ide-source-snippet-chunk.h"
+#include "ide-source-snippet-context.h"
+
+struct _IdeSourceSnippet
+{
+  IdeObject                parent_instance;
+
+  IdeSourceSnippetContext *snippet_context;
+  GtkTextBuffer           *buffer;
+  GPtrArray               *chunks;
+  GArray                  *runs;
+  GtkTextMark             *mark_begin;
+  GtkTextMark             *mark_end;
+  gchar                   *trigger;
+  gchar                   *language;
+  gchar                   *description;
+
+  gint                     tab_stop;
+  gint                     max_tab_stop;
+  gint                     current_chunk;
+
+  guint                    inserted : 1;
+};
+
+enum {
+  PROP_0,
+  PROP_BUFFER,
+  PROP_MARK_BEGIN,
+  PROP_MARK_END,
+  PROP_TAB_STOP,
+  PROP_TRIGGER,
+  PROP_LANGUAGE,
+  PROP_DESCRIPTION,
+  LAST_PROP
+};
+
+G_DEFINE_TYPE (IdeSourceSnippet, ide_source_snippet, G_TYPE_OBJECT)
+
+static GParamSpec * gParamSpecs[LAST_PROP];
+
+IdeSourceSnippet *
+ide_source_snippet_new (const gchar *trigger,
+                        const gchar *language)
+{
+  IdeSourceSnippet *ret;
+
+  ret = g_object_new (IDE_TYPE_SOURCE_SNIPPET,
+                      "trigger", trigger,
+                      "language", language,
+                      NULL);
+
+  return ret;
+}
+
+/**
+ * ide_source_snippet_copy:
+ *
+ * Returns: (transfer full): An #IdeSourceSnippet.
+ */
+IdeSourceSnippet *
+ide_source_snippet_copy (IdeSourceSnippet *self)
+{
+  IdeSourceSnippetChunk *chunk;
+  IdeSourceSnippet *ret;
+  gint i;
+
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET (self), NULL);
+
+  ret = g_object_new (IDE_TYPE_SOURCE_SNIPPET,
+                      "trigger", self->trigger,
+                      "language", self->language,
+                      "description", self->description,
+                      NULL);
+
+  for (i = 0; i < self->chunks->len; i++)
+    {
+      chunk = g_ptr_array_index (self->chunks, i);
+      chunk = ide_source_snippet_chunk_copy (chunk);
+      ide_source_snippet_add_chunk (ret, chunk);
+      g_object_unref (chunk);
+    }
+
+  return ret;
+}
+
+gint
+ide_source_snippet_get_tab_stop (IdeSourceSnippet *self)
+{
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET (self), -1);
+
+  return self->tab_stop;
+}
+
+guint
+ide_source_snippet_get_n_chunks (IdeSourceSnippet *self)
+{
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET (self), 0);
+
+  return self->chunks->len;
+}
+
+/**
+ * ide_source_snippet_get_nth_chunk:
+ *
+ * Returns: (transfer none):
+ */
+IdeSourceSnippetChunk *
+ide_source_snippet_get_nth_chunk (IdeSourceSnippet *self,
+                                  guint             n)
+{
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET (self), 0);
+
+  if (n < self->chunks->len)
+    return g_ptr_array_index (self->chunks, n);
+
+  return NULL;
+}
+
+const gchar *
+ide_source_snippet_get_trigger (IdeSourceSnippet *self)
+{
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET (self), NULL);
+
+  return self->trigger;
+}
+
+void
+ide_source_snippet_set_trigger (IdeSourceSnippet *self,
+                                const gchar      *trigger)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET (self));
+
+  g_free (self->trigger);
+  self->trigger = g_strdup (trigger);
+}
+
+const gchar *
+ide_source_snippet_get_language (IdeSourceSnippet *self)
+{
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET (self), NULL);
+
+  return self->language;
+}
+
+void
+ide_source_snippet_set_language (IdeSourceSnippet *self,
+                                 const gchar      *language)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET (self));
+
+  g_free (self->language);
+  self->language = g_strdup (language);
+}
+
+const gchar *
+ide_source_snippet_get_description (IdeSourceSnippet *self)
+{
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET (self), NULL);
+
+  return self->description;
+}
+
+void
+ide_source_snippet_set_description (IdeSourceSnippet *self,
+                                    const gchar      *description)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET (self));
+
+  g_free (self->description);
+  self->description = g_strdup (description);
+}
+
+static gint
+ide_source_snippet_get_offset (IdeSourceSnippet *self,
+                               GtkTextIter      *iter)
+{
+  GtkTextIter begin;
+  gint ret;
+
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET (self), 0);
+  g_return_val_if_fail (iter, 0);
+
+  gtk_text_buffer_get_iter_at_mark (self->buffer, &begin, self->mark_begin);
+  ret = gtk_text_iter_get_offset (iter) - gtk_text_iter_get_offset (&begin);
+  ret = MAX (0, ret);
+
+  return ret;
+}
+
+static gint
+ide_source_snippet_get_index (IdeSourceSnippet *self,
+                              GtkTextIter      *iter)
+{
+  gint offset;
+  gint run;
+  gint i;
+
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET (self), 0);
+  g_return_val_if_fail (iter, 0);
+
+  offset = ide_source_snippet_get_offset (self, iter);
+
+  for (i = 0; i < self->runs->len; i++)
+    {
+      run = g_array_index (self->runs, gint, i);
+      offset -= run;
+      if (offset <= 0)
+        {
+          /*
+           * HACK: This is the central part of the hack by using offsets
+           *       instead of textmarks (which gives us lots of gravity grief).
+           *       We guess which snippet it is based on the current chunk.
+           */
+          if ((i + 1) == self->current_chunk)
+            return (i + 1);
+          return i;
+        }
+    }
+
+  return (self->runs->len - 1);
+}
+
+static gboolean
+ide_source_snippet_within_bounds (IdeSourceSnippet *self,
+                                  GtkTextIter      *iter)
+{
+  GtkTextIter begin;
+  GtkTextIter end;
+  gboolean ret;
+
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET (self), FALSE);
+  g_return_val_if_fail (iter, FALSE);
+
+  gtk_text_buffer_get_iter_at_mark (self->buffer, &begin, self->mark_begin);
+  gtk_text_buffer_get_iter_at_mark (self->buffer, &end, self->mark_end);
+
+  ret = ((gtk_text_iter_compare (&begin, iter) <= 0) &&
+         (gtk_text_iter_compare (&end, iter) >= 0));
+
+  return ret;
+}
+
+gboolean
+ide_source_snippet_insert_set (IdeSourceSnippet *self,
+                               GtkTextMark      *mark)
+{
+  GtkTextIter iter;
+
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET (self), FALSE);
+  g_return_val_if_fail (GTK_IS_TEXT_MARK (mark), FALSE);
+
+  gtk_text_buffer_get_iter_at_mark (self->buffer, &iter, mark);
+
+  if (!ide_source_snippet_within_bounds (self, &iter))
+    return FALSE;
+
+  self->current_chunk = ide_source_snippet_get_index (self, &iter);
+
+  return TRUE;
+}
+
+static void
+ide_source_snippet_get_nth_chunk_range (IdeSourceSnippet *self,
+                                        gint              n,
+                                        GtkTextIter      *begin,
+                                        GtkTextIter      *end)
+{
+  gint run;
+  gint i;
+
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET (self));
+  g_return_if_fail (n >= 0);
+  g_return_if_fail (begin);
+  g_return_if_fail (end);
+
+  gtk_text_buffer_get_iter_at_mark (self->buffer, begin, self->mark_begin);
+
+  for (i = 0; i < n; i++)
+    {
+      run = g_array_index (self->runs, gint, i);
+      gtk_text_iter_forward_chars (begin, run);
+    }
+
+  gtk_text_iter_assign (end, begin);
+  run = g_array_index (self->runs, gint, n);
+  gtk_text_iter_forward_chars (end, run);
+}
+
+void
+ide_source_snippet_get_chunk_range (IdeSourceSnippet      *self,
+                                    IdeSourceSnippetChunk *chunk,
+                                    GtkTextIter           *begin,
+                                    GtkTextIter           *end)
+{
+  IdeSourceSnippetChunk *item;
+  guint i;
+
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET (self));
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET_CHUNK (chunk));
+
+  for (i = 0; i < self->chunks->len; i++)
+    {
+      item = g_ptr_array_index (self->chunks, i);
+
+      if (item == chunk)
+        {
+          ide_source_snippet_get_nth_chunk_range (self, i, begin, end);
+          return;
+        }
+    }
+
+  g_warning (_("Chunk does not belong to snippet."));
+}
+
+static void
+ide_source_snippet_select_chunk (IdeSourceSnippet *self,
+                                 gint              n)
+{
+  GtkTextIter begin;
+  GtkTextIter end;
+
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET (self));
+  g_return_if_fail (n >= 0);
+  g_return_if_fail (n < self->runs->len);
+
+  ide_source_snippet_get_nth_chunk_range (self, n, &begin, &end);
+  gtk_text_buffer_select_range (self->buffer, &begin, &end);
+  self->current_chunk = n;
+}
+
+gboolean
+ide_source_snippet_move_next (IdeSourceSnippet *self)
+{
+  IdeSourceSnippetChunk *chunk = NULL;
+  GtkTextIter iter;
+  gint i;
+
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET (self), FALSE);
+
+  if (self->tab_stop > self->max_tab_stop)
+    return FALSE;
+
+  self->tab_stop++;
+
+  for (i = 0; i < self->chunks->len; i++)
+    {
+      chunk = g_ptr_array_index (self->chunks, i);
+      if (ide_source_snippet_chunk_get_tab_stop (chunk) == self->tab_stop)
+        {
+          ide_source_snippet_select_chunk (self, i);
+          return TRUE;
+        }
+    }
+
+  for (i = 0; i < self->chunks->len; i++)
+    {
+      chunk = g_ptr_array_index (self->chunks, i);
+      if (!ide_source_snippet_chunk_get_tab_stop (chunk))
+        {
+          ide_source_snippet_select_chunk (self, i);
+          return FALSE;
+        }
+    }
+
+  gtk_text_buffer_get_iter_at_mark (self->buffer, &iter, self->mark_end);
+  gtk_text_buffer_select_range (self->buffer, &iter, &iter);
+  self->current_chunk = self->chunks->len - 1;
+
+  return FALSE;
+}
+
+gboolean
+ide_source_snippet_move_previous (IdeSourceSnippet *self)
+{
+  IdeSourceSnippetChunk *chunk = NULL;
+  gint i;
+
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET (self), FALSE);
+
+  self->tab_stop = MAX (1, self->tab_stop - 1);
+
+  for (i = 0; i < self->chunks->len; i++)
+    {
+      chunk = g_ptr_array_index (self->chunks, i);
+      if (ide_source_snippet_chunk_get_tab_stop (chunk) == self->tab_stop)
+        {
+          ide_source_snippet_select_chunk (self, i);
+          return TRUE;
+        }
+    }
+
+  return FALSE;
+}
+
+static void
+ide_source_snippet_update_context (IdeSourceSnippet *self)
+{
+  IdeSourceSnippetContext *context;
+  IdeSourceSnippetChunk *chunk;
+  const gchar *text;
+  gchar key[12];
+  guint i;
+  gint tab_stop;
+
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET (self));
+
+  context = ide_source_snippet_get_context (self);
+
+  ide_source_snippet_context_emit_changed (context);
+
+  for (i = 0; i < self->chunks->len; i++)
+    {
+      chunk = g_ptr_array_index (self->chunks, i);
+      tab_stop = ide_source_snippet_chunk_get_tab_stop (chunk);
+      if (tab_stop > 0)
+        {
+          if ((text = ide_source_snippet_chunk_get_text (chunk)))
+            {
+              g_snprintf (key, sizeof key, "%d", tab_stop);
+              key[sizeof key - 1] = '\0';
+              ide_source_snippet_context_add_variable (context, key, text);
+            }
+        }
+    }
+
+  ide_source_snippet_context_emit_changed (context);
+}
+
+gboolean
+ide_source_snippet_begin (IdeSourceSnippet *self,
+                          GtkTextBuffer    *buffer,
+                          GtkTextIter      *iter)
+{
+  IdeSourceSnippetContext *context;
+  IdeSourceSnippetChunk *chunk;
+  const gchar *text;
+  gboolean ret;
+  gint len;
+  gint i;
+
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET (self), FALSE);
+  g_return_val_if_fail (!self->buffer, FALSE);
+  g_return_val_if_fail (!self->mark_begin, FALSE);
+  g_return_val_if_fail (!self->mark_end, FALSE);
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE);
+  g_return_val_if_fail (iter, FALSE);
+
+  self->inserted = TRUE;
+
+  context = ide_source_snippet_get_context (self);
+
+  ide_source_snippet_update_context (self);
+  ide_source_snippet_context_emit_changed (context);
+  ide_source_snippet_update_context (self);
+
+  self->buffer = g_object_ref (buffer);
+  self->mark_begin = gtk_text_buffer_create_mark (buffer, NULL, iter, TRUE);
+  g_object_add_weak_pointer (G_OBJECT (self->mark_begin),
+                             (gpointer *) &self->mark_begin);
+
+  gtk_text_buffer_begin_user_action (buffer);
+
+  for (i = 0; i < self->chunks->len; i++)
+    {
+      chunk = g_ptr_array_index (self->chunks, i);
+      if ((text = ide_source_snippet_chunk_get_text (chunk)))
+        {
+          len = g_utf8_strlen (text, -1);
+          g_array_append_val (self->runs, len);
+          gtk_text_buffer_insert (buffer, iter, text, -1);
+        }
+    }
+
+  self->mark_end = gtk_text_buffer_create_mark (buffer, NULL, iter, FALSE);
+  g_object_add_weak_pointer (G_OBJECT (self->mark_end),
+                             (gpointer *) &self->mark_end);
+
+  g_object_ref (self->mark_begin);
+  g_object_ref (self->mark_end);
+
+  gtk_text_buffer_end_user_action (buffer);
+
+  ret = ide_source_snippet_move_next (self);
+
+  return ret;
+}
+
+void
+ide_source_snippet_finish (IdeSourceSnippet *self)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET (self));
+}
+
+void
+ide_source_snippet_pause (IdeSourceSnippet *self)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET (self));
+}
+
+void
+ide_source_snippet_unpause (IdeSourceSnippet *self)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET (self));
+}
+
+void
+ide_source_snippet_add_chunk (IdeSourceSnippet      *self,
+                              IdeSourceSnippetChunk *chunk)
+{
+  gint tab_stop;
+
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET (self));
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET_CHUNK (chunk));
+  g_return_if_fail (!self->inserted);
+
+  g_ptr_array_add (self->chunks, g_object_ref (chunk));
+
+  ide_source_snippet_chunk_set_context (chunk, self->snippet_context);
+
+  tab_stop = ide_source_snippet_chunk_get_tab_stop (chunk);
+  self->max_tab_stop = MAX (self->max_tab_stop, tab_stop);
+}
+
+gchar *
+ide_source_snippet_get_nth_text (IdeSourceSnippet *self,
+                                 gint              n)
+{
+  GtkTextIter iter;
+  GtkTextIter end;
+  gchar *ret;
+  gint i;
+
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET (self), NULL);
+  g_return_val_if_fail (n >= 0, NULL);
+
+  gtk_text_buffer_get_iter_at_mark (self->buffer, &iter, self->mark_begin);
+
+  for (i = 0; i < n; i++)
+    gtk_text_iter_forward_chars (&iter, g_array_index (self->runs, gint, i));
+
+  gtk_text_iter_assign (&end, &iter);
+  gtk_text_iter_forward_chars (&end, g_array_index (self->runs, gint, n));
+
+  ret = gtk_text_buffer_get_text (self->buffer, &iter, &end, TRUE);
+
+  return ret;
+}
+
+static void
+ide_source_snippet_replace_chunk_text (IdeSourceSnippet *self,
+                                       gint              n,
+                                       const gchar      *text)
+{
+  GtkTextIter begin;
+  GtkTextIter end;
+
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET (self));
+  g_return_if_fail (n >= 0);
+  g_return_if_fail (text);
+
+  ide_source_snippet_get_nth_chunk_range (self, n, &begin, &end);
+  gtk_text_buffer_delete (self->buffer, &begin, &end);
+  gtk_text_buffer_insert (self->buffer, &end, text, -1);
+  g_array_index (self->runs, gint, n) = g_utf8_strlen (text, -1);
+}
+
+static void
+ide_source_snippet_rewrite_updated_chunks (IdeSourceSnippet *self)
+{
+  IdeSourceSnippetChunk *chunk;
+  const gchar *text;
+  gchar *real_text;
+  gint i;
+
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET (self));
+
+  for (i = 0; i < self->chunks->len; i++)
+    {
+      chunk = g_ptr_array_index (self->chunks, i);
+      text = ide_source_snippet_chunk_get_text (chunk);
+      real_text = ide_source_snippet_get_nth_text (self, i);
+      if (!!g_strcmp0 (text, real_text))
+        ide_source_snippet_replace_chunk_text (self, i, text);
+      g_free (real_text);
+    }
+}
+
+void
+ide_source_snippet_before_insert_text (IdeSourceSnippet *self,
+                                       GtkTextBuffer    *buffer,
+                                       GtkTextIter      *iter,
+                                       gchar            *text,
+                                       gint              len)
+{
+  gint utf8_len;
+  gint n;
+
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET (self));
+  g_return_if_fail (self->current_chunk >= 0);
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+  g_return_if_fail (iter);
+
+  n = ide_source_snippet_get_index (self, iter);
+  utf8_len = g_utf8_strlen (text, len);
+  g_array_index (self->runs, gint, n) += utf8_len;
+
+#if 0
+  g_print ("I: ");
+  for (n = 0; n < self->runs->len; n++)
+    g_print ("%d ", g_array_index (self->runs, gint, n));
+  g_print ("\n");
+#endif
+}
+
+void
+ide_source_snippet_after_insert_text (IdeSourceSnippet *self,
+                                      GtkTextBuffer    *buffer,
+                                      GtkTextIter      *iter,
+                                      gchar            *text,
+                                      gint              len)
+{
+  IdeSourceSnippetChunk *chunk;
+  GtkTextMark *here;
+  gchar *new_text;
+  gint n;
+
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET (self));
+  g_return_if_fail (self->current_chunk >= 0);
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+  g_return_if_fail (iter);
+
+  n = ide_source_snippet_get_index (self, iter);
+  chunk = g_ptr_array_index (self->chunks, n);
+  new_text = ide_source_snippet_get_nth_text (self, n);
+  ide_source_snippet_chunk_set_text (chunk, new_text);
+  ide_source_snippet_chunk_set_text_set (chunk, TRUE);
+  g_free (new_text);
+
+  here = gtk_text_buffer_create_mark (buffer, NULL, iter, TRUE);
+
+  ide_source_snippet_update_context (self);
+  ide_source_snippet_update_context (self);
+  ide_source_snippet_rewrite_updated_chunks (self);
+
+  gtk_text_buffer_get_iter_at_mark (buffer, iter, here);
+  gtk_text_buffer_delete_mark (buffer, here);
+
+#if 0
+  ide_source_snippet_context_dump (self->snippet_context);
+#endif
+}
+
+void
+ide_source_snippet_before_delete_range (IdeSourceSnippet *self,
+                                        GtkTextBuffer    *buffer,
+                                        GtkTextIter      *begin,
+                                        GtkTextIter      *end)
+{
+  IdeSourceSnippetChunk *chunk;
+  gchar *new_text;
+  gint *run;
+  gint len;
+  gint n;
+  gint i;
+  gint lower_bound = -1;
+  gint upper_bound = -1;
+
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET (self));
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+  g_return_if_fail (begin);
+  g_return_if_fail (end);
+
+  len = gtk_text_iter_get_offset (end) - gtk_text_iter_get_offset (begin);
+  n = ide_source_snippet_get_index (self, begin);
+  self->current_chunk = n;
+
+  while (len && n < self->runs->len)
+    {
+      if (lower_bound == -1 || n < lower_bound)
+        lower_bound = n;
+      if (n > upper_bound)
+        upper_bound = n;
+      run = &g_array_index (self->runs, gint, n);
+      if (len > *run)
+        {
+          len -= *run;
+          *run = 0;
+          n++;
+          continue;
+        }
+      *run -= len;
+      len = 0;
+      break;
+    }
+
+  for (i = lower_bound; i <= upper_bound; i++)
+    {
+      chunk = g_ptr_array_index (self->chunks, i);
+      new_text = ide_source_snippet_get_nth_text (self, i);
+      ide_source_snippet_chunk_set_text (chunk, new_text);
+      ide_source_snippet_chunk_set_text_set (chunk, TRUE);
+      g_free (new_text);
+    }
+
+#if 0
+  g_print ("D: ");
+  for (n = 0; n < self->runs->len; n++)
+    g_print ("%d ", g_array_index (self->runs, gint, n));
+  g_print ("\n");
+#endif
+}
+
+void
+ide_source_snippet_after_delete_range (IdeSourceSnippet *self,
+                                       GtkTextBuffer    *buffer,
+                                       GtkTextIter      *begin,
+                                       GtkTextIter      *end)
+{
+  GtkTextMark *here;
+
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET (self));
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+  g_return_if_fail (begin);
+  g_return_if_fail (end);
+
+  here = gtk_text_buffer_create_mark (buffer, NULL, begin, TRUE);
+
+  ide_source_snippet_update_context (self);
+  ide_source_snippet_update_context (self);
+  ide_source_snippet_rewrite_updated_chunks (self);
+
+  gtk_text_buffer_get_iter_at_mark (buffer, begin, here);
+  gtk_text_buffer_get_iter_at_mark (buffer, end, here);
+  gtk_text_buffer_delete_mark (buffer, here);
+
+#if 0
+  ide_source_snippet_context_dump (self->snippet_context);
+#endif
+}
+
+/**
+ * ide_source_snippet_get_mark_begin:
+ *
+ * Returns: (transfer none):
+ */
+GtkTextMark *
+ide_source_snippet_get_mark_begin (IdeSourceSnippet *self)
+{
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET (self), NULL);
+
+  return self->mark_begin;
+}
+
+/**
+ * ide_source_snippet_get_mark_end:
+ *
+ * Returns: (transfer none):
+ */
+GtkTextMark *
+ide_source_snippet_get_mark_end (IdeSourceSnippet *self)
+{
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET (self), NULL);
+
+  return self->mark_end;
+}
+
+/**
+ * ide_source_snippet_get_context:
+ *
+ * Returns: (transfer none):
+ */
+IdeSourceSnippetContext *
+ide_source_snippet_get_context (IdeSourceSnippet *self)
+{
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPET (self), NULL);
+
+  if (!self->snippet_context)
+    {
+      IdeSourceSnippetChunk *chunk;
+      guint i;
+
+      self->snippet_context = ide_source_snippet_context_new ();
+
+      for (i = 0; i < self->chunks->len; i++)
+        {
+          chunk = g_ptr_array_index (self->chunks, i);
+          ide_source_snippet_chunk_set_context (chunk, self->snippet_context);
+        }
+    }
+
+  return self->snippet_context;
+}
+
+static void
+ide_source_snippet_dispose (GObject *object)
+{
+  IdeSourceSnippet *self = (IdeSourceSnippet *)object;
+
+  if (self->mark_begin)
+    {
+      g_object_remove_weak_pointer (G_OBJECT (self->mark_begin),
+                                    (gpointer *) &self->mark_begin);
+      gtk_text_buffer_delete_mark (self->buffer, self->mark_begin);
+      self->mark_begin = NULL;
+    }
+
+  if (self->mark_end)
+    {
+      g_object_remove_weak_pointer (G_OBJECT (self->mark_end),
+                                    (gpointer *) &self->mark_end);
+      gtk_text_buffer_delete_mark (self->buffer, self->mark_end);
+      self->mark_end = NULL;
+    }
+
+  g_clear_pointer (&self->runs, (GDestroyNotify) g_array_unref);
+  g_clear_pointer (&self->chunks, (GDestroyNotify) g_ptr_array_unref);
+
+  g_clear_object (&self->buffer);
+  g_clear_object (&self->snippet_context);
+
+  g_free(self->language);
+  g_free(self->description);
+}
+
+static void
+ide_source_snippet_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (ide_source_snippet_parent_class)->finalize (object);
+}
+
+static void
+ide_source_snippet_get_property (GObject    *object,
+                                 guint       prop_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+  IdeSourceSnippet *self = IDE_SOURCE_SNIPPET (object);
+
+  switch (prop_id)
+    {
+    case PROP_BUFFER:
+      g_value_set_object (value, self->buffer);
+      break;
+
+    case PROP_MARK_BEGIN:
+      g_value_set_object (value, self->mark_begin);
+      break;
+
+    case PROP_MARK_END:
+      g_value_set_object (value, self->mark_end);
+      break;
+
+    case PROP_TRIGGER:
+      g_value_set_string (value, self->trigger);
+      break;
+
+    case PROP_LANGUAGE:
+      g_value_set_string (value, self->language);
+      break;
+
+    case PROP_DESCRIPTION:
+      g_value_set_string (value, self->description);
+      break;
+
+    case PROP_TAB_STOP:
+      g_value_set_uint (value, self->tab_stop);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_source_snippet_set_property (GObject      *object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  IdeSourceSnippet *self = IDE_SOURCE_SNIPPET (object);
+
+  switch (prop_id)
+    {
+    case PROP_TRIGGER:
+      ide_source_snippet_set_trigger (self, g_value_get_string (value));
+      break;
+
+    case PROP_LANGUAGE:
+      ide_source_snippet_set_language (self, g_value_get_string (value));
+      break;
+
+    case PROP_DESCRIPTION:
+      ide_source_snippet_set_description (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_source_snippet_class_init (IdeSourceSnippetClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = ide_source_snippet_dispose;
+  object_class->finalize = ide_source_snippet_finalize;
+  object_class->get_property = ide_source_snippet_get_property;
+  object_class->set_property = ide_source_snippet_set_property;
+
+  gParamSpecs[PROP_BUFFER] =
+    g_param_spec_object ("buffer",
+                         _("Buffer"),
+                         _("The GtkTextBuffer for the snippet."),
+                         GTK_TYPE_TEXT_BUFFER,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_BUFFER,
+                                   gParamSpecs[PROP_BUFFER]);
+
+  gParamSpecs[PROP_MARK_BEGIN] =
+    g_param_spec_object ("mark-begin",
+                         _("Mark Begin"),
+                         _("The beginning text mark."),
+                         GTK_TYPE_TEXT_MARK,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_MARK_BEGIN,
+                                   gParamSpecs[PROP_MARK_BEGIN]);
+
+  gParamSpecs[PROP_MARK_END] =
+    g_param_spec_object ("mark-end",
+                         _("Mark End"),
+                         _("The ending text mark."),
+                         GTK_TYPE_TEXT_MARK,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_MARK_END,
+                                   gParamSpecs[PROP_MARK_END]);
+
+  gParamSpecs[PROP_TRIGGER] =
+    g_param_spec_string ("trigger",
+                         _("Trigger"),
+                         _("The trigger for the snippet."),
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_TRIGGER,
+                                   gParamSpecs[PROP_TRIGGER]);
+
+  gParamSpecs[PROP_LANGUAGE] =
+    g_param_spec_string ("language",
+                         _("Language"),
+                         _("The language for the snippet."),
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_LANGUAGE,
+                                   gParamSpecs[PROP_LANGUAGE]);
+
+  gParamSpecs[PROP_DESCRIPTION] =
+    g_param_spec_string ("description",
+                         _("Description"),
+                         _("The description for the snippet."),
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_DESCRIPTION,
+                                   gParamSpecs[PROP_DESCRIPTION]);
+
+  gParamSpecs[PROP_TAB_STOP] =
+    g_param_spec_int ("tab-stop",
+                      _("Tab Stop"),
+                      _("The current tab stop."),
+                      -1,
+                      G_MAXINT,
+                      -1,
+                      (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_TAB_STOP,
+                                   gParamSpecs[PROP_TAB_STOP]);
+}
+
+static void
+ide_source_snippet_init (IdeSourceSnippet *self)
+{
+  self->tab_stop = 0;
+  self->max_tab_stop = -1;
+  self->chunks = g_ptr_array_new_with_free_func (g_object_unref);
+  self->runs = g_array_new (FALSE, FALSE, sizeof (gint));
+  self->description = NULL;
+}
diff --git a/libide/ide-source-snippet.h b/libide/ide-source-snippet.h
new file mode 100644
index 0000000..8d679a8
--- /dev/null
+++ b/libide/ide-source-snippet.h
@@ -0,0 +1,60 @@
+/* ide-source-snippet.h
+ *
+ * Copyright (C) 2013 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 IDE_SOURCE_SNIPPET_H
+#define IDE_SOURCE_SNIPPET_H
+
+#include <gtk/gtk.h>
+
+#include "ide-object.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_SOURCE_SNIPPET (ide_source_snippet_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeSourceSnippet, ide_source_snippet, IDE, SOURCE_SNIPPET, GObject)
+
+IdeSourceSnippet        *ide_source_snippet_new             (const gchar           *trigger,
+                                                             const gchar           *language);
+IdeSourceSnippet        *ide_source_snippet_copy            (IdeSourceSnippet      *snippet);
+const gchar             *ide_source_snippet_get_trigger     (IdeSourceSnippet      *snippet);
+void                     ide_source_snippet_set_trigger     (IdeSourceSnippet      *snippet,
+                                                             const gchar           *trigger);
+const gchar             *ide_source_snippet_get_language    (IdeSourceSnippet      *snippet);
+void                     ide_source_snippet_set_language    (IdeSourceSnippet      *snippet,
+                                                             const gchar           *language);
+const gchar             *ide_source_snippet_get_description (IdeSourceSnippet      *snippet);
+void                     ide_source_snippet_set_description (IdeSourceSnippet      *snippet,
+                                                             const gchar           *description);
+void                     ide_source_snippet_add_chunk       (IdeSourceSnippet      *snippet,
+                                                             IdeSourceSnippetChunk *chunk);
+guint                    ide_source_snippet_get_n_chunks    (IdeSourceSnippet      *snippet);
+gint                     ide_source_snippet_get_tab_stop    (IdeSourceSnippet      *snippet);
+IdeSourceSnippetChunk   *ide_source_snippet_get_nth_chunk   (IdeSourceSnippet      *snippet,
+                                                             guint                  n);
+void                     ide_source_snippet_get_chunk_range (IdeSourceSnippet      *snippet,
+                                                             IdeSourceSnippetChunk *chunk,
+                                                             GtkTextIter           *begin,
+                                                             GtkTextIter           *end);
+GtkTextMark             *ide_source_snippet_get_mark_begin  (IdeSourceSnippet      *snippet);
+GtkTextMark             *ide_source_snippet_get_mark_end    (IdeSourceSnippet      *snippet);
+IdeSourceSnippetContext *ide_source_snippet_get_context     (IdeSourceSnippet      *snippet);
+
+G_END_DECLS
+
+#endif /* IDE_SOURCE_SNIPPET_H */
diff --git a/libide/ide-source-snippets-manager.c b/libide/ide-source-snippets-manager.c
new file mode 100644
index 0000000..f57903a
--- /dev/null
+++ b/libide/ide-source-snippets-manager.c
@@ -0,0 +1,251 @@
+/* ide-source-snippets-manager.c
+ *
+ * Copyright (C) 2013 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/>.
+ */
+
+#define G_LOG_DOMAIN "ide-source-snippets-manager"
+
+#include <glib/gi18n.h>
+
+#include "ide-global.h"
+#include "ide-source-snippets-manager.h"
+#include "ide-source-snippet-parser.h"
+#include "ide-source-snippets.h"
+#include "ide-source-snippet.h"
+
+struct _IdeSourceSnippetsManager
+{
+  IdeObject   parent_instance;
+
+  GHashTable *by_language_id;
+};
+
+G_DEFINE_TYPE (IdeSourceSnippetsManager, ide_source_snippets_manager, IDE_TYPE_OBJECT)
+
+#define SNIPPETS_DIRECTORY "/org/gnome/libide/snippets/"
+
+static gboolean
+ide_source_snippets_manager_load_file (IdeSourceSnippetsManager  *self,
+                                       GFile                     *file,
+                                       GError                   **error)
+{
+  IdeSourceSnippetParser *parser;
+  GList *iter;
+
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPETS_MANAGER (self), FALSE);
+  g_return_val_if_fail (G_IS_FILE (file), FALSE);
+
+  parser = ide_source_snippet_parser_new ();
+
+  if (!ide_source_snippet_parser_load_from_file (parser, file, error))
+    {
+      g_object_unref (parser);
+      return FALSE;
+    }
+
+  for (iter = ide_source_snippet_parser_get_snippets (parser); iter; iter = iter->next)
+    {
+      IdeSourceSnippets *snippets;
+      IdeSourceSnippet *snippet;
+      const gchar *language;
+
+      snippet  = iter->data;
+      language = ide_source_snippet_get_language (snippet);
+      snippets = g_hash_table_lookup (self->by_language_id, language);
+
+      if (!snippets)
+        {
+          snippets = ide_source_snippets_new ();
+          g_hash_table_insert (self->by_language_id, g_strdup (language), snippets);
+        }
+
+      ide_source_snippets_add (snippets, snippet);
+    }
+
+  g_object_unref (parser);
+
+  return TRUE;
+}
+
+static void
+ide_source_snippets_manager_load_directory (IdeSourceSnippetsManager *manager,
+                                           const gchar             *path)
+{
+  const gchar *name;
+  GError *error = NULL;
+  gchar *filename;
+  GFile *file;
+  GDir *dir;
+
+  dir = g_dir_open (path, 0, &error);
+
+  if (!dir)
+    {
+      g_warning (_("Failed to open directory: %s"), error->message);
+      g_error_free (error);
+      return;
+    }
+
+  while ((name = g_dir_read_name (dir)))
+    {
+      if (g_str_has_suffix (name, ".snippets"))
+        {
+          filename = g_build_filename (path, name, NULL);
+          file = g_file_new_for_path (filename);
+          if (!ide_source_snippets_manager_load_file (manager, file, &error))
+            {
+              g_warning (_("Failed to load file: %s: %s"), filename, error->message);
+              g_clear_error (&error);
+            }
+          g_object_unref (file);
+          g_free (filename);
+        }
+    }
+
+  g_dir_close (dir);
+}
+
+static void
+ide_source_snippets_manager_load_worker (GTask        *task,
+                                         gpointer      source_object,
+                                         gpointer      task_data,
+                                         GCancellable *cancellable)
+{
+  g_autofree gchar *path = NULL;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (IDE_IS_SOURCE_SNIPPETS_MANAGER (source_object));
+
+  /* Load internal snippets */
+  path = g_build_filename (g_get_user_config_dir (), ide_get_program_name (), "snippets", NULL);
+  g_print ("Loading %s\n", path);
+  g_mkdir_with_parents (path, 0700);
+  ide_source_snippets_manager_load_directory (source_object, path);
+
+  g_task_return_boolean (task, TRUE);
+}
+
+void
+ide_source_snippets_manager_load_async (IdeSourceSnippetsManager *self,
+                                        GCancellable             *cancellable,
+                                        GAsyncReadyCallback       callback,
+                                        gpointer                  user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPETS_MANAGER (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_run_in_thread (task, ide_source_snippets_manager_load_worker);
+}
+
+gboolean
+ide_source_snippets_manager_load_finish (IdeSourceSnippetsManager  *self,
+                                         GAsyncResult              *result,
+                                         GError                   **error)
+{
+  GTask *task = (GTask *)result;
+
+  g_return_val_if_fail (G_IS_TASK (task), FALSE);
+
+  return g_task_propagate_boolean (task, error);
+}
+
+/**
+ * ide_source_snippets_manager_get_for_language:
+ *
+ * Gets the snippets for a given source language.
+ *
+ * Returns: (transfer none) (nullable): An #IdeSourceSnippets or %NULL.
+ */
+IdeSourceSnippets *
+ide_source_snippets_manager_get_for_language (IdeSourceSnippetsManager *self,
+                                              GtkSourceLanguage        *language)
+{
+  IdeSourceSnippets *snippets;
+  const char *language_id;
+
+  g_return_val_if_fail (IDE_IS_SOURCE_SNIPPETS_MANAGER (self), NULL);
+  g_return_val_if_fail (GTK_SOURCE_IS_LANGUAGE (language), NULL);
+
+  language_id = gtk_source_language_get_id (language);
+  snippets = g_hash_table_lookup (self->by_language_id, language_id);
+
+  return snippets;
+}
+
+static void
+ide_source_snippets_manager_constructed (GObject *object)
+{
+  IdeSourceSnippetsManager *self = (IdeSourceSnippetsManager *)object;
+  GError *error = NULL;
+  gchar **names;
+  guint i;
+
+  g_assert (IDE_IS_SOURCE_SNIPPETS_MANAGER (self));
+
+  names = g_resources_enumerate_children (SNIPPETS_DIRECTORY, G_RESOURCE_LOOKUP_FLAGS_NONE, &error);
+
+  if (!names)
+    {
+      g_message ("%s", error->message);
+      g_clear_error (&error);
+      return;
+    }
+
+  for (i = 0; names[i]; i++)
+    {
+      g_autofree gchar *path = NULL;
+      g_autoptr(GFile) file = NULL;
+
+      path = g_strdup_printf ("resource://"SNIPPETS_DIRECTORY"%s", names[i]);
+      file = g_file_new_for_uri (path);
+
+      if (!ide_source_snippets_manager_load_file (self, file, &error))
+        {
+          g_message ("%s", error->message);
+          g_clear_error (&error);
+        }
+    }
+
+  g_strfreev (names);
+}
+
+static void
+ide_source_snippets_manager_finalize (GObject *object)
+{
+  IdeSourceSnippetsManager *self = (IdeSourceSnippetsManager *)object;
+
+  g_clear_pointer (&self->by_language_id, g_hash_table_unref);
+
+  G_OBJECT_CLASS (ide_source_snippets_manager_parent_class)->finalize (object);
+}
+
+static void
+ide_source_snippets_manager_class_init (IdeSourceSnippetsManagerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = ide_source_snippets_manager_constructed;
+  object_class->finalize = ide_source_snippets_manager_finalize;
+}
+
+static void
+ide_source_snippets_manager_init (IdeSourceSnippetsManager *self)
+{
+  self->by_language_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+}
diff --git a/libide/ide-source-snippets-manager.h b/libide/ide-source-snippets-manager.h
new file mode 100644
index 0000000..7843e20
--- /dev/null
+++ b/libide/ide-source-snippets-manager.h
@@ -0,0 +1,45 @@
+/* ide-source-snippets-manager.h
+ *
+ * Copyright (C) 2013 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 IDE_SOURCE_SNIPPETS_MANAGER_H
+#define IDE_SOURCE_SNIPPETS_MANAGER_H
+
+#include <gtksourceview/gtksourcelanguage.h>
+
+#include "ide-object.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_SOURCE_SNIPPETS_MANAGER (ide_source_snippets_manager_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeSourceSnippetsManager, ide_source_snippets_manager,
+                      IDE, SOURCE_SNIPPETS_MANAGER, IdeObject)
+
+void               ide_source_snippets_manager_load_async       (IdeSourceSnippetsManager  *self,
+                                                                 GCancellable              *cancellable,
+                                                                 GAsyncReadyCallback        callback,
+                                                                 gpointer                   user_data);
+gboolean           ide_source_snippets_manager_load_finish      (IdeSourceSnippetsManager  *self,
+                                                                 GAsyncResult              *result,
+                                                                 GError                   **error);
+IdeSourceSnippets *ide_source_snippets_manager_get_for_language (IdeSourceSnippetsManager  *manager,
+                                                                 GtkSourceLanguage         *language);
+
+G_END_DECLS
+
+#endif /* IDE_SOURCE_SNIPPETS_MANAGER_H */
diff --git a/libide/ide-source-snippets.c b/libide/ide-source-snippets.c
new file mode 100644
index 0000000..881b7f2
--- /dev/null
+++ b/libide/ide-source-snippets.c
@@ -0,0 +1,176 @@
+/* ide-source-snippets.c
+ *
+ * Copyright (C) 2013 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 <glib/gi18n.h>
+#include <string.h>
+
+#include "ide-source-snippet.h"
+#include "ide-source-snippet-chunk.h"
+#include "ide-source-snippet-parser.h"
+#include "ide-source-snippets.h"
+
+#include "trie.h"
+
+G_DEFINE_TYPE (IdeSourceSnippets, ide_source_snippets, G_TYPE_OBJECT)
+
+struct _IdeSourceSnippetsPrivate
+{
+  Trie *snippets;
+};
+
+IdeSourceSnippets *
+ide_source_snippets_new (void)
+{
+  return g_object_new (IDE_TYPE_SOURCE_SNIPPETS,
+                       NULL);
+}
+
+void
+ide_source_snippets_clear (IdeSourceSnippets *snippets)
+{
+  IdeSourceSnippetsPrivate *priv;
+
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPETS (snippets));
+
+  priv = snippets->priv;
+
+  trie_destroy (priv->snippets);
+  priv->snippets = trie_new (g_object_unref);
+}
+
+static gboolean
+copy_into (Trie        *trie,
+           const gchar *key,
+           gpointer     value,
+           gpointer     user_data)
+{
+  IdeSourceSnippet *snippet = value;
+  Trie *dest = user_data;
+
+  g_assert (dest);
+  g_assert (IDE_IS_SOURCE_SNIPPET (snippet));
+
+  trie_insert (dest, key, g_object_ref (snippet));
+
+  return FALSE;
+}
+
+void
+ide_source_snippets_merge (IdeSourceSnippets *snippets,
+                          IdeSourceSnippets *other)
+{
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPETS (snippets));
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPETS (other));
+
+  trie_traverse (other->priv->snippets,
+                 "",
+                 G_PRE_ORDER,
+                 G_TRAVERSE_LEAVES,
+                 -1,
+                 copy_into,
+                 snippets->priv->snippets);
+}
+
+void
+ide_source_snippets_add (IdeSourceSnippets *snippets,
+                        IdeSourceSnippet  *snippet)
+{
+  const gchar *trigger;
+
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPETS (snippets));
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPET (snippet));
+
+  trigger = ide_source_snippet_get_trigger (snippet);
+  trie_insert (snippets->priv->snippets, trigger, g_object_ref (snippet));
+}
+
+gboolean
+ide_source_snippets_foreach_cb (Trie        *trie,
+                               const gchar *key,
+                               gpointer     value,
+                               gpointer     user_data)
+{
+  gpointer *closure = user_data;
+
+  ((GFunc) closure[0])(value, closure[1]);
+
+  return FALSE;
+}
+
+/**
+ * ide_source_snippets_foreach:
+ * @foreach_func: (scope call): A callback to execute for each snippet.
+ *
+ */
+void
+ide_source_snippets_foreach (IdeSourceSnippets *snippets,
+                             const gchar       *prefix,
+                             GFunc              foreach_func,
+                             gpointer           user_data)
+{
+  IdeSourceSnippetsPrivate *priv;
+  gpointer closure[2] = { foreach_func, user_data };
+
+  g_return_if_fail (IDE_IS_SOURCE_SNIPPETS (snippets));
+  g_return_if_fail (foreach_func);
+
+  priv = snippets->priv;
+
+  if (!prefix)
+    prefix = "";
+
+  trie_traverse (priv->snippets,
+                 prefix,
+                 G_PRE_ORDER,
+                 G_TRAVERSE_LEAVES,
+                 -1,
+                 ide_source_snippets_foreach_cb,
+                 (gpointer) closure);
+}
+
+static void
+ide_source_snippets_finalize (GObject *object)
+{
+  IdeSourceSnippetsPrivate *priv;
+
+  priv = IDE_SOURCE_SNIPPETS (object)->priv;
+
+  g_clear_pointer (&priv->snippets, (GDestroyNotify) trie_destroy);
+
+  G_OBJECT_CLASS (ide_source_snippets_parent_class)->finalize (object);
+}
+
+static void
+ide_source_snippets_class_init (IdeSourceSnippetsClass *klass)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (klass);
+  object_class->finalize = ide_source_snippets_finalize;
+  g_type_class_add_private (object_class, sizeof (IdeSourceSnippetsPrivate));
+}
+
+static void
+ide_source_snippets_init (IdeSourceSnippets *snippets)
+{
+  snippets->priv = G_TYPE_INSTANCE_GET_PRIVATE (snippets,
+                                                IDE_TYPE_SOURCE_SNIPPETS,
+                                                IdeSourceSnippetsPrivate);
+
+  snippets->priv->snippets = trie_new (g_object_unref);
+}
diff --git a/libide/ide-source-snippets.h b/libide/ide-source-snippets.h
new file mode 100644
index 0000000..16f074d
--- /dev/null
+++ b/libide/ide-source-snippets.h
@@ -0,0 +1,67 @@
+/* ide-source-snippets.h
+ *
+ * Copyright (C) 2013 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 IDE_SOURCE_SNIPPETS_H
+#define IDE_SOURCE_SNIPPETS_H
+
+#include <gio/gio.h>
+
+#include "ide-source-snippet.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_SOURCE_SNIPPETS            (ide_source_snippets_get_type())
+#define IDE_SOURCE_SNIPPETS(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDE_TYPE_SOURCE_SNIPPETS, 
IdeSourceSnippets))
+#define IDE_SOURCE_SNIPPETS_CONST(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDE_TYPE_SOURCE_SNIPPETS, 
IdeSourceSnippets const))
+#define IDE_SOURCE_SNIPPETS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  IDE_TYPE_SOURCE_SNIPPETS, 
IdeSourceSnippetsClass))
+#define IDE_IS_SOURCE_SNIPPETS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDE_TYPE_SOURCE_SNIPPETS))
+#define IDE_IS_SOURCE_SNIPPETS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  IDE_TYPE_SOURCE_SNIPPETS))
+#define IDE_SOURCE_SNIPPETS_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  IDE_TYPE_SOURCE_SNIPPETS, 
IdeSourceSnippetsClass))
+
+typedef struct _IdeSourceSnippets        IdeSourceSnippets;
+typedef struct _IdeSourceSnippetsClass   IdeSourceSnippetsClass;
+typedef struct _IdeSourceSnippetsPrivate IdeSourceSnippetsPrivate;
+
+struct _IdeSourceSnippets
+{
+  GObject parent;
+
+  /*< private >*/
+  IdeSourceSnippetsPrivate *priv;
+};
+
+struct _IdeSourceSnippetsClass
+{
+  GObjectClass parent_class;
+};
+
+void              ide_source_snippets_add            (IdeSourceSnippets *snippets,
+                                                     IdeSourceSnippet  *snippet);
+void              ide_source_snippets_clear          (IdeSourceSnippets *snippets);
+void              ide_source_snippets_merge          (IdeSourceSnippets *snippets,
+                                                     IdeSourceSnippets *other);
+IdeSourceSnippets *ide_source_snippets_new            (void);
+GType             ide_source_snippets_get_type       (void);
+void              ide_source_snippets_foreach        (IdeSourceSnippets *snippets,
+                                                     const gchar      *prefix,
+                                                     GFunc             foreach_func,
+                                                     gpointer          user_data);
+
+G_END_DECLS
+
+#endif /* IDE_SOURCE_SNIPPETS_H */
diff --git a/libide/ide-types.h b/libide/ide-types.h
index 2dbd371..43db87d 100644
--- a/libide/ide-types.h
+++ b/libide/ide-types.h
@@ -124,6 +124,16 @@ typedef struct _IdeSourceLocation              IdeSourceLocation;
 
 typedef struct _IdeSourceRange                 IdeSourceRange;
 
+typedef struct _IdeSourceSnippet               IdeSourceSnippet;
+
+typedef struct _IdeSourceSnippetChunk          IdeSourceSnippetChunk;
+
+typedef struct _IdeSourceSnippetContext        IdeSourceSnippetContext;
+
+typedef struct _IdeSourceSnippets              IdeSourceSnippets;
+
+typedef struct _IdeSourceSnippetsManager       IdeSourceSnippetsManager;
+
 typedef struct _IdeSymbol                      IdeSymbol;
 
 typedef struct _IdeSymbolResolver              IdeSymbolResolver;
diff --git a/libide/ide.h b/libide/ide.h
index e43dd7e..e07fbd8 100644
--- a/libide/ide.h
+++ b/libide/ide.h
@@ -65,6 +65,11 @@ G_BEGIN_DECLS
 #include "ide-search-provider.h"
 #include "ide-search-result.h"
 #include "ide-service.h"
+#include "ide-source-snippet-chunk.h"
+#include "ide-source-snippet-context.h"
+#include "ide-source-snippet.h"
+#include "ide-source-snippets.h"
+#include "ide-source-snippets-manager.h"
 #include "ide-source-location.h"
 #include "ide-source-range.h"
 #include "ide-source-view.h"
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 047ff79..0fbb881 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -49,6 +49,12 @@ libide/ide-script-manager.c
 libide/ide-script.c
 libide/ide-search-result.c
 libide/ide-service.c
+libide/ide-source-snippet-chunk.c
+libide/ide-source-snippet-completion-item.c
+libide/ide-source-snippet-completion-provider.c
+libide/ide-source-snippet-parser.c
+libide/ide-source-snippet.c
+libide/ide-source-snippets-manager.c
 libide/ide.c
 libide/pygobject/ide-pygobject-script.c
 libide/tasks/ide-load-directory-task.c



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