[gtksourceview/wip/chergert/snippets: 2/2] snippet: implement basic snippet system
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtksourceview/wip/chergert/snippets: 2/2] snippet: implement basic snippet system
- Date: Mon, 20 Jan 2020 19:24:11 +0000 (UTC)
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, "<", 4);
+ break;
+
+ case '>':
+ g_string_append_len (str, ">", 4);
+ break;
+
+ case '&':
+ g_string_append_len (str, "&", 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]