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