[gtksourceview/wip/chergert/snippets: 2/2] snippet: implement basic snippet system



commit bfba67e678f4f1a5dbeb66f32fc9564d54c6875e
Author: Christian Hergert <chergert redhat com>
Date:   Fri Jan 17 16:12:00 2020 -0800

    snippet: implement basic snippet system
    
    This is a straightforward port of Builder's snippet system to
    GtkSourceView. Additional work is necessary to integrate with the
    GtkSourceView object as well as additional documentation.

 docs/reference/meson.build                      |    2 +
 gtksourceview/gtksource.h                       |    3 +
 gtksourceview/gtksourcesnippet-private.h        |   71 ++
 gtksourceview/gtksourcesnippet.c                | 1299 +++++++++++++++++++++++
 gtksourceview/gtksourcesnippet.h                |   70 ++
 gtksourceview/gtksourcesnippetchunk.c           |  402 +++++++
 gtksourceview/gtksourcesnippetchunk.h           |   67 ++
 gtksourceview/gtksourcesnippetcontext-private.h |   29 +
 gtksourceview/gtksourcesnippetcontext.c         |  911 ++++++++++++++++
 gtksourceview/gtksourcesnippetcontext.h         |   65 ++
 gtksourceview/gtksourcetypes.h                  |    3 +
 gtksourceview/meson.build                       |    6 +
 12 files changed, 2928 insertions(+)
---
diff --git a/docs/reference/meson.build b/docs/reference/meson.build
index 622a4de8..7fd35b46 100644
--- a/docs/reference/meson.build
+++ b/docs/reference/meson.build
@@ -35,6 +35,8 @@ reference_private_h = [
   'gtksourcepixbufhelper-private.h',
   'gtksourceregex-private.h',
   'gtksourcesearchcontext-private.h',
+  'gtksourcesnippet-private.h',
+  'gtksourcesnippetcontext-private.h',
   'gtksourcestyle-private.h',
   'gtksourcestylescheme-private.h',
   'gtksourcestyleschememanager-private.h',
diff --git a/gtksourceview/gtksource.h b/gtksourceview/gtksource.h
index 517c06ae..bea7c201 100644
--- a/gtksourceview/gtksource.h
+++ b/gtksourceview/gtksource.h
@@ -47,6 +47,9 @@
 #include "gtksourceregion.h"
 #include "gtksourcesearchcontext.h"
 #include "gtksourcesearchsettings.h"
+#include "gtksourcesnippet.h"
+#include "gtksourcesnippetchunk.h"
+#include "gtksourcesnippetcontext.h"
 #include "gtksourcespacedrawer.h"
 #include "gtksourcestyle.h"
 #include "gtksourcestylescheme.h"
diff --git a/gtksourceview/gtksourcesnippet-private.h b/gtksourceview/gtksourcesnippet-private.h
new file mode 100644
index 00000000..0a84ed60
--- /dev/null
+++ b/gtksourceview/gtksourcesnippet-private.h
@@ -0,0 +1,71 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+ #pragma once
+
+ #include "gtksourcesnippet.h"
+
+ G_BEGIN_DECLS
+
+G_GNUC_INTERNAL
+void         _gtk_source_snippet_replace_current_chunk_text (GtkSourceSnippet *self,
+                                                             const gchar      *new_text);
+G_GNUC_INTERNAL
+gchar       *_gtk_source_snippet_get_edited_text            (GtkSourceSnippet *self);
+G_GNUC_INTERNAL
+gboolean     _gtk_source_snippet_begin                      (GtkSourceSnippet *self,
+                                                             GtkTextBuffer    *buffer,
+                                                             GtkTextIter      *iter);
+G_GNUC_INTERNAL
+void         _gtk_source_snippet_finish                     (GtkSourceSnippet *self);
+G_GNUC_INTERNAL
+gboolean     _gtk_source_snippet_move_next                  (GtkSourceSnippet *self);
+G_GNUC_INTERNAL
+gboolean     _gtk_source_snippet_move_previous              (GtkSourceSnippet *self);
+G_GNUC_INTERNAL
+void         _gtk_source_snippet_before_insert_text         (GtkSourceSnippet *self,
+                                                             GtkTextBuffer    *buffer,
+                                                             GtkTextIter      *iter,
+                                                             gchar            *text,
+                                                             gint              len);
+G_GNUC_INTERNAL
+void         _gtk_source_snippet_after_insert_text          (GtkSourceSnippet *self,
+                                                             GtkTextBuffer    *buffer,
+                                                             GtkTextIter      *iter,
+                                                             gchar            *text,
+                                                             gint              len);
+G_GNUC_INTERNAL
+void         _gtk_source_snippet_before_delete_range        (GtkSourceSnippet *self,
+                                                             GtkTextBuffer    *buffer,
+                                                             GtkTextIter      *begin,
+                                                             GtkTextIter      *end);
+G_GNUC_INTERNAL
+void         _gtk_source_snippet_after_delete_range         (GtkSourceSnippet *self,
+                                                             GtkTextBuffer    *buffer,
+                                                             GtkTextIter      *begin,
+                                                             GtkTextIter      *end);
+G_GNUC_INTERNAL
+gboolean     _gtk_source_snippet_insert_set                 (GtkSourceSnippet *self,
+                                                             GtkTextMark      *mark);
+G_GNUC_INTERNAL
+GtkTextMark *_gtk_source_snippet_get_mark_begin             (GtkSourceSnippet *self);
+G_GNUC_INTERNAL
+GtkTextMark *_gtk_source_snippet_get_mark_end               (GtkSourceSnippet *self);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcesnippet.c b/gtksourceview/gtksourcesnippet.c
new file mode 100644
index 00000000..f0fd74d3
--- /dev/null
+++ b/gtksourceview/gtksourcesnippet.c
@@ -0,0 +1,1299 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gtksourcesnippet-private.h"
+#include "gtksourcesnippetchunk.h"
+#include "gtksourcesnippetcontext.h"
+#include "gtksourcesnippetcontext-private.h"
+
+#define TAG_SNIPPET_POSITION "snippet::edit-position"
+
+struct _GtkSourceSnippet
+{
+       GObject                  parent_instance;
+
+       GtkSourceSnippetContext *context;
+       GtkTextBuffer           *buffer;
+       GPtrArray               *chunks;
+       GArray                  *runs;
+
+       GtkTextMark             *mark_begin;
+       GtkTextMark             *mark_end;
+       gchar                   *trigger;
+       const gchar             *language_id;
+       gchar                   *description;
+
+       gint                     focus_position;
+       gint                     max_focus_position;
+       gint                     current_chunk;
+
+       guint                    inserted : 1;
+};
+
+G_DEFINE_TYPE (GtkSourceSnippet, gtk_source_snippet, G_TYPE_OBJECT)
+
+enum {
+       PROP_0,
+       PROP_BUFFER,
+       PROP_DESCRIPTION,
+       PROP_LANGUAGE_ID,
+       PROP_MARK_BEGIN,
+       PROP_MARK_END,
+       PROP_POSITION,
+       PROP_TRIGGER,
+       N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+/**
+ * gtk_source_snippet_new:
+ * @trigger: (nullable): the trigger word
+ * @language_id: (nullable): the source language
+ *
+ * Creates a new #GtkSourceSnippet
+ *
+ * Returns: (transfer full): A new #GtkSourceSnippet
+ *
+ * Since: 5.0
+ */
+GtkSourceSnippet *
+gtk_source_snippet_new (const gchar *trigger,
+                        const gchar *language_id)
+{
+       g_return_val_if_fail (trigger != NULL, NULL);
+
+       return g_object_new (GTK_SOURCE_TYPE_SNIPPET,
+                            "trigger", trigger,
+                            "language-id", language_id,
+                            NULL);
+}
+
+/**
+ * gtk_source_snippet_copy:
+ * @self: a #GtkSourceSnippet
+ *
+ * Does a deep copy of the snippet.
+ *
+ * Returns: (transfer full): A new #GtkSourceSnippet
+ *
+ * Since: 5.0
+ */
+GtkSourceSnippet *
+gtk_source_snippet_copy (GtkSourceSnippet *self)
+{
+       GtkSourceSnippet *ret;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET (self), NULL);
+
+       ret = g_object_new (GTK_SOURCE_TYPE_SNIPPET,
+                           "trigger", self->trigger,
+                           "language-id", self->language_id,
+                           "description", self->description,
+                           NULL);
+
+       for (guint i = 0; i < self->chunks->len; i++)
+       {
+               GtkSourceSnippetChunk *old_chunk;
+               GtkSourceSnippetChunk *new_chunk;
+
+               old_chunk = g_ptr_array_index (self->chunks, i);
+               new_chunk = gtk_source_snippet_chunk_copy (old_chunk);
+               gtk_source_snippet_add_chunk (ret, new_chunk);
+               g_object_unref (new_chunk);
+       }
+
+       return g_steal_pointer (&ret);
+}
+
+/**
+ * gtk_source_snippet_get_focus_position:
+ * @self: a #GtkSourceSnippet
+ *
+ * Gets the current focus for the snippet. This is changed
+ * as the user tabs through focus locations.
+ *
+ * Returns: The focus position, or -1 if unset.
+ *
+ * Since: 5.0
+ */
+gint
+gtk_source_snippet_get_focus_position (GtkSourceSnippet *self)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET (self), -1);
+
+       return self->focus_position;
+}
+
+/**
+ * gtk_source_snippet_get_n_chunks:
+ * @self: a #GtkSourceSnippet
+ *
+ * Gets the number of chunks in the snippet.
+ *
+ * Note that not all chunks are editable.
+ *
+ * Returns: The number of chunks.
+ *
+ * Since: 5.0
+ */
+guint
+gtk_source_snippet_get_n_chunks (GtkSourceSnippet *self)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET (self), 0);
+
+       return self->chunks->len;
+}
+
+/**
+ * gtk_source_snippet_get_nth_chunk:
+ * @self: a #GtkSourceSnippet
+ * @nth: the nth chunk to get
+ *
+ * Gets the chunk at @nth.
+ *
+ * Returns: (transfer none): an #GtkSourceSnippetChunk
+ *
+ * Since: 5.0
+ */
+GtkSourceSnippetChunk *
+gtk_source_snippet_get_nth_chunk (GtkSourceSnippet *self,
+                           guint             nth)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET (self), 0);
+
+       if (nth < self->chunks->len)
+               return g_ptr_array_index (self->chunks, nth);
+
+       return NULL;
+}
+
+/**
+ * gtk_source_snippet_get_trigger:
+ * @self: a #GtkSourceSnippet
+ *
+ * Gets the trigger for the source snippet. A trigger is
+ * a word that can be expanded into the full snippet when
+ * the user presses Tab.
+ *
+ * Returns: (nullable): A string or %NULL
+ *
+ * Since: 5.0
+ */
+const gchar *
+gtk_source_snippet_get_trigger (GtkSourceSnippet *self)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET (self), NULL);
+
+       return self->trigger;
+}
+
+/**
+ * gtk_source_snippet_set_trigger:
+ * @self: a #GtkSourceSnippet
+ * @trigger: the trigger word
+ *
+ * Sets the trigger for the snippet.
+ *
+ * Since: 5.0
+ */
+void
+gtk_source_snippet_set_trigger (GtkSourceSnippet *self,
+                                const gchar      *trigger)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET (self));
+
+       if (g_strcmp0 (trigger, self->trigger) != 0)
+       {
+               g_free (self->trigger);
+               self->trigger = g_strdup (trigger);
+               g_object_notify_by_pspec (G_OBJECT (self),
+                                         properties [PROP_TRIGGER]);
+       }
+}
+
+/**
+ * gtk_source_snippet_get_language_id:
+ * @self: a #GtkSourceSnippet
+ *
+ * Gets the language-id used for the source snippet.
+ *
+ * The language identifier should be one that matches a
+ * source language #GtkSourceLanguage:id property.
+ *
+ * Returns: the language identifier
+ *
+ * Since: 5.0
+ */
+const gchar *
+gtk_source_snippet_get_language_id (GtkSourceSnippet *self)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET (self), NULL);
+
+       return self->language_id;
+}
+
+/**
+ * gtk_source_snippet_set_language_id:
+ * @self: a #GtkSourceSnippet
+ * @language_id: the language identifier for the snippet
+ *
+ * Sets the language identifier for the snippet.
+ *
+ * This should match the #GtkSourceLanguage:id identifier.
+ *
+ * Since: 5.0
+ */
+void
+gtk_source_snippet_set_language_id (GtkSourceSnippet *self,
+                                    const gchar      *language_id)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET (self));
+
+       language_id = g_intern_string (language_id);
+
+       if (language_id != self->language_id)
+       {
+               self->language_id = language_id;
+               g_object_notify_by_pspec (G_OBJECT (self),
+                                         properties [PROP_LANGUAGE_ID]);
+       }
+}
+
+/**
+ * gtk_source_snippet_get_description:
+ * @self: a #GtkSourceSnippet
+ *
+ * Gets the description for the snippet.
+ *
+ * Since: 5.0
+ */
+const gchar *
+gtk_source_snippet_get_description (GtkSourceSnippet *self)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET (self), NULL);
+
+       return self->description;
+}
+
+/**
+ * gtk_source_snippet_set_description:
+ * @self: a #GtkSourceSnippet
+ * @description: the snippet description
+ *
+ * Sets the description for the snippet.
+ *
+ * Since: 5.0
+ */
+void
+gtk_source_snippet_set_description (GtkSourceSnippet *self,
+                                    const gchar      *description)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET (self));
+
+       if (g_strcmp0 (description, self->description) != 0)
+       {
+               g_free (self->description);
+               self->description = g_strdup (description);
+               g_object_notify_by_pspec (G_OBJECT (self),
+                                         properties [PROP_DESCRIPTION]);
+       }
+}
+
+static gint
+gtk_source_snippet_get_offset (GtkSourceSnippet *self,
+                               GtkTextIter      *iter)
+{
+       GtkTextIter begin;
+       gint ret;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_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
+gtk_source_snippet_get_index (GtkSourceSnippet *self,
+                              GtkTextIter      *iter)
+{
+       gint offset;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET (self), 0);
+       g_return_val_if_fail (iter, 0);
+
+       offset = gtk_source_snippet_get_offset (self, iter);
+
+       for (guint i = 0; i < self->runs->len; i++)
+       {
+               gint run = g_array_index (self->runs, gint, i);
+
+               offset -= run;
+
+               if (offset <= 0)
+               {
+                       /*
+                        * NOTE:
+                        *
+                        * 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 (self->current_chunk > -1 && (i + 1) == (guint)self->current_chunk)
+                               return i + 1;
+                       else
+                               return i;
+               }
+       }
+
+       return (self->runs->len - 1);
+}
+
+static gboolean
+gtk_source_snippet_within_bounds (GtkSourceSnippet *self,
+                                  GtkTextIter      *iter)
+{
+       GtkTextIter begin;
+       GtkTextIter end;
+       gboolean ret;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_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
+_gtk_source_snippet_insert_set (GtkSourceSnippet *self,
+                                GtkTextMark      *mark)
+{
+       GtkTextIter iter;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_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 (!gtk_source_snippet_within_bounds (self, &iter))
+       {
+               return FALSE;
+       }
+
+       self->current_chunk = gtk_source_snippet_get_index (self, &iter);
+
+       return TRUE;
+}
+
+static void
+gtk_source_snippet_get_nth_chunk_range (GtkSourceSnippet *self,
+                                        gint              nth,
+                                        GtkTextIter      *begin,
+                                        GtkTextIter      *end)
+{
+       gint run;
+
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET (self));
+       g_return_if_fail (nth >= 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 (guint i = 0; i < nth; 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, nth);
+       gtk_text_iter_forward_chars (end, run);
+}
+
+static void
+gtk_source_snippet_get_chunk_range (GtkSourceSnippet      *self,
+                                    GtkSourceSnippetChunk *chunk,
+                                    GtkTextIter           *begin,
+                                    GtkTextIter           *end)
+{
+
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET (self));
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk));
+
+       for (guint i = 0; i < self->chunks->len; i++)
+       {
+               GtkSourceSnippetChunk *item;
+
+               item = g_ptr_array_index (self->chunks, i);
+
+               if (item == chunk)
+               {
+                       gtk_source_snippet_get_nth_chunk_range (self, i, begin, end);
+                       return;
+               }
+       }
+
+       g_warn_if_reached ();
+}
+
+static void
+gtk_source_snippet_select_chunk (GtkSourceSnippet *self,
+                                 gint              nth)
+{
+       GtkTextIter begin;
+       GtkTextIter end;
+
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET (self));
+       g_return_if_fail (nth >= 0);
+       g_return_if_fail ((guint)nth < self->runs->len);
+
+       gtk_source_snippet_get_nth_chunk_range (self, nth, &begin, &end);
+       gtk_text_iter_order (&begin, &end);
+
+       g_debug ("Selecting chunk %d with range %d:%d to %d:%d (offset %d+%d)",
+                nth,
+                gtk_text_iter_get_line (&begin) + 1,
+                gtk_text_iter_get_line_offset (&begin) + 1,
+                gtk_text_iter_get_line (&end) + 1,
+                gtk_text_iter_get_line_offset (&end) + 1,
+                gtk_text_iter_get_offset (&begin),
+                gtk_text_iter_get_offset (&end) - gtk_text_iter_get_offset (&begin));
+
+       gtk_text_buffer_select_range (self->buffer, &begin, &end);
+       self->current_chunk = nth;
+
+#ifndef G_DISABLE_ASSERT
+       {
+               GtkTextIter set_begin;
+               GtkTextIter set_end;
+
+               gtk_text_buffer_get_selection_bounds (self->buffer, &set_begin, &set_end);
+
+               g_assert (gtk_text_iter_equal (&set_begin, &begin));
+               g_assert (gtk_text_iter_equal (&set_end, &end));
+       }
+#endif
+}
+
+gboolean
+_gtk_source_snippet_move_next (GtkSourceSnippet *self)
+{
+       GtkTextIter iter;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET (self), FALSE);
+
+       if (self->focus_position > self->max_focus_position)
+       {
+               return FALSE;
+       }
+
+       self->focus_position++;
+
+       for (guint i = 0; i < self->chunks->len; i++)
+       {
+               GtkSourceSnippetChunk *chunk = g_ptr_array_index (self->chunks, i);
+
+               if (gtk_source_snippet_chunk_get_focus_position (chunk) == self->focus_position)
+               {
+                       gtk_source_snippet_select_chunk (self, i);
+                       return TRUE;
+               }
+       }
+
+       for (guint i = 0; i < self->chunks->len; i++)
+       {
+               GtkSourceSnippetChunk *chunk = g_ptr_array_index (self->chunks, i);
+
+               if (gtk_source_snippet_chunk_get_focus_position (chunk) == 0)
+               {
+                       gtk_source_snippet_select_chunk (self, i);
+                       return FALSE;
+               }
+       }
+
+       g_debug ("No more tab stops, moving to end of snippet");
+
+       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
+_gtk_source_snippet_move_previous (GtkSourceSnippet *self)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET (self), FALSE);
+
+       self->focus_position = MAX (1, self->focus_position - 1);
+
+       for (guint i = 0; i < self->chunks->len; i++)
+       {
+               GtkSourceSnippetChunk *chunk = g_ptr_array_index (self->chunks, i);
+
+               if (gtk_source_snippet_chunk_get_focus_position (chunk) == self->focus_position)
+               {
+                       gtk_source_snippet_select_chunk (self, i);
+                       return TRUE;
+               }
+       }
+
+       g_debug ("No previous tab stop to select, ignoring move request");
+
+       return FALSE;
+}
+
+static void
+gtk_source_snippet_update_context (GtkSourceSnippet *self)
+{
+       GtkSourceSnippetContext *context;
+
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET (self));
+
+       if (self->chunks == NULL || self->chunks->len == 0)
+       {
+               return;
+       }
+
+       context = gtk_source_snippet_get_context (self);
+
+       _gtk_source_snippet_context_emit_changed (context);
+
+       for (guint i = 0; i < self->chunks->len; i++)
+       {
+               GtkSourceSnippetChunk *chunk = g_ptr_array_index (self->chunks, i);
+               gint focus_position;
+
+               g_assert (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk));
+
+               focus_position = gtk_source_snippet_chunk_get_focus_position (chunk);
+
+               if (focus_position > 0)
+               {
+                       const gchar *text;
+
+                       if ((text = gtk_source_snippet_chunk_get_text (chunk)))
+                       {
+                               gchar key[12];
+
+                               g_snprintf (key, sizeof key, "%d", focus_position);
+                               key[sizeof key - 1] = '\0';
+
+                               gtk_source_snippet_context_set_variable (context, key, text);
+                       }
+               }
+       }
+
+       _gtk_source_snippet_context_emit_changed (context);
+}
+
+static void
+gtk_source_snippet_clear_tags (GtkSourceSnippet *self)
+{
+       g_assert (GTK_SOURCE_IS_SNIPPET (self));
+
+       if (self->mark_begin != NULL && self->mark_end != NULL)
+       {
+               GtkTextBuffer *buffer;
+               GtkTextIter begin;
+               GtkTextIter end;
+
+               buffer = gtk_text_mark_get_buffer (self->mark_begin);
+
+               gtk_text_buffer_get_iter_at_mark (buffer, &begin, self->mark_begin);
+               gtk_text_buffer_get_iter_at_mark (buffer, &end, self->mark_end);
+
+               gtk_text_buffer_remove_tag_by_name (buffer,
+                                                   TAG_SNIPPET_POSITION,
+                                                   &begin, &end);
+       }
+}
+
+static void
+gtk_source_snippet_update_tags (GtkSourceSnippet *self)
+{
+       GtkTextBuffer *buffer;
+
+       g_assert (GTK_SOURCE_IS_SNIPPET (self));
+
+       gtk_source_snippet_clear_tags (self);
+
+       buffer = gtk_text_mark_get_buffer (self->mark_begin);
+
+       for (guint i = 0; i < self->chunks->len; i++)
+       {
+               GtkSourceSnippetChunk *chunk = g_ptr_array_index (self->chunks, i);
+               gint focus_position = gtk_source_snippet_chunk_get_focus_position (chunk);
+
+               if (focus_position >= 0)
+               {
+                       GtkTextIter begin;
+                       GtkTextIter end;
+
+                       gtk_source_snippet_get_chunk_range (self, chunk, &begin, &end);
+                       gtk_text_buffer_apply_tag_by_name (buffer,
+                                                          TAG_SNIPPET_POSITION,
+                                                          &begin, &end);
+               }
+       }
+}
+
+gboolean
+_gtk_source_snippet_begin (GtkSourceSnippet *self,
+                           GtkTextBuffer    *buffer,
+                           GtkTextIter      *iter)
+{
+       GtkSourceSnippetContext *context;
+       GtkTextMark *mark;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_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 = gtk_source_snippet_get_context (self);
+
+       gtk_source_snippet_update_context (self);
+       _gtk_source_snippet_context_emit_changed (context);
+       gtk_source_snippet_update_context (self);
+
+       self->buffer = g_object_ref (buffer);
+
+       mark = gtk_text_buffer_create_mark (buffer, NULL, iter, TRUE);
+       self->mark_begin = g_object_ref (mark);
+
+       gtk_text_buffer_begin_user_action (buffer);
+
+       for (guint i = 0; i < self->chunks->len; i++)
+       {
+               GtkSourceSnippetChunk *chunk;
+               const gchar *text;
+
+               chunk = g_ptr_array_index (self->chunks, i);
+               text = gtk_source_snippet_chunk_get_text (chunk);
+
+               if (text != NULL)
+               {
+                       gint len;
+
+                       len = g_utf8_strlen (text, -1);
+                       g_array_append_val (self->runs, len);
+                       gtk_text_buffer_insert (buffer, iter, text, -1);
+               }
+       }
+
+       mark = gtk_text_buffer_create_mark (buffer, NULL, iter, FALSE);
+       self->mark_end = g_object_ref (mark);
+
+       gtk_text_buffer_end_user_action (buffer);
+
+       gtk_source_snippet_update_tags (self);
+
+       return _gtk_source_snippet_move_next (self);
+}
+
+void
+_gtk_source_snippet_finish (GtkSourceSnippet *self)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET (self));
+
+       gtk_source_snippet_clear_tags (self);
+
+       g_clear_object (&self->mark_begin);
+       g_clear_object (&self->mark_end);
+       g_clear_object (&self->buffer);
+}
+
+void
+gtk_source_snippet_add_chunk (GtkSourceSnippet      *self,
+                              GtkSourceSnippetChunk *chunk)
+{
+       gint focus_position;
+
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET (self));
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk));
+       g_return_if_fail (!self->inserted);
+
+       g_ptr_array_add (self->chunks, g_object_ref (chunk));
+
+       gtk_source_snippet_chunk_set_context (chunk, self->context);
+
+       focus_position = gtk_source_snippet_chunk_get_focus_position (chunk);
+       self->max_focus_position = MAX (self->max_focus_position, focus_position);
+}
+
+static gchar *
+gtk_source_snippet_get_nth_text (GtkSourceSnippet *self,
+                                 gint              nth)
+{
+       GtkTextIter iter;
+       GtkTextIter end;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET (self), NULL);
+       g_return_val_if_fail (nth >= 0, NULL);
+
+       gtk_text_buffer_get_iter_at_mark (self->buffer, &iter, self->mark_begin);
+
+       for (gint i = 0; i < nth; 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, nth));
+
+       return gtk_text_buffer_get_text (self->buffer, &iter, &end, TRUE);
+}
+
+static void
+gtk_source_snippet_replace_chunk_text (GtkSourceSnippet *self,
+                                       gint              nth,
+                                       const gchar      *text)
+{
+       GtkTextIter begin;
+       GtkTextIter end;
+       gint diff = 0;
+
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET (self));
+       g_return_if_fail (nth >= 0);
+       g_return_if_fail (text);
+
+       /*
+        * This replaces the text for the snippet. We insert new text before
+        * we delete the old text to ensure things are more stable as we
+        * manipulate the runs. Avoiding zero-length runs, even temporarily
+        * can be helpful.
+        */
+
+       gtk_source_snippet_get_nth_chunk_range (self, nth, &begin, &end);
+
+       if (!gtk_text_iter_equal (&begin, &end))
+       {
+               gtk_text_iter_order (&begin, &end);
+               diff = gtk_text_iter_get_offset (&end) - gtk_text_iter_get_offset (&begin);
+       }
+
+       g_array_index (self->runs, gint, nth) += g_utf8_strlen (text, -1);
+       gtk_text_buffer_insert (self->buffer, &begin, text, -1);
+
+       /* At this point, begin should be updated to the end of where we inserted
+        * our new text. If `diff` is non-zero, then we need to remove those
+        * characters immediately after `begin`.
+        */
+       if (diff != 0)
+       {
+               end = begin;
+               gtk_text_iter_forward_chars (&end, diff);
+               g_array_index (self->runs, gint, nth) -= diff;
+               gtk_text_buffer_delete (self->buffer, &begin, &end);
+       }
+}
+
+static void
+gtk_source_snippet_rewrite_updated_chunks (GtkSourceSnippet *self)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET (self));
+
+       for (guint i = 0; i < self->chunks->len; i++)
+       {
+               GtkSourceSnippetChunk *chunk = g_ptr_array_index (self->chunks, i);
+               const gchar *text;
+               gchar *real_text;
+
+               text = gtk_source_snippet_chunk_get_text (chunk);
+               real_text = gtk_source_snippet_get_nth_text (self, i);
+
+               if (g_strcmp0 (text, real_text) != 0)
+               {
+                       gtk_source_snippet_replace_chunk_text (self, i, text);
+               }
+
+               g_free (real_text);
+       }
+}
+
+void
+_gtk_source_snippet_before_insert_text (GtkSourceSnippet *self,
+                                        GtkTextBuffer    *buffer,
+                                        GtkTextIter      *iter,
+                                        gchar            *text,
+                                        gint              len)
+{
+       gint utf8_len;
+       gint n;
+
+       g_return_if_fail (GTK_SOURCE_IS_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 = gtk_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
+_gtk_source_snippet_after_insert_text (GtkSourceSnippet *self,
+                                       GtkTextBuffer    *buffer,
+                                       GtkTextIter      *iter,
+                                       gchar            *text,
+                                       gint              len)
+{
+       GtkSourceSnippetChunk *chunk;
+       GtkTextMark *here;
+       gchar *new_text;
+       gint n;
+
+       g_return_if_fail (GTK_SOURCE_IS_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 = gtk_source_snippet_get_index (self, iter);
+       chunk = g_ptr_array_index (self->chunks, n);
+       new_text = gtk_source_snippet_get_nth_text (self, n);
+       gtk_source_snippet_chunk_set_text (chunk, new_text);
+       gtk_source_snippet_chunk_set_text_set (chunk, TRUE);
+       g_free (new_text);
+
+       here = gtk_text_buffer_create_mark (buffer, NULL, iter, TRUE);
+
+       gtk_source_snippet_update_context (self);
+       gtk_source_snippet_update_context (self);
+       gtk_source_snippet_rewrite_updated_chunks (self);
+
+       gtk_text_buffer_get_iter_at_mark (buffer, iter, here);
+       gtk_text_buffer_delete_mark (buffer, here);
+
+       gtk_source_snippet_update_tags (self);
+}
+
+void
+_gtk_source_snippet_before_delete_range (GtkSourceSnippet *self,
+                                         GtkTextBuffer    *buffer,
+                                         GtkTextIter      *begin,
+                                         GtkTextIter      *end)
+{
+       gint *run;
+       gint len;
+       gint n;
+       gint lower_bound = -1;
+       gint upper_bound = -1;
+
+       g_return_if_fail (GTK_SOURCE_IS_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 = gtk_source_snippet_get_index (self, begin);
+
+       if (n < 0)
+       {
+               return;
+       }
+
+       self->current_chunk = n;
+
+       while (len != 0 && (guint)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;
+
+               break;
+       }
+
+       if (lower_bound == -1 || upper_bound == -1)
+       {
+               return;
+       }
+
+       for (gint i = lower_bound; i <= upper_bound; i++)
+       {
+               GtkSourceSnippetChunk *chunk = g_ptr_array_index (self->chunks, i);
+               gchar *new_text;
+
+               new_text = gtk_source_snippet_get_nth_text (self, i);
+               gtk_source_snippet_chunk_set_text (chunk, new_text);
+               gtk_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
+_gtk_source_snippet_after_delete_range (GtkSourceSnippet *self,
+                                        GtkTextBuffer    *buffer,
+                                        GtkTextIter      *begin,
+                                        GtkTextIter      *end)
+{
+       GtkTextMark *here;
+
+       g_return_if_fail (GTK_SOURCE_IS_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);
+
+       gtk_source_snippet_update_context (self);
+       gtk_source_snippet_update_context (self);
+       gtk_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);
+
+       gtk_source_snippet_update_tags (self);
+}
+
+GtkTextMark *
+_gtk_source_snippet_get_mark_begin (GtkSourceSnippet *self)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET (self), NULL);
+
+       return self->mark_begin;
+}
+
+GtkTextMark *
+_gtk_source_snippet_get_mark_end (GtkSourceSnippet *self)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET (self), NULL);
+
+       return self->mark_end;
+}
+
+/**
+ * gtk_source_snippet_get_context:
+ * @self: an #GtkSourceSnippet
+ *
+ * Get's the context used for expanding the snippet.
+ *
+ * Returns: (nullable) (transfer none): an #GtkSourceSnippetContext
+ *
+ * Since: 5.0
+ */
+GtkSourceSnippetContext *
+gtk_source_snippet_get_context (GtkSourceSnippet *self)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET (self), NULL);
+
+       if (self->context == NULL)
+       {
+               GtkSourceSnippetChunk *chunk;
+
+               self->context = gtk_source_snippet_context_new ();
+
+               for (guint i = 0; i < self->chunks->len; i++)
+               {
+                       chunk = g_ptr_array_index (self->chunks, i);
+                       gtk_source_snippet_chunk_set_context (chunk, self->context);
+               }
+       }
+
+       return self->context;
+}
+
+static void
+gtk_source_snippet_dispose (GObject *object)
+{
+       GtkSourceSnippet *self = (GtkSourceSnippet *)object;
+
+       if (self->mark_begin != NULL)
+       {
+               gtk_text_buffer_delete_mark (self->buffer, self->mark_begin);
+               g_clear_object (&self->mark_begin);
+       }
+
+       if (self->mark_end != NULL)
+       {
+               gtk_text_buffer_delete_mark (self->buffer, self->mark_end);
+               g_clear_object (&self->mark_end);
+       }
+
+       g_clear_pointer (&self->runs, g_array_unref);
+       g_clear_pointer (&self->chunks, g_ptr_array_unref);
+
+       g_clear_object (&self->buffer);
+       g_clear_object (&self->context);
+
+       G_OBJECT_CLASS (gtk_source_snippet_parent_class)->dispose (object);
+}
+
+static void
+gtk_source_snippet_finalize (GObject *object)
+{
+       GtkSourceSnippet *self = (GtkSourceSnippet *)object;
+
+       g_clear_pointer (&self->description, g_free);
+       g_clear_pointer (&self->trigger, g_free);
+       g_clear_object (&self->buffer);
+
+       G_OBJECT_CLASS (gtk_source_snippet_parent_class)->finalize (object);
+}
+
+static void
+gtk_source_snippet_get_property (GObject    *object,
+                                 guint       prop_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+       GtkSourceSnippet *self = GTK_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_ID:
+               g_value_set_string (value, self->language_id);
+               break;
+
+       case PROP_DESCRIPTION:
+               g_value_set_string (value, self->description);
+               break;
+
+       case PROP_POSITION:
+               g_value_set_uint (value, self->focus_position);
+               break;
+
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       }
+}
+
+static void
+gtk_source_snippet_set_property (GObject      *object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+       GtkSourceSnippet *self = GTK_SOURCE_SNIPPET (object);
+
+       switch (prop_id)
+       {
+       case PROP_TRIGGER:
+               gtk_source_snippet_set_trigger (self, g_value_get_string (value));
+               break;
+
+       case PROP_LANGUAGE_ID:
+               gtk_source_snippet_set_language_id (self, g_value_get_string (value));
+               break;
+
+       case PROP_DESCRIPTION:
+               gtk_source_snippet_set_description (self, g_value_get_string (value));
+               break;
+
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       }
+}
+
+static void
+gtk_source_snippet_class_init (GtkSourceSnippetClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->dispose = gtk_source_snippet_dispose;
+       object_class->finalize = gtk_source_snippet_finalize;
+       object_class->get_property = gtk_source_snippet_get_property;
+       object_class->set_property = gtk_source_snippet_set_property;
+
+       properties[PROP_BUFFER] =
+               g_param_spec_object ("buffer",
+                                    "Buffer",
+                                    "The GtkTextBuffer for the snippet",
+                                    GTK_TYPE_TEXT_BUFFER,
+                                    (G_PARAM_READABLE |
+                                     G_PARAM_EXPLICIT_NOTIFY |
+                                     G_PARAM_STATIC_STRINGS));
+
+       properties[PROP_MARK_BEGIN] =
+               g_param_spec_object ("mark-begin",
+                                    "Mark Begin",
+                                    "The beginning text mark",
+                                    GTK_TYPE_TEXT_MARK,
+                                    (G_PARAM_READABLE |
+                                     G_PARAM_EXPLICIT_NOTIFY |
+                                     G_PARAM_STATIC_STRINGS));
+
+       properties[PROP_MARK_END] =
+               g_param_spec_object ("mark-end",
+                                    "Mark End",
+                                    "The ending text mark",
+                                    GTK_TYPE_TEXT_MARK,
+                                    (G_PARAM_READABLE |
+                                     G_PARAM_EXPLICIT_NOTIFY |
+                                     G_PARAM_STATIC_STRINGS));
+
+       properties[PROP_TRIGGER] =
+               g_param_spec_string ("trigger",
+                                    "Trigger",
+                                    "The trigger for the snippet",
+                                    NULL,
+                                    (G_PARAM_READWRITE |
+                                     G_PARAM_EXPLICIT_NOTIFY |
+                                     G_PARAM_STATIC_STRINGS));
+
+       properties[PROP_LANGUAGE_ID] =
+               g_param_spec_string ("language-id",
+                                    "Language Id",
+                                    "The language-id for the snippet",
+                                    NULL,
+                                    (G_PARAM_READWRITE |
+                                     G_PARAM_EXPLICIT_NOTIFY |
+                                     G_PARAM_STATIC_STRINGS));
+
+       properties[PROP_DESCRIPTION] =
+               g_param_spec_string ("description",
+                                    "Description",
+                                    "The description for the snippet",
+                                    NULL,
+                                    (G_PARAM_READWRITE |
+                                     G_PARAM_EXPLICIT_NOTIFY |
+                                     G_PARAM_STATIC_STRINGS));
+
+       properties[PROP_POSITION] =
+               g_param_spec_int ("position",
+                                 "Position",
+                                 "The current position",
+                                 -1,
+                                 G_MAXINT,
+                                 -1,
+                                 (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gtk_source_snippet_init (GtkSourceSnippet *self)
+{
+       self->max_focus_position = -1;
+       self->chunks = g_ptr_array_new_with_free_func (g_object_unref);
+       self->runs = g_array_new (FALSE, FALSE, sizeof (gint));
+}
+
+gchar *
+_gtk_source_snippet_get_edited_text (GtkSourceSnippet *self)
+{
+       GtkTextIter begin;
+       GtkTextIter end;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET (self), NULL);
+
+       if (self->mark_begin == NULL || self->mark_end == NULL)
+       {
+               return NULL;
+       }
+
+       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);
+
+       return gtk_text_iter_get_slice (&begin, &end);
+}
+
+void
+_gtk_source_snippet_replace_current_chunk_text (GtkSourceSnippet *self,
+                                                const gchar      *new_text)
+{
+       GtkSourceSnippetChunk *chunk;
+       gint utf8_len;
+
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET (self));
+       g_return_if_fail (self->chunks != NULL);
+
+       if (self->current_chunk < 0 || self->current_chunk >= self->chunks->len)
+       {
+               return;
+       }
+
+       chunk = g_ptr_array_index (self->chunks, self->current_chunk);
+
+       gtk_source_snippet_chunk_set_text (chunk, new_text);
+       gtk_source_snippet_chunk_set_text_set (chunk, TRUE);
+
+       g_assert (self->current_chunk >= 0);
+       g_assert (self->current_chunk < self->runs->len);
+
+       utf8_len = g_utf8_strlen (new_text, -1);
+       g_array_index (self->runs, gint, self->current_chunk) = utf8_len;
+}
diff --git a/gtksourceview/gtksourcesnippet.h b/gtksourceview/gtksourcesnippet.h
new file mode 100644
index 00000000..85588023
--- /dev/null
+++ b/gtksourceview/gtksourcesnippet.h
@@ -0,0 +1,70 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION)
+#error "Only <gtksourceview/gtksource.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+
+#include "gtksourcetypes.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_SNIPPET (gtk_source_snippet_get_type())
+
+GTK_SOURCE_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (GtkSourceSnippet, gtk_source_snippet, GTK_SOURCE, SNIPPET, GObject)
+
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkSourceSnippet        *gtk_source_snippet_new                        (const gchar           *trigger,
+                                                                        const gchar           *language_id);
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkSourceSnippet        *gtk_source_snippet_copy                       (GtkSourceSnippet      *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+const gchar             *gtk_source_snippet_get_trigger                (GtkSourceSnippet      *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void                     gtk_source_snippet_set_trigger                (GtkSourceSnippet      *self,
+                                                                        const gchar           *trigger);
+GTK_SOURCE_AVAILABLE_IN_5_0
+const gchar             *gtk_source_snippet_get_language_id            (GtkSourceSnippet      *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void                     gtk_source_snippet_set_language_id            (GtkSourceSnippet      *self,
+                                                                        const gchar           *language_id);
+GTK_SOURCE_AVAILABLE_IN_5_0
+const gchar             *gtk_source_snippet_get_description            (GtkSourceSnippet      *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void                     gtk_source_snippet_set_description            (GtkSourceSnippet      *self,
+                                                                        const gchar           *description);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void                     gtk_source_snippet_add_chunk                  (GtkSourceSnippet      *self,
+                                                                        GtkSourceSnippetChunk *chunk);
+GTK_SOURCE_AVAILABLE_IN_5_0
+guint                    gtk_source_snippet_get_n_chunks               (GtkSourceSnippet      *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+gint                     gtk_source_snippet_get_focus_position         (GtkSourceSnippet      *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkSourceSnippetChunk   *gtk_source_snippet_get_nth_chunk              (GtkSourceSnippet      *self,
+                                                                        guint                  nth);
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkSourceSnippetContext *gtk_source_snippet_get_context                (GtkSourceSnippet      *self);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcesnippetchunk.c b/gtksourceview/gtksourcesnippetchunk.c
new file mode 100644
index 00000000..4b58854a
--- /dev/null
+++ b/gtksourceview/gtksourcesnippetchunk.c
@@ -0,0 +1,402 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtksourcesnippetchunk.h"
+#include "gtksourcesnippetcontext.h"
+
+/**
+ * SECTION:snippetchunk
+ * @title: GtkSourceSnippetChunk
+ * @short_description: An chunk of text within the source snippet
+ *
+ * The #GtkSourceSnippetChunk represents a single chunk of text that
+ * may or may not be an edit point within the snippet. Chunks that are
+ * an edit point (also called a tab stop) have the
+ * #GtkSourceSnippetChunk:focus-position property set.
+ *
+ * Since: 5.0
+ */
+
+struct _GtkSourceSnippetChunk
+{
+       GObject                  parent_instance;
+
+       GtkSourceSnippetContext *context;
+       gulong                   context_changed_handler;
+       gint                     focus_position;
+       gchar                   *spec;
+       gchar                   *text;
+
+       guint                    text_set : 1;
+};
+
+G_DEFINE_TYPE (GtkSourceSnippetChunk, gtk_source_snippet_chunk, G_TYPE_OBJECT)
+
+enum {
+       PROP_0,
+       PROP_CONTEXT,
+       PROP_SPEC,
+       PROP_FOCUS_POSITION,
+       PROP_TEXT,
+       PROP_TEXT_SET,
+       N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+/**
+ * gtk_source_snippet_chunk_new:
+ *
+ * Create a new #GtkSourceSnippetChunk that can be added to
+ * a #GtkSourceSnippet.
+ *
+ * Since: 5.0
+ */
+GtkSourceSnippetChunk *
+gtk_source_snippet_chunk_new (void)
+{
+       return g_object_new (GTK_SOURCE_TYPE_SNIPPET_CHUNK, NULL);
+}
+
+/**
+ * gtk_source_snippet_chunk_copy:
+ *
+ * Copies the source snippet.
+ *
+ * Returns: (transfer full): An #GtkSourceSnippetChunk.
+ *
+ * Since: 5.0
+ */
+GtkSourceSnippetChunk *
+gtk_source_snippet_chunk_copy (GtkSourceSnippetChunk *chunk)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk), NULL);
+
+       return g_object_new (GTK_SOURCE_TYPE_SNIPPET_CHUNK,
+                            "spec", chunk->spec,
+                            "focus-position", chunk->focus_position,
+                            NULL);
+}
+
+static void
+on_context_changed (GtkSourceSnippetContext *context,
+                    GtkSourceSnippetChunk   *chunk)
+{
+       gchar *text;
+
+       g_assert (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk));
+       g_assert (GTK_SOURCE_IS_SNIPPET_CONTEXT (context));
+
+       if (!chunk->text_set)
+       {
+               text = gtk_source_snippet_context_expand (context, chunk->spec);
+               gtk_source_snippet_chunk_set_text (chunk, text);
+               g_free (text);
+       }
+}
+
+/**
+ * gtk_source_snippet_chunk_get_context:
+ *
+ * Gets the context for the snippet insertion.
+ *
+ * Returns: (transfer none): An #GtkSourceSnippetContext.
+ *
+ * Since: 5.0
+ */
+GtkSourceSnippetContext *
+gtk_source_snippet_chunk_get_context (GtkSourceSnippetChunk *chunk)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk), NULL);
+
+       return chunk->context;
+}
+
+void
+gtk_source_snippet_chunk_set_context (GtkSourceSnippetChunk   *chunk,
+                                      GtkSourceSnippetContext *context)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk));
+       g_return_if_fail (!context || GTK_SOURCE_IS_SNIPPET_CONTEXT (context));
+
+       if (context != chunk->context)
+       {
+               g_clear_signal_handler (&chunk->context_changed_handler,
+                                       chunk->context);
+               g_clear_object (&chunk->context);
+
+               if (context != NULL)
+               {
+                       chunk->context = g_object_ref (context);
+                       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),
+                                         properties [PROP_CONTEXT]);
+       }
+}
+
+const gchar *
+gtk_source_snippet_chunk_get_spec (GtkSourceSnippetChunk *chunk)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk), NULL);
+
+       return chunk->spec;
+}
+
+void
+gtk_source_snippet_chunk_set_spec (GtkSourceSnippetChunk *chunk,
+                                   const gchar           *spec)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk));
+
+       if (g_strcmp0 (spec, chunk->spec) != 0)
+       {
+               g_free (chunk->spec);
+               chunk->spec = g_strdup (spec);
+               g_object_notify_by_pspec (G_OBJECT (chunk),
+                                         properties [PROP_SPEC]);
+       }
+}
+
+gint
+gtk_source_snippet_chunk_get_focus_position (GtkSourceSnippetChunk *chunk)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk), 0);
+
+       return chunk->focus_position;
+}
+
+void
+gtk_source_snippet_chunk_set_focus_position (GtkSourceSnippetChunk *chunk,
+                                             gint                   focus_position)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk));
+
+       focus_position = MAX (focus_position, -1);
+
+       if (chunk->focus_position != focus_position)
+       {
+               chunk->focus_position = focus_position;
+               g_object_notify_by_pspec (G_OBJECT (chunk),
+                                         properties [PROP_FOCUS_POSITION]);
+       }
+}
+
+const gchar *
+gtk_source_snippet_chunk_get_text (GtkSourceSnippetChunk *chunk)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk), NULL);
+
+       return chunk->text ? chunk->text : "";
+}
+
+void
+gtk_source_snippet_chunk_set_text (GtkSourceSnippetChunk *chunk,
+                                   const gchar           *text)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk));
+
+       if (g_strcmp0 (chunk->text, text) != 0)
+       {
+               g_free (chunk->text);
+               chunk->text = g_strdup (text);
+               g_object_notify_by_pspec (G_OBJECT (chunk),
+                                         properties [PROP_TEXT]);
+       }
+}
+
+gboolean
+gtk_source_snippet_chunk_get_text_set (GtkSourceSnippetChunk *chunk)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk), FALSE);
+
+       return chunk->text_set;
+}
+
+void
+gtk_source_snippet_chunk_set_text_set (GtkSourceSnippetChunk *chunk,
+                                       gboolean               text_set)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET_CHUNK (chunk));
+
+       text_set = !!text_set;
+
+       if (chunk->text_set != text_set)
+       {
+               chunk->text_set = text_set;
+               g_object_notify_by_pspec (G_OBJECT (chunk),
+                                         properties [PROP_TEXT_SET]);
+       }
+}
+
+static void
+gtk_source_snippet_chunk_finalize (GObject *object)
+{
+       GtkSourceSnippetChunk *chunk = (GtkSourceSnippetChunk *)object;
+
+       g_clear_pointer (&chunk->spec, g_free);
+       g_clear_pointer (&chunk->text, g_free);
+       g_clear_object (&chunk->context);
+
+       G_OBJECT_CLASS (gtk_source_snippet_chunk_parent_class)->finalize (object);
+}
+
+static void
+gtk_source_snippet_chunk_get_property (GObject    *object,
+                                       guint       prop_id,
+                                       GValue     *value,
+                                       GParamSpec *pspec)
+{
+       GtkSourceSnippetChunk *chunk = GTK_SOURCE_SNIPPET_CHUNK (object);
+
+       switch (prop_id)
+       {
+       case PROP_CONTEXT:
+               g_value_set_object (value, gtk_source_snippet_chunk_get_context (chunk));
+               break;
+
+       case PROP_SPEC:
+               g_value_set_string (value, gtk_source_snippet_chunk_get_spec (chunk));
+               break;
+
+       case PROP_FOCUS_POSITION:
+               g_value_set_int (value, gtk_source_snippet_chunk_get_focus_position (chunk));
+               break;
+
+       case PROP_TEXT:
+               g_value_set_string (value, gtk_source_snippet_chunk_get_text (chunk));
+               break;
+
+       case PROP_TEXT_SET:
+               g_value_set_boolean (value, gtk_source_snippet_chunk_get_text_set (chunk));
+               break;
+
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+       }
+}
+
+static void
+gtk_source_snippet_chunk_set_property (GObject      *object,
+                                       guint         prop_id,
+                                       const GValue *value,
+                                       GParamSpec   *pspec)
+{
+       GtkSourceSnippetChunk *chunk = GTK_SOURCE_SNIPPET_CHUNK (object);
+
+       switch (prop_id)
+       {
+       case PROP_CONTEXT:
+               gtk_source_snippet_chunk_set_context (chunk, g_value_get_object (value));
+               break;
+
+       case PROP_FOCUS_POSITION:
+               gtk_source_snippet_chunk_set_focus_position (chunk, g_value_get_int (value));
+               break;
+
+       case PROP_SPEC:
+               gtk_source_snippet_chunk_set_spec (chunk, g_value_get_string (value));
+               break;
+
+       case PROP_TEXT:
+               gtk_source_snippet_chunk_set_text (chunk, g_value_get_string (value));
+               break;
+
+       case PROP_TEXT_SET:
+               gtk_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
+gtk_source_snippet_chunk_class_init (GtkSourceSnippetChunkClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = gtk_source_snippet_chunk_finalize;
+       object_class->get_property = gtk_source_snippet_chunk_get_property;
+       object_class->set_property = gtk_source_snippet_chunk_set_property;
+
+       properties[PROP_CONTEXT] =
+               g_param_spec_object ("context",
+                                    "Context",
+                                    "The snippet context.",
+                                    GTK_SOURCE_TYPE_SNIPPET_CONTEXT,
+                                    (G_PARAM_READWRITE |
+                                     G_PARAM_EXPLICIT_NOTIFY |
+                                     G_PARAM_STATIC_STRINGS));
+
+       properties[PROP_SPEC] =
+               g_param_spec_string ("spec",
+                                    "Spec",
+                                    "The specification to expand using the context.",
+                                    NULL,
+                                    (G_PARAM_READWRITE |
+                                     G_PARAM_EXPLICIT_NOTIFY |
+                                     G_PARAM_STATIC_STRINGS));
+
+       properties[PROP_FOCUS_POSITION] =
+               g_param_spec_int ("focus-position",
+                                 "Focus Position",
+                                 "The focus position for the chunk.",
+                                 -1,
+                                 G_MAXINT,
+                                 -1,
+                                 (G_PARAM_READWRITE |
+                                  G_PARAM_EXPLICIT_NOTIFY |
+                                  G_PARAM_STATIC_STRINGS));
+
+       properties[PROP_TEXT] =
+               g_param_spec_string ("text",
+                                    "Text",
+                                    "The text for the chunk.",
+                                    NULL,
+                                    (G_PARAM_READWRITE |
+                                     G_PARAM_EXPLICIT_NOTIFY |
+                                     G_PARAM_STATIC_STRINGS));
+
+       properties[PROP_TEXT_SET] =
+               g_param_spec_boolean ("text-set",
+                                     "If text property is set",
+                                     "If the text property has been manually set.",
+                                     FALSE,
+                                     (G_PARAM_READWRITE |
+                                      G_PARAM_EXPLICIT_NOTIFY |
+                                      G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gtk_source_snippet_chunk_init (GtkSourceSnippetChunk *chunk)
+{
+       chunk->focus_position = -1;
+       chunk->spec = g_strdup ("");
+}
diff --git a/gtksourceview/gtksourcesnippetchunk.h b/gtksourceview/gtksourcesnippetchunk.h
new file mode 100644
index 00000000..460478e8
--- /dev/null
+++ b/gtksourceview/gtksourcesnippetchunk.h
@@ -0,0 +1,67 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION)
+#error "Only <gtksourceview/gtksource.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+
+#include "gtksourcetypes.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_SNIPPET_CHUNK (gtk_source_snippet_chunk_get_type())
+
+GTK_SOURCE_AVAILABLE_IN_5_0
+G_DECLARE_FINAL_TYPE (GtkSourceSnippetChunk, gtk_source_snippet_chunk, GTK_SOURCE, SNIPPET_CHUNK, GObject)
+
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkSourceSnippetChunk   *gtk_source_snippet_chunk_new                (void);
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkSourceSnippetChunk   *gtk_source_snippet_chunk_copy               (GtkSourceSnippetChunk   *chunk);
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkSourceSnippetContext *gtk_source_snippet_chunk_get_context        (GtkSourceSnippetChunk   *chunk);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void                     gtk_source_snippet_chunk_set_context        (GtkSourceSnippetChunk   *chunk,
+                                                                      GtkSourceSnippetContext *context);
+GTK_SOURCE_AVAILABLE_IN_5_0
+const gchar             *gtk_source_snippet_chunk_get_spec           (GtkSourceSnippetChunk   *chunk);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void                     gtk_source_snippet_chunk_set_spec           (GtkSourceSnippetChunk   *chunk,
+                                                                      const gchar             *spec);
+GTK_SOURCE_AVAILABLE_IN_5_0
+gint                     gtk_source_snippet_chunk_get_focus_position (GtkSourceSnippetChunk   *chunk);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void                     gtk_source_snippet_chunk_set_focus_position (GtkSourceSnippetChunk   *chunk,
+                                                                      gint                     position);
+GTK_SOURCE_AVAILABLE_IN_5_0
+const gchar             *gtk_source_snippet_chunk_get_text           (GtkSourceSnippetChunk   *chunk);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void                     gtk_source_snippet_chunk_set_text           (GtkSourceSnippetChunk   *chunk,
+                                                                      const gchar             *text);
+GTK_SOURCE_AVAILABLE_IN_5_0
+gboolean                 gtk_source_snippet_chunk_get_text_set       (GtkSourceSnippetChunk   *chunk);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void                     gtk_source_snippet_chunk_set_text_set       (GtkSourceSnippetChunk   *chunk,
+                                                                      gboolean                 text_set);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcesnippetcontext-private.h b/gtksourceview/gtksourcesnippetcontext-private.h
new file mode 100644
index 00000000..8fb734ca
--- /dev/null
+++ b/gtksourceview/gtksourcesnippetcontext-private.h
@@ -0,0 +1,29 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "gtksourcesnippetcontext.h"
+
+G_BEGIN_DECLS
+
+G_GNUC_INTERNAL
+void _gtk_source_snippet_context_emit_changed (GtkSourceSnippetContext *context);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcesnippetcontext.c b/gtksourceview/gtksourcesnippetcontext.c
new file mode 100644
index 00000000..dacd517a
--- /dev/null
+++ b/gtksourceview/gtksourcesnippetcontext.c
@@ -0,0 +1,911 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <glib/gi18n.h>
+#include <stdlib.h>
+
+#include "gtksourcesnippetcontext-private.h"
+
+/**
+ * SECTION:snippetcontext
+ * @title: GtkSourceSnippetContext
+ * @short_description: Context for expanding #GtkSourceSnippetChunk
+ *
+ * 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 #GtkSourceSnippetChunk can expand themselves by executing
+ * script within the context.
+ *
+ * The #GtkSourceSnippet will build the context and then expand each of the
+ * chunks during the insertion/edit phase.
+ *
+ * Since: 5.0
+ */
+
+struct _GtkSourceSnippetContext
+{
+  GObject     parent_instance;
+
+  GHashTable *constants;
+  GHashTable *variables;
+  gchar      *line_prefix;
+  gint        tab_width;
+  guint       use_spaces : 1;
+};
+
+struct _GtkSourceSnippetContextClass
+{
+  GObjectClass parent;
+};
+
+G_DEFINE_TYPE (GtkSourceSnippetContext, gtk_source_snippet_context, G_TYPE_OBJECT)
+
+enum {
+  CHANGED,
+  N_SIGNALS
+};
+
+typedef gchar *(*InputFilter) (const gchar *input);
+
+static GHashTable *filters;
+static guint signals[N_SIGNALS];
+
+/**
+ * gtk_source_snippet_context_new:
+ *
+ * Creates a new #GtkSourceSnippetContext.
+ *
+ * Generally, this isn't needed unless you are controlling the
+ * expansion of snippets manually.
+ *
+ * Returns: (transfer full): a #GtkSourceSnippetContext
+ *
+ * Since: 5.0
+ */
+GtkSourceSnippetContext *
+gtk_source_snippet_context_new (void)
+{
+       return g_object_new (GTK_SOURCE_TYPE_SNIPPET_CONTEXT, NULL);
+}
+
+/**
+ * gtk_source_snippet_context_clear_variables:
+ * @self: a #GtkSourceSnippetContext
+ *
+ * Removes all variables from the context.
+ *
+ * Since: 5.0
+ */
+void
+gtk_source_snippet_context_clear_variables (GtkSourceSnippetContext *self)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET_CONTEXT (self));
+
+       g_hash_table_remove_all (self->variables);
+}
+
+/**
+ * gtk_source_snippet_context_set_variable:
+ * @self: a #GtkSourceSnippetContext
+ * @key: the variable name
+ * @value: the value for the variable
+ *
+ * Sets a variable within the context.
+ *
+ * This variable may be overridden by future updates to the
+ * context.
+ *
+ * Since: 5.0
+ */
+void
+gtk_source_snippet_context_set_variable (GtkSourceSnippetContext *self,
+                                         const gchar             *key,
+                                         const gchar             *value)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET_CONTEXT (self));
+       g_return_if_fail (key);
+
+       g_hash_table_replace (self->variables, g_strdup (key), g_strdup (value));
+}
+
+/**
+ * gtk_source_snippet_context_set_constant:
+ * @self: a #GtkSourceSnippetContext
+ * @key: the constant name
+ * @value: the value of the constant
+ *
+ * Sets a constatnt within the context. This is similar to
+ * a variable set with gtk_source_snippet_context_set_variable()
+ * but is expected to not change during use of the snippet.
+ *
+ * Examples would be the date or users name.
+ *
+ * Since: 5.0
+ */
+void
+gtk_source_snippet_context_set_constant (GtkSourceSnippetContext *self,
+                                         const gchar             *key,
+                                         const gchar             *value)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET_CONTEXT (self));
+       g_return_if_fail (key);
+
+       g_hash_table_replace (self->constants, g_strdup (key), g_strdup (value));
+}
+
+/**
+ * gtk_source_snippet_context_get_variable:
+ * @self: a #GtkSourceSnippetContext
+ * @key: the name of the variable
+ *
+ * Gets the current value for a variable named @key.
+ *
+ * Returns: (transfer none) (nullable): the value for the variable, or %NULL
+ *
+ * Since: 5.0
+ */
+const gchar *
+gtk_source_snippet_context_get_variable (GtkSourceSnippetContext *self,
+                                         const gchar             *key)
+{
+       const gchar *ret;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET_CONTEXT (self), NULL);
+
+       if (!(ret = g_hash_table_lookup (self->variables, key)))
+               ret = g_hash_table_lookup (self->constants, key);
+
+       return ret;
+}
+
+static gchar *
+filter_lower (const gchar *input)
+{
+       return input != NULL ? g_utf8_strdown (input, -1) : NULL;
+}
+
+static gchar *
+filter_upper (const gchar *input)
+{
+       return input != NULL ? g_utf8_strup (input, -1) : NULL;
+}
+
+static gchar *
+filter_capitalize (const gchar *input)
+{
+       gunichar c;
+       GString *str;
+
+       if (input == NULL)
+               return NULL;
+
+       if (*input == 0)
+               return g_strdup ("");
+
+       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));
+       if (*input)
+               g_string_append (str, input);
+
+       return g_string_free (str, FALSE);
+}
+
+static gchar *
+filter_uncapitalize (const gchar *input)
+{
+       gunichar c;
+       GString *str;
+
+       if (input == NULL)
+               return NULL;
+
+       c = g_utf8_get_char (input);
+       if (g_unichar_islower (c))
+               return g_strdup (input);
+
+       str = g_string_new (NULL);
+       input = g_utf8_next_char (input);
+       g_string_append_unichar (str, g_unichar_tolower (c));
+       g_string_append (str, input);
+
+       return g_string_free (str, FALSE);
+}
+
+static gchar *
+filter_html (const gchar *input)
+{
+       GString *str;
+
+       if (input == NULL)
+               return NULL;
+
+       str = g_string_new (NULL);
+
+       for (; *input; input = g_utf8_next_char (input))
+       {
+               gunichar 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;
+
+               case '&':
+                       g_string_append_len (str, "&amp;", 5);
+                       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;
+       GString *str;
+
+       if (input == NULL)
+               return NULL;
+
+       if (!strchr (input, '_') && !strchr (input, ' ') && !strchr (input, '-'))
+               return filter_capitalize (input);
+
+       str = g_string_new (NULL);
+
+       for (; *input; input = g_utf8_next_char (input))
+       {
+               gunichar 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);
+       }
+
+       if (g_str_has_suffix (str->str, "Private"))
+               g_string_truncate (str, str->len - strlen ("Private"));
+
+       return g_string_free (str, FALSE);
+}
+
+static gchar *
+filter_functify (const gchar *input)
+{
+       gunichar last = 0;
+       GString *str;
+
+       if (input == NULL)
+               return NULL;
+
+       str = g_string_new (NULL);
+
+       for (; *input; input = g_utf8_next_char (input))
+       {
+               gunichar c = g_utf8_get_char (input);
+               gunichar 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;
+       }
+
+       if (g_str_has_suffix (str->str, "_private") ||
+           g_str_has_suffix (str->str, "_PRIVATE"))
+       {
+               g_string_truncate (str, str->len - strlen ("_private"));
+       }
+
+       return g_string_free (str, FALSE);
+}
+
+static gchar *
+filter_namespace (const gchar *input)
+{
+       gunichar last = 0;
+       GString *str;
+       gboolean first_is_lower = FALSE;
+
+       if (input == NULL)
+               return NULL;
+
+       str = g_string_new (NULL);
+
+       for (; *input; input = g_utf8_next_char (input))
+       {
+               gunichar c = g_utf8_get_char (input);
+               gunichar 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 g_steal_pointer (&ret);
+       }
+
+       return g_string_free (str, FALSE);
+}
+
+static gchar *
+filter_class (const gchar *input)
+{
+       gchar *camel;
+       gchar *ns;
+       gchar *ret = NULL;
+
+       if (input == NULL)
+               return 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 (input == NULL)
+               return NULL;
+
+       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 g_steal_pointer (&ret);
+}
+
+static gchar *
+filter_space (const gchar *input)
+{
+       GString *str;
+
+       if (input == NULL)
+               return NULL;
+
+       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_descend_path (const gchar *input)
+{
+       const gchar *pos;
+
+       if (input == NULL)
+               return NULL;
+
+       while (*input == G_DIR_SEPARATOR)
+       {
+               input++;
+       }
+
+       if ((pos = strchr (input, G_DIR_SEPARATOR)))
+       {
+               return g_strdup (pos + 1);
+       }
+
+       return NULL;
+}
+
+static gchar *
+filter_stripsuffix (const gchar *input)
+{
+       const gchar *endpos;
+
+       if (input == NULL)
+               return NULL;
+
+       endpos = strrchr (input, '.');
+
+       if (endpos != NULL)
+       {
+               return g_strndup (input, (endpos - input));
+       }
+
+       return g_strdup (input);
+}
+
+static gchar *
+filter_slash_to_dots (const gchar *input)
+{
+       GString *str;
+
+       if (input == NULL)
+               return NULL;
+
+       str = g_string_new (NULL);
+
+       for (; *input; input = g_utf8_next_char (input))
+       {
+               gunichar ch = g_utf8_get_char (input);
+
+               if (ch == G_DIR_SEPARATOR)
+               {
+                       g_string_append_c (str, '.');
+               }
+               else
+               {
+                       g_string_append_unichar (str, ch);
+               }
+       }
+
+       return g_string_free (str, FALSE);
+}
+
+static gchar *
+apply_filter (gchar       *input,
+              const gchar *filter)
+{
+       InputFilter filter_func;
+
+       if ((filter_func = g_hash_table_lookup (filters, filter)))
+       {
+               gchar *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);
+
+       filter_names = g_strsplit (filters_list, "|", 0);
+       for (guint 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 *
+gtk_source_snippet_context_expand (GtkSourceSnippetContext *self,
+                                   const gchar             *input)
+{
+       const gchar *expand;
+       gboolean is_dynamic;
+       GString *str;
+       gchar key[12];
+       glong n;
+       gint i;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_SNIPPET_CONTEXT (self), NULL);
+       g_return_val_if_fail (input, NULL);
+
+       is_dynamic = (*input == '$');
+
+       str = g_string_new (NULL);
+
+       for (; *input; input = g_utf8_next_char (input))
+       {
+               gunichar 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 = gtk_source_snippet_context_get_variable (self, key);
+                               if (expand)
+                                       g_string_append (str, expand);
+                               continue;
+                       }
+                       else
+                       {
+                               if (strchr (input, '|'))
+                               {
+                                       gchar *lkey;
+
+                                       lkey = g_strndup (input, strchr (input, '|') - input);
+                                       expand = gtk_source_snippet_context_get_variable (self, lkey);
+                                       g_free (lkey);
+
+                                       if (expand)
+                                       {
+                                               g_string_append (str, expand);
+                                               input = strchr (input, '|') - 1;
+                                       }
+                                       else
+                                       {
+                                               input += strlen (input) - 1;
+                                       }
+                               }
+                               else
+                               {
+                                       expand = gtk_source_snippet_context_get_variable (self, 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 = gtk_source_snippet_context_expand (self, slice);
+
+                               g_string_append (str, expanded);
+
+                               g_free (expanded);
+                               g_free (slice);
+
+                               continue;
+                       }
+               }
+               else if (c == '\t')
+               {
+                       if (self->use_spaces)
+                       {
+                               for (i = 0; i < self->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 (self->line_prefix)
+                       {
+                               g_string_append (str, self->line_prefix);
+                       }
+
+                       continue;
+               }
+
+               g_string_append_unichar (str, c);
+       }
+
+       return g_string_free (str, FALSE);
+}
+
+void
+gtk_source_snippet_context_set_tab_width (GtkSourceSnippetContext *self,
+                                          gint                     tab_width)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET_CONTEXT (self));
+
+       if (tab_width != self->tab_width)
+       {
+               self->tab_width = tab_width;
+       }
+}
+
+void
+gtk_source_snippet_context_set_use_spaces (GtkSourceSnippetContext *self,
+                                           gboolean                 use_spaces)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET_CONTEXT (self));
+
+       use_spaces = !!use_spaces;
+
+       if (self->use_spaces != use_spaces)
+       {
+               self->use_spaces = use_spaces;
+       }
+}
+
+void
+gtk_source_snippet_context_set_line_prefix (GtkSourceSnippetContext *self,
+                                            const gchar             *line_prefix)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET_CONTEXT (self));
+
+       if (g_strcmp0 (line_prefix, self->line_prefix) != 0)
+       {
+               g_free (self->line_prefix);
+               self->line_prefix = g_strdup (line_prefix);
+       }
+}
+
+void
+_gtk_source_snippet_context_emit_changed (GtkSourceSnippetContext *self)
+{
+       g_return_if_fail (GTK_SOURCE_IS_SNIPPET_CONTEXT (self));
+
+       g_signal_emit (self, signals [CHANGED], 0);
+}
+
+static void
+gtk_source_snippet_context_finalize (GObject *object)
+{
+       GtkSourceSnippetContext *self = (GtkSourceSnippetContext *)object;
+
+       g_clear_pointer (&self->constants, g_hash_table_unref);
+       g_clear_pointer (&self->variables, g_hash_table_unref);
+       g_clear_pointer (&self->line_prefix, g_free);
+
+       G_OBJECT_CLASS (gtk_source_snippet_context_parent_class)->finalize (object);
+}
+
+static void
+gtk_source_snippet_context_class_init (GtkSourceSnippetContextClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = gtk_source_snippet_context_finalize;
+
+       /**
+        * GtkSourceSnippetContext::changed:
+        *
+        * The "changed" signal is emitted when a change has been
+        * discovered in one of the chunks of the snippet which has
+        * caused a variable or other dynamic data within the context
+        * to have changed.
+        *
+        * Since: 5.0
+        */
+       signals[CHANGED] =
+               g_signal_new ("changed",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_FIRST,
+                             0,
+                             NULL, NULL, NULL,
+                             G_TYPE_NONE,
+                             0);
+
+       filters = g_hash_table_new (g_str_hash, g_str_equal);
+       g_hash_table_insert (filters, (gpointer) "lower", filter_lower);
+       g_hash_table_insert (filters, (gpointer) "upper", filter_upper);
+       g_hash_table_insert (filters, (gpointer) "capitalize", filter_capitalize);
+       g_hash_table_insert (filters, (gpointer) "decapitalize", filter_uncapitalize);
+       g_hash_table_insert (filters, (gpointer) "uncapitalize", filter_uncapitalize);
+       g_hash_table_insert (filters, (gpointer) "html", filter_html);
+       g_hash_table_insert (filters, (gpointer) "camelize", filter_camelize);
+       g_hash_table_insert (filters, (gpointer) "functify", filter_functify);
+       g_hash_table_insert (filters, (gpointer) "namespace", filter_namespace);
+       g_hash_table_insert (filters, (gpointer) "class", filter_class);
+       g_hash_table_insert (filters, (gpointer) "space", filter_space);
+       g_hash_table_insert (filters, (gpointer) "stripsuffix", filter_stripsuffix);
+       g_hash_table_insert (filters, (gpointer) "instance", filter_instance);
+       g_hash_table_insert (filters, (gpointer) "slash_to_dots", filter_slash_to_dots);
+       g_hash_table_insert (filters, (gpointer) "descend_path", filter_descend_path);
+}
+
+static void
+gtk_source_snippet_context_init (GtkSourceSnippetContext *self)
+{
+       GDateTime *dt;
+       gchar *str;
+
+       self->variables = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+       self->constants = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+#define ADD_CONSTANT(k, v) \
+       g_hash_table_insert (self->constants, g_strdup (k), g_strdup (v))
+
+       ADD_CONSTANT ("username", g_get_user_name ());
+       ADD_CONSTANT ("fullname", g_get_real_name ());
+       ADD_CONSTANT ("author", g_get_real_name ());
+
+       dt = g_date_time_new_now_local ();
+       str = g_date_time_format (dt, "%Y");
+       ADD_CONSTANT ("year", str);
+       g_free (str);
+       str = g_date_time_format (dt, "%b");
+       ADD_CONSTANT ("shortmonth", str);
+       g_free (str);
+       str = g_date_time_format (dt, "%d");
+       ADD_CONSTANT ("day", str);
+       g_free (str);
+       str = g_date_time_format (dt, "%a");
+       ADD_CONSTANT ("shortweekday", str);
+       g_free (str);
+       g_date_time_unref (dt);
+
+       ADD_CONSTANT ("email", "unknown@localhost");
+
+#undef ADD_CONSTANT
+}
diff --git a/gtksourceview/gtksourcesnippetcontext.h b/gtksourceview/gtksourcesnippetcontext.h
new file mode 100644
index 00000000..14e569d6
--- /dev/null
+++ b/gtksourceview/gtksourcesnippetcontext.h
@@ -0,0 +1,65 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#if !defined (GTK_SOURCE_H_INSIDE) && !defined (GTK_SOURCE_COMPILATION)
+#error "Only <gtksourceview/gtksource.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+
+#include "gtksourcetypes.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_SNIPPET_CONTEXT (gtk_source_snippet_context_get_type())
+
+GTK_SOURCE_AVAILABLE_IN_5_0
+G_DECLARE_FINAL_TYPE (GtkSourceSnippetContext, gtk_source_snippet_context, GTK_SOURCE, SNIPPET_CONTEXT, 
GObject)
+
+GTK_SOURCE_AVAILABLE_IN_5_0
+GtkSourceSnippetContext *gtk_source_snippet_context_new             (void);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void                     gtk_source_snippet_context_clear_variables (GtkSourceSnippetContext *self);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void                     gtk_source_snippet_context_set_variable    (GtkSourceSnippetContext *self,
+                                                                     const gchar             *key,
+                                                                     const gchar             *value);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void                     gtk_source_snippet_context_set_constant    (GtkSourceSnippetContext *self,
+                                                                     const gchar             *key,
+                                                                     const gchar             *value);
+GTK_SOURCE_AVAILABLE_IN_5_0
+const gchar             *gtk_source_snippet_context_get_variable    (GtkSourceSnippetContext *self,
+                                                                     const gchar             *key);
+GTK_SOURCE_AVAILABLE_IN_5_0
+gchar                   *gtk_source_snippet_context_expand          (GtkSourceSnippetContext *self,
+                                                                     const gchar             *input);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void                     gtk_source_snippet_context_set_tab_width   (GtkSourceSnippetContext *self,
+                                                                     gint                     tab_width);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void                     gtk_source_snippet_context_set_use_spaces  (GtkSourceSnippetContext *self,
+                                                                     gboolean                 use_spaces);
+GTK_SOURCE_AVAILABLE_IN_5_0
+void                     gtk_source_snippet_context_set_line_prefix (GtkSourceSnippetContext *self,
+                                                                     const gchar             *line_prefix);
+
+G_END_DECLS
diff --git a/gtksourceview/gtksourcetypes.h b/gtksourceview/gtksourcetypes.h
index 3ac09423..8cf43b47 100644
--- a/gtksourceview/gtksourcetypes.h
+++ b/gtksourceview/gtksourcetypes.h
@@ -59,6 +59,9 @@ typedef struct _GtkSourceMark                      GtkSourceMark;
 typedef struct _GtkSourcePrintCompositor           GtkSourcePrintCompositor;
 typedef struct _GtkSourceSearchContext             GtkSourceSearchContext;
 typedef struct _GtkSourceSearchSettings            GtkSourceSearchSettings;
+typedef struct _GtkSourceSnippet                   GtkSourceSnippet;
+typedef struct _GtkSourceSnippetChunk              GtkSourceSnippetChunk;
+typedef struct _GtkSourceSnippetContext            GtkSourceSnippetContext;
 typedef struct _GtkSourceSpaceDrawer               GtkSourceSpaceDrawer;
 typedef struct _GtkSourceStyle                     GtkSourceStyle;
 typedef struct _GtkSourceStyleSchemeChooserButton  GtkSourceStyleSchemeChooserButton;
diff --git a/gtksourceview/meson.build b/gtksourceview/meson.build
index f28546a8..7aca6f1b 100644
--- a/gtksourceview/meson.build
+++ b/gtksourceview/meson.build
@@ -33,6 +33,9 @@ core_public_h = files([
   'gtksourceregion.h',
   'gtksourcesearchcontext.h',
   'gtksourcesearchsettings.h',
+  'gtksourcesnippet.h',
+  'gtksourcesnippetchunk.h',
+  'gtksourcesnippetcontext.h',
   'gtksourcespacedrawer.h',
   'gtksourcestyle.h',
   'gtksourcestylescheme.h',
@@ -73,6 +76,9 @@ core_public_c = files([
   'gtksourceregion.c',
   'gtksourcesearchcontext.c',
   'gtksourcesearchsettings.c',
+  'gtksourcesnippet.c',
+  'gtksourcesnippetchunk.c',
+  'gtksourcesnippetcontext.c',
   'gtksourcespacedrawer.c',
   'gtksourcestyle.c',
   'gtksourcestylescheme.c',


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