[gtksourceview] Implemented context classes support



commit 4ed7a7a7e3454682bcc0a838aadf168ff07d8a8a
Author: Jesse van den Kieboom <jesse vandenkieboom epfl ch>
Date:   Thu Dec 31 14:39:33 2009 +0100

    Implemented context classes support
    
    Fixes bug #472660

 gtksourceview/gtksourcebuffer.c            |  158 +++++++++++++++
 gtksourceview/gtksourcebuffer.h            |   20 ++
 gtksourceview/gtksourcecontextengine.c     |  300 +++++++++++++++++++++++++++-
 gtksourceview/gtksourcecontextengine.h     |   10 +-
 gtksourceview/gtksourceengine.c            |   11 +
 gtksourceview/gtksourceengine.h            |    8 +
 gtksourceview/gtksourcelanguage-parser-1.c |    5 +-
 gtksourceview/gtksourcelanguage-parser-2.c |   91 ++++++++-
 gtksourceview/language-specs/language2.rng |   31 +++-
 tests/test-widget.c                        |   97 +++++++++-
 10 files changed, 710 insertions(+), 21 deletions(-)
---
diff --git a/gtksourceview/gtksourcebuffer.c b/gtksourceview/gtksourcebuffer.c
index dfa6659..ecb69a9 100644
--- a/gtksourceview/gtksourcebuffer.c
+++ b/gtksourceview/gtksourcebuffer.c
@@ -80,6 +80,8 @@
 
 #define MAX_CHARS_BEFORE_FINDING_A_MATCH    10000
 
+#define TAG_CONTEXT_CLASS_NAME "GtkSourceViewTagContextClassName"
+
 /* Signals */
 enum {
 	HIGHLIGHT_UPDATED,
@@ -1961,3 +1963,159 @@ gtk_source_buffer_remove_source_marks (GtkSourceBuffer   *buffer,
 	g_slist_free (list);
 }
 
+
+/**
+ * gtk_source_buffer_iter_has_context_class:
+ * @buffer: a #GtkSourceBuffer.
+ * @iter: a #GtkTextIter
+ * @context_class: class to search for
+ *
+ * Check if the class @context_klass is set on @iter.
+ *
+ * Since: 2.10
+ **/
+gboolean
+gtk_source_buffer_iter_has_context_class (GtkSourceBuffer   *buffer,
+                                          const GtkTextIter *iter,
+                                          const gchar       *context_class)
+{
+	GtkTextTag *tag;
+
+	g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), FALSE);
+	g_return_val_if_fail (iter != NULL, FALSE);
+	g_return_val_if_fail (context_class != NULL, FALSE);
+
+	tag = _gtk_source_engine_get_context_class_tag (buffer->priv->highlight_engine,
+							context_class);
+
+	if (tag != NULL)
+	{
+		return gtk_text_iter_has_tag (iter, tag);
+	}
+	else
+	{
+		return FALSE;
+	}
+}
+
+/**
+ * gtk_source_buffer_get_context_classes_at_iter:
+ * @buffer: a #GtkSourceBuffer.
+ * @iter: a #GtkTextIter
+ *
+ * Get all defined context classes at @iter.
+ *
+ * Returns: a new %NULL terminated array of context class names. Use
+ *          #g_strfreev to free the array if it is no longer needed.
+ *
+ * Since: 2.10
+ **/
+gchar **
+gtk_source_buffer_get_context_classes_at_iter (GtkSourceBuffer   *buffer,
+                                               const GtkTextIter *iter)
+{
+	GSList *tags;
+	GSList *item;
+	GPtrArray *ret;
+
+	g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), NULL);
+	g_return_val_if_fail (iter != NULL, NULL);
+
+	tags = gtk_text_iter_get_tags (iter);
+	ret = g_ptr_array_new ();
+
+	for (item = tags; item; item = g_slist_next (item))
+	{
+		gchar const *name = g_object_get_data (G_OBJECT (item->data),
+		                                       TAG_CONTEXT_CLASS_NAME);
+
+		if (name != NULL)
+		{
+			g_ptr_array_add (ret, g_strdup (name));
+		}
+	}
+
+	g_ptr_array_add (ret, NULL);
+	return (gchar **) g_ptr_array_free (ret, FALSE);
+}
+
+/**
+ * gtk_source_buffer_iter_forward_to_context_class_toggle:
+ * @buffer: a #GtkSourceBuffer.
+ * @iter: a #GtkTextIter
+ * @context_class: the context class
+ *
+ * Moves forward to the next toggle (on or off) of the context class. If no
+ * matching context class toggles are found, returns %FALSE, otherwise %TRUE.
+ * Does not return toggles located at @iter, only toggles after @iter. Sets
+ * @iter to the location of the toggle, or to the end of the buffer if no
+ * toggle is found.
+ *
+ * Returns: whether we found a context class toggle after @iter
+ *
+ * Since: 2.10
+ **/
+gboolean
+gtk_source_buffer_iter_forward_to_context_class_toggle (GtkSourceBuffer *buffer,
+                                                        GtkTextIter     *iter,
+                                                        const gchar     *context_class)
+{
+	GtkTextTag *tag;
+
+	g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), FALSE);
+	g_return_val_if_fail (iter != NULL, FALSE);
+	g_return_val_if_fail (context_class != NULL, FALSE);
+
+	tag = _gtk_source_engine_get_context_class_tag (buffer->priv->highlight_engine,
+							context_class);
+
+	if (tag == NULL)
+	{
+		return FALSE;
+	}
+	else
+	{
+		return gtk_text_iter_forward_to_tag_toggle (iter, tag);
+	}
+}
+
+/**
+ * gtk_source_buffer_iter_backward_to_context_class_toggle:
+ * @buffer: a #GtkSourceBuffer.
+ * @iter: a #GtkTextIter
+ * @context_class: the context class
+ *
+ * Moves backward to the next toggle (on or off) of the context class. If no
+ * matching context class toggles are found, returns %FALSE, otherwise %TRUE.
+ * Does not return toggles located at @iter, only toggles after @iter. Sets
+ * @iter to the location of the toggle, or to the end of the buffer if no
+ * toggle is found.
+ *
+ * Returns: whether we found a context class toggle before @iter
+ *
+ * Since: 2.10
+ **/
+gboolean
+gtk_source_buffer_iter_backward_to_context_class_toggle (GtkSourceBuffer *buffer,
+                                                         GtkTextIter     *iter,
+                                                         const gchar     *context_class)
+{
+	GtkTextTag *tag;
+
+	g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), FALSE);
+	g_return_val_if_fail (iter != NULL, FALSE);
+	g_return_val_if_fail (context_class != NULL, FALSE);
+
+	tag = _gtk_source_engine_get_context_class_tag (buffer->priv->highlight_engine,
+							context_class);
+
+	if (tag == NULL)
+	{
+		return FALSE;
+	}
+	else
+	{
+		return gtk_text_iter_backward_to_tag_toggle (iter, tag);
+	}
+}
+
diff --git a/gtksourceview/gtksourcebuffer.h b/gtksourceview/gtksourcebuffer.h
index f578fba..67ec9f2 100644
--- a/gtksourceview/gtksourcebuffer.h
+++ b/gtksourceview/gtksourcebuffer.h
@@ -134,6 +134,26 @@ void			 gtk_source_buffer_remove_source_marks	(GtkSourceBuffer        *buffer,
 								 const GtkTextIter      *start,
 								 const GtkTextIter      *end,
 								 const gchar            *category);
+
+gboolean		 gtk_source_buffer_iter_has_context_class
+								(GtkSourceBuffer	*buffer,
+								 const GtkTextIter	*iter,
+								 const gchar            *context_class);
+
+gchar			**gtk_source_buffer_get_context_classes_at_iter
+								(GtkSourceBuffer	*buffer,
+								 const GtkTextIter	*iter);
+
+gboolean		 gtk_source_buffer_iter_forward_to_context_class_toggle
+								(GtkSourceBuffer	*buffer,
+								 GtkTextIter		*iter,
+								 const gchar		*context_class);
+
+gboolean		 gtk_source_buffer_iter_backward_to_context_class_toggle
+								(GtkSourceBuffer	*buffer,
+								 GtkTextIter		*iter,
+								 const gchar		*context_class);
+
 /* private */
 void			 _gtk_source_buffer_update_highlight	(GtkSourceBuffer        *buffer,
 								 const GtkTextIter      *start,
diff --git a/gtksourceview/gtksourcecontextengine.c b/gtksourceview/gtksourcecontextengine.c
index 4f4b79d..4b00887 100644
--- a/gtksourceview/gtksourcecontextengine.c
+++ b/gtksourceview/gtksourcecontextengine.c
@@ -116,6 +116,8 @@
 #define ENGINE_ID(ce) ((ce)->priv->ctx_data->lang->priv->id)
 #define ENGINE_STYLES_MAP(ce) ((ce)->priv->ctx_data->lang->priv->styles)
 
+#define TAG_CONTEXT_CLASS_NAME "GtkSourceViewTagContextClassName"
+
 typedef struct _RegexInfo RegexInfo;
 typedef struct _RegexAndMatch RegexAndMatch;
 typedef struct _Regex Regex;
@@ -129,6 +131,7 @@ typedef struct _DefinitionChild DefinitionChild;
 typedef struct _DefinitionsIter DefinitionsIter;
 typedef struct _LineInfo LineInfo;
 typedef struct _InvalidRegion InvalidRegion;
+typedef struct _ContextClassTag ContextClassTag;
 
 typedef enum {
 	GTK_SOURCE_CONTEXT_ENGINE_ERROR_DUPLICATED_ID = 0,
@@ -201,6 +204,9 @@ struct _ContextDefinition
 	GSList			*sub_patterns;
 	guint			 n_sub_patterns;
 
+	/* List of class definitions */
+	GSList			*context_classes;
+
 	/* Union of every regular expression we can find from this
 	 * context. */
 	Regex			*reg_all;
@@ -218,6 +224,9 @@ struct _SubPatternDefinition
 	gchar			*style;
 	SubPatternWhere		 where;
 
+	/* List of class definitions */
+	GSList                  *context_classes;
+
 	/* index in the ContextDefinition's list */
 	guint			 index;
 
@@ -276,6 +285,12 @@ struct _Context
 	GtkTextTag		*tag;
 	GtkTextTag	       **subpattern_tags;
 
+	/* Cache for generated list of class tags */
+	GSList                  *context_classes;
+
+	/* Cache for generated list of subpattern class tags */
+	GSList                 **subpattern_context_classes;
+
 	guint			 ref_count;
 	/* see context_freeze() */
 	guint                    frozen : 1;
@@ -383,6 +398,18 @@ struct _InvalidRegion
 	gint			 delta;
 };
 
+struct _GtkSourceContextClass
+{
+	gchar    *name;
+	gboolean  enabled;
+};
+
+struct _ContextClassTag
+{
+	GtkTextTag *tag;
+	gboolean enabled;
+};
+
 struct _GtkSourceContextData
 {
 	guint			 ref_count;
@@ -406,6 +433,8 @@ struct _GtkSourceContextEnginePrivate
 	 * tag priorities */
 	guint			 n_tags;
 
+	GHashTable		*context_classes;
+
 	/* Whether or not to actually highlight the buffer. */
 	gboolean		 highlight;
 
@@ -514,6 +543,49 @@ static gboolean		mem_usage_timeout	(GtkSourceContextEngine *ce);
 
 /* TAGS AND STUFF -------------------------------------------------------------- */
 
+GtkSourceContextClass *
+gtk_source_context_class_new (gchar const *name,
+                              gboolean     enabled)
+{
+	GtkSourceContextClass *def = g_slice_new (GtkSourceContextClass);
+
+	def->name = g_strdup (name);
+	def->enabled = enabled;
+
+	return def;
+}
+
+static GtkSourceContextClass *
+gtk_source_context_class_copy (GtkSourceContextClass *cclass)
+{
+	return gtk_source_context_class_new (cclass->name, cclass->enabled);
+}
+
+void
+gtk_source_context_class_free (GtkSourceContextClass *cclass)
+{
+	g_free (cclass->name);
+	g_slice_free (GtkSourceContextClass, cclass);
+}
+
+static ContextClassTag *
+context_class_tag_new (GtkTextTag *tag,
+                       gboolean    enabled)
+{
+	ContextClassTag *attrtag = g_slice_new (ContextClassTag);
+
+	attrtag->tag = tag;
+	attrtag->enabled = enabled;
+
+	return attrtag;
+}
+
+static void
+context_class_tag_free (ContextClassTag *attrtag)
+{
+	g_slice_free (ContextClassTag, attrtag);
+}
+
 struct BufAndIters {
 	GtkTextBuffer *buffer;
 	const GtkTextIter *start, *end;
@@ -537,6 +609,18 @@ unhighlight_region_cb (G_GNUC_UNUSED gpointer style,
 }
 
 static void
+unhighlight_region_class_cb (G_GNUC_UNUSED gpointer class,
+                             GtkTextTag             *tag,
+                             gpointer                user_data)
+{
+	struct BufAndIters *data = user_data;
+	gtk_text_buffer_remove_tag (data->buffer,
+	                            tag,
+	                            data->start,
+	                            data->end);
+}
+
+static void
 unhighlight_region (GtkSourceContextEngine *ce,
 		    const GtkTextIter      *start,
 		    const GtkTextIter      *end)
@@ -551,6 +635,7 @@ unhighlight_region (GtkSourceContextEngine *ce,
 		return;
 
 	g_hash_table_foreach (ce->priv->tags, (GHFunc) unhighlight_region_cb, &data);
+	g_hash_table_foreach (ce->priv->context_classes, (GHFunc) unhighlight_region_class_cb, &data);
 }
 
 #define MAX_STYLE_DEPENDENCY_DEPTH	50
@@ -746,6 +831,118 @@ get_context_tag (GtkSourceContextEngine *ce,
 	return context->tag;
 }
 
+static GtkTextTag *
+get_context_class_tag (GtkSourceContextEngine *ce,
+                       gchar const            *name)
+{
+	GtkTextTag *ret;
+
+	ret = g_hash_table_lookup (ce->priv->context_classes, name);
+
+	if (ret == NULL)
+	{
+		ret = gtk_text_buffer_create_tag (ce->priv->buffer, NULL, NULL);
+		g_object_set_data_full (G_OBJECT (ret),
+		                        TAG_CONTEXT_CLASS_NAME,
+		                        g_strdup (name),
+		                        (GDestroyNotify)g_free);
+
+		g_hash_table_insert (ce->priv->context_classes,
+		                     g_strdup (name),
+		                     ret);
+	}
+
+	return ret;
+}
+
+static GSList *
+extend_context_classes (GtkSourceContextEngine *ce,
+                        GSList                 *definitions)
+{
+	GSList *item;
+	GSList *ret = NULL;
+
+	for (item = definitions; item != NULL; item = g_slist_next (item))
+	{
+		GtkSourceContextClass *cclass = item->data;
+		ContextClassTag *attrtag = context_class_tag_new (get_context_class_tag (ce, cclass->name),
+		                                                  cclass->enabled);
+
+		ret = g_slist_prepend (ret, attrtag);
+	}
+
+	return g_slist_reverse (ret);
+}
+
+static GSList *
+get_subpattern_context_classes (GtkSourceContextEngine *ce,
+                                Context                *context,
+                                SubPatternDefinition   *sp_def)
+{
+	g_assert (sp_def->index < context->definition->n_sub_patterns);
+
+	if (context->subpattern_context_classes == NULL)
+		context->subpattern_context_classes = g_new0 (GSList *, context->definition->n_sub_patterns);
+
+	if (context->subpattern_context_classes[sp_def->index] == NULL)
+	{
+		context->subpattern_context_classes[sp_def->index] =
+				extend_context_classes (ce,
+		                                        sp_def->context_classes);
+	}
+
+	return context->subpattern_context_classes[sp_def->index];
+}
+
+static GSList *
+get_context_classes (GtkSourceContextEngine *ce,
+                     Context                *context)
+{
+	if (context->context_classes == NULL)
+	{
+		context->context_classes =
+				extend_context_classes (ce,
+		                                        context->definition->context_classes);
+	}
+
+	return context->context_classes;
+}
+
+static void
+apply_context_classes (GtkSourceContextEngine *ce,
+                       GSList                 *context_classes,
+                       gint                    start,
+                       gint                    end)
+{
+	GtkTextIter start_iter;
+	GtkTextIter end_iter;
+	GSList *item;
+
+	gtk_text_buffer_get_iter_at_offset (ce->priv->buffer, &start_iter, start);
+	end_iter = start_iter;
+	gtk_text_iter_forward_chars (&end_iter, end - start);
+
+	for (item = context_classes; item != NULL; item = g_slist_next (item))
+	{
+		ContextClassTag *attrtag = item->data;
+
+		if (attrtag->enabled)
+		{
+			gtk_text_buffer_apply_tag (ce->priv->buffer,
+			                           attrtag->tag,
+			                           &start_iter,
+			                           &end_iter);
+		}
+		else
+		{
+			gtk_text_buffer_remove_tag (ce->priv->buffer,
+			                            attrtag->tag,
+			                            &start_iter,
+			                            &end_iter);
+		}
+	}
+}
+
 static void
 apply_tags (GtkSourceContextEngine *ce,
 	    Segment                *segment,
@@ -757,6 +954,7 @@ apply_tags (GtkSourceContextEngine *ce,
 	GtkTextBuffer *buffer = ce->priv->buffer;
 	SubPattern *sp;
 	Segment *child;
+	GSList *context_classes;
 
 	g_assert (segment != NULL);
 
@@ -769,6 +967,17 @@ apply_tags (GtkSourceContextEngine *ce,
 	start_offset = MAX (start_offset, segment->start_at);
 	end_offset = MIN (end_offset, segment->end_at);
 
+	context_classes = get_context_classes (ce,
+	                                       segment->context);
+
+	if (context_classes != NULL)
+	{
+		apply_context_classes (ce,
+		                       context_classes,
+		                       start_offset,
+		                       end_offset);
+	}
+
 	tag = get_context_tag (ce, segment->context);
 
 	if (tag != NULL)
@@ -801,12 +1010,18 @@ apply_tags (GtkSourceContextEngine *ce,
 	{
 		if (sp->start_at >= start_offset && sp->end_at <= end_offset)
 		{
+			gint start = MAX (start_offset, sp->start_at);
+			gint end = MIN (end_offset, sp->end_at);
+
+			context_classes = get_subpattern_context_classes (ce,
+				                                          segment->context,
+				                                          sp->definition);
+
 			tag = get_subpattern_tag (ce, segment->context, sp->definition);
+			apply_context_classes (ce, context_classes, start, end);
 
 			if (tag != NULL)
 			{
-				gint start = MAX (start_offset, sp->start_at);
-				gint end = MIN (end_offset, sp->end_at);
 				gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
 				end_iter = start_iter;
 				gtk_text_iter_forward_chars (&end_iter, end - start);
@@ -2182,6 +2397,14 @@ remove_tags_hash_cb (G_GNUC_UNUSED gpointer style,
 	g_slist_free (tags);
 }
 
+static void
+remove_context_classes_hash_cb (G_GNUC_UNUSED gpointer class,
+                                GtkTextTag             *tag,
+                                GtkTextTagTable        *table)
+{
+	gtk_text_tag_table_remove (table, tag);
+}
+
 /**
  * destroy_tags_hash:
  *
@@ -2198,6 +2421,15 @@ destroy_tags_hash (GtkSourceContextEngine *ce)
 	ce->priv->tags = NULL;
 }
 
+static void
+destroy_context_classes_hash (GtkSourceContextEngine *ce)
+{
+	g_hash_table_foreach (ce->priv->context_classes, (GHFunc) remove_context_classes_hash_cb,
+	                      gtk_text_buffer_get_tag_table (ce->priv->buffer));
+	g_hash_table_destroy (ce->priv->context_classes);
+	ce->priv->context_classes = NULL;
+}
+
 /**
  * gtk_source_context_engine_attach_buffer:
  *
@@ -2260,6 +2492,8 @@ gtk_source_context_engine_attach_buffer (GtkSourceEngine *engine,
 		destroy_tags_hash (ce);
 		ce->priv->n_tags = 0;
 
+		destroy_context_classes_hash (ce);
+
 		if (ce->priv->refresh_region != NULL)
 			gtk_text_region_destroy (ce->priv->refresh_region, FALSE);
 		if (ce->priv->highlight_requests != NULL)
@@ -2289,6 +2523,7 @@ gtk_source_context_engine_attach_buffer (GtkSourceEngine *engine,
 		ce->priv->root_segment = create_segment (ce, NULL, ce->priv->root_context, 0, 0, TRUE, NULL);
 
 		ce->priv->tags = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+		ce->priv->context_classes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
 
 		gtk_text_buffer_get_bounds (buffer, &start, &end);
 		ce->priv->invalid_region.start = gtk_text_buffer_create_mark (buffer, NULL,
@@ -2414,6 +2649,14 @@ gtk_source_context_engine_finalize (GObject *object)
 	G_OBJECT_CLASS (_gtk_source_context_engine_parent_class)->finalize (object);
 }
 
+static GtkTextTag *
+gtk_source_context_engine_get_context_class_tag (GtkSourceEngine *engine,
+						 const gchar     *context_class)
+{
+	return g_hash_table_lookup (GTK_SOURCE_CONTEXT_ENGINE (engine)->priv->context_classes,
+				    context_class);
+}
+
 static void
 _gtk_source_context_engine_class_init (GtkSourceContextEngineClass *klass)
 {
@@ -2427,6 +2670,7 @@ _gtk_source_context_engine_class_init (GtkSourceContextEngineClass *klass)
 	engine_class->text_deleted = gtk_source_context_engine_text_deleted;
 	engine_class->update_highlight = gtk_source_context_engine_update_highlight;
 	engine_class->set_style_scheme = gtk_source_context_engine_set_style_scheme;
+	engine_class->get_context_class_tag = gtk_source_context_engine_get_context_class_tag;
 
 	g_type_class_add_private (object_class, sizeof (GtkSourceContextEnginePrivate));
 }
@@ -3393,6 +3637,7 @@ static void
 context_unref (Context *context)
 {
 	ContextPtr *children;
+	gint i;
 
 	if (context == NULL || --context->ref_count != 0)
 		return;
@@ -3433,7 +3678,25 @@ context_unref (Context *context)
 
 	regex_unref (context->end);
 	regex_unref (context->reg_all);
+
+	if (context->subpattern_context_classes != NULL)
+	{
+		for (i = 0; i < context->definition->n_sub_patterns; ++i)
+		{
+			g_slist_foreach (context->subpattern_context_classes[i], 
+			                 (GFunc)context_class_tag_free,
+			                 NULL);
+
+			g_slist_free (context->subpattern_context_classes[i]);
+		}
+	}
+
+	g_slist_foreach (context->context_classes, (GFunc)context_class_tag_free, NULL);
+	g_slist_free (context->context_classes);
+
+	g_free (context->subpattern_context_classes);
 	g_free (context->subpattern_tags);
+
 	g_slice_free (Context, context);
 }
 
@@ -5678,6 +5941,20 @@ definition_child_free (DefinitionChild *ch)
 #endif
 }
 
+static GSList *
+copy_context_classes (GSList *context_classes)
+{
+	GSList *ret = NULL;
+
+	while (context_classes)
+	{
+		ret = g_slist_prepend (ret, gtk_source_context_class_copy (context_classes->data));
+		context_classes = g_slist_next (context_classes);
+	}
+
+	return g_slist_reverse (ret);
+}
+
 static ContextDefinition *
 context_definition_new (const gchar        *id,
 			ContextType         type,
@@ -5685,6 +5962,7 @@ context_definition_new (const gchar        *id,
 			const gchar        *start,
 			const gchar        *end,
 			const gchar        *style,
+			GSList             *context_classes,
 			GtkSourceContextFlags flags,
 			GError            **error)
 {
@@ -5775,6 +6053,8 @@ context_definition_new (const gchar        *id,
 	definition->sub_patterns = NULL;
 	definition->n_sub_patterns = 0;
 
+	definition->context_classes = copy_context_classes (context_classes);
+
 	return definition;
 }
 
@@ -5815,6 +6095,10 @@ context_definition_unref (ContextDefinition *definition)
 		g_free (sp_def->style);
 		if (sp_def->is_named)
 			g_free (sp_def->u.name);
+
+		g_slist_foreach (sp_def->context_classes, (GFunc) gtk_source_context_class_free, NULL);
+		g_slist_free (sp_def->context_classes);
+
 		g_slice_free (SubPatternDefinition, sp_def);
 		sub_pattern_list = sub_pattern_list->next;
 	}
@@ -5824,6 +6108,9 @@ context_definition_unref (ContextDefinition *definition)
 	g_free (definition->default_style);
 	regex_unref (definition->reg_all);
 
+	g_slist_foreach (definition->context_classes, (GFunc) gtk_source_context_class_free, NULL);
+	g_slist_free (definition->context_classes);
+
 	g_slist_foreach (definition->children, (GFunc) definition_child_free, NULL);
 	g_slist_free (definition->children);
 	g_slice_free (ContextDefinition, definition);
@@ -5888,6 +6175,7 @@ _gtk_source_context_data_define_context (GtkSourceContextData *ctx_data,
 					 const gchar          *start_regex,
 					 const gchar          *end_regex,
 					 const gchar          *style,
+					 GSList               *context_classes,
 					 GtkSourceContextFlags flags,
 					 GError              **error)
 {
@@ -5952,6 +6240,7 @@ _gtk_source_context_data_define_context (GtkSourceContextData *ctx_data,
 
 	definition = context_definition_new (id, type, match_regex,
 					     start_regex, end_regex, style,
+					     context_classes,
 					     flags, error);
 	if (definition == NULL)
 		return FALSE;
@@ -5974,6 +6263,7 @@ _gtk_source_context_data_add_sub_pattern (GtkSourceContextData *ctx_data,
 					  const gchar          *name,
 					  const gchar          *where,
 					  const gchar          *style,
+					  GSList               *context_classes,
 					  GError              **error)
 {
 	ContextDefinition *parent;
@@ -6049,6 +6339,8 @@ _gtk_source_context_data_add_sub_pattern (GtkSourceContextData *ctx_data,
 	parent->sub_patterns = g_slist_append (parent->sub_patterns, sp_def);
 	sp_def->index = parent->n_sub_patterns++;
 
+	sp_def->context_classes = copy_context_classes (context_classes);
+
 	return TRUE;
 }
 
@@ -6385,7 +6677,7 @@ _gtk_source_context_data_set_escape_char (GtkSourceContextData *ctx_data,
 	definitions = g_slist_reverse (definitions);
 
 	if (!_gtk_source_context_data_define_context (ctx_data, "gtk-source-context-engine-escape",
-						      NULL, pattern, NULL, NULL, NULL,
+						      NULL, pattern, NULL, NULL, NULL, NULL,
 						      GTK_SOURCE_CONTEXT_EXTEND_PARENT,
 						      &error))
 		goto out;
@@ -6394,7 +6686,7 @@ _gtk_source_context_data_set_escape_char (GtkSourceContextData *ctx_data,
 	pattern = g_strdup_printf ("%s$", escaped);
 
 	if (!_gtk_source_context_data_define_context (ctx_data, "gtk-source-context-engine-line-escape",
-						      NULL, NULL, pattern, "^", NULL,
+						      NULL, NULL, pattern, "^", NULL, NULL,
 						      GTK_SOURCE_CONTEXT_EXTEND_PARENT,
 						      &error))
 		goto out;
diff --git a/gtksourceview/gtksourcecontextengine.h b/gtksourceview/gtksourcecontextengine.h
index f83fb9f..5074000 100644
--- a/gtksourceview/gtksourcecontextengine.h
+++ b/gtksourceview/gtksourcecontextengine.h
@@ -36,7 +36,7 @@ G_BEGIN_DECLS
 
 typedef struct _GtkSourceContextData          GtkSourceContextData;
 typedef struct _GtkSourceContextReplace       GtkSourceContextReplace;
-
+typedef struct _GtkSourceContextClass         GtkSourceContextClass;
 typedef struct _GtkSourceContextEngine        GtkSourceContextEngine;
 typedef struct _GtkSourceContextEngineClass   GtkSourceContextEngineClass;
 typedef struct _GtkSourceContextEnginePrivate GtkSourceContextEnginePrivate;
@@ -75,6 +75,12 @@ GtkSourceContextData *_gtk_source_context_data_new	(GtkSourceLanguage	*lang);
 GtkSourceContextData *_gtk_source_context_data_ref	(GtkSourceContextData	*data);
 void		 _gtk_source_context_data_unref		(GtkSourceContextData	*data);
 
+GtkSourceContextClass *
+		gtk_source_context_class_new		(gchar const *name,
+							 gboolean     enabled);
+
+void		gtk_source_context_class_free		(GtkSourceContextClass *cclass);
+
 GtkSourceContextEngine *_gtk_source_context_engine_new  (GtkSourceContextData	*data);
 
 gboolean	 _gtk_source_context_data_define_context
@@ -85,6 +91,7 @@ gboolean	 _gtk_source_context_data_define_context
 							 const gchar		 *start_regex,
 							 const gchar		 *end_regex,
 							 const gchar		 *style,
+							 GSList			 *context_classes,
 							 GtkSourceContextFlags    flags,
 							 GError			**error);
 
@@ -95,6 +102,7 @@ gboolean	 _gtk_source_context_data_add_sub_pattern
 							 const gchar		 *name,
 							 const gchar		 *where,
 							 const gchar		 *style,
+							 GSList			 *context_classes,
 							 GError			**error);
 
 gboolean	 _gtk_source_context_data_add_ref 	(GtkSourceContextData	 *data,
diff --git a/gtksourceview/gtksourceengine.c b/gtksourceview/gtksourceengine.c
index 699d29e..e89dfe2 100644
--- a/gtksourceview/gtksourceengine.c
+++ b/gtksourceview/gtksourceengine.c
@@ -104,3 +104,14 @@ _gtk_source_engine_set_style_scheme (GtkSourceEngine      *engine,
 
 	GTK_SOURCE_ENGINE_GET_CLASS (engine)->set_style_scheme (engine, scheme);
 }
+
+GtkTextTag *
+_gtk_source_engine_get_context_class_tag (GtkSourceEngine *engine,
+					  const gchar     *context_class)
+{
+	g_return_val_if_fail (GTK_IS_SOURCE_ENGINE (engine), NULL);
+	g_return_val_if_fail (context_class != NULL, NULL);
+
+	return GTK_SOURCE_ENGINE_GET_CLASS (engine)->get_context_class_tag (engine,
+									    context_class);
+}
diff --git a/gtksourceview/gtksourceengine.h b/gtksourceview/gtksourceengine.h
index bc087e9..2d95488 100644
--- a/gtksourceview/gtksourceengine.h
+++ b/gtksourceview/gtksourceengine.h
@@ -63,6 +63,10 @@ struct _GtkSourceEngineClass
 
 	void     (* set_style_scheme) (GtkSourceEngine      *engine,
 				       GtkSourceStyleScheme *scheme);
+
+	GtkTextTag *(* get_context_class_tag)
+				      (GtkSourceEngine      *engine,
+				       const gchar          *context_class);
 };
 
 GType       _gtk_source_engine_get_type		(void) G_GNUC_CONST;
@@ -82,6 +86,10 @@ void        _gtk_source_engine_update_highlight	(GtkSourceEngine      *engine,
 void        _gtk_source_engine_set_style_scheme	(GtkSourceEngine      *engine,
 						 GtkSourceStyleScheme *scheme);
 
+GtkTextTag *_gtk_source_engine_get_context_class_tag
+						 (GtkSourceEngine     *engine,
+						  const gchar         *context_class);
+
 G_END_DECLS
 
 #endif /* __GTK_SOURCE_ENGINE_H__ */
diff --git a/gtksourceview/gtksourcelanguage-parser-1.c b/gtksourceview/gtksourcelanguage-parser-1.c
index 15dab3f..ab61ca9 100644
--- a/gtksourceview/gtksourcelanguage-parser-1.c
+++ b/gtksourceview/gtksourcelanguage-parser-1.c
@@ -97,7 +97,7 @@ ctx_data_add_simple_pattern (GtkSourceContextData *ctx_data,
 	result = _gtk_source_context_data_define_context (ctx_data, real_id,
 							  root_id,
 							  fixed, NULL, NULL,
-							  style,
+							  style, NULL,
 							  GTK_SOURCE_CONTEXT_EXTEND_PARENT |
 								GTK_SOURCE_CONTEXT_END_AT_LINE_END,
 							  &error);
@@ -145,6 +145,7 @@ ctx_data_add_syntax_pattern (GtkSourceContextData *ctx_data,
 							  pattern_start,
 							  pattern_end,
 							  style,
+							  NULL,
 							  flags,
 							  &error);
 
@@ -658,7 +659,7 @@ define_root_context (GtkSourceContextData *ctx_data,
 	id = g_strdup_printf ("%s:%s", language->priv->id, language->priv->id);
 	result = _gtk_source_context_data_define_context (ctx_data, id,
 							  NULL, NULL, NULL, NULL,
-							  NULL,
+							  NULL, NULL,
 							  GTK_SOURCE_CONTEXT_EXTEND_PARENT,
 							  &error);
 
diff --git a/gtksourceview/gtksourcelanguage-parser-2.c b/gtksourceview/gtksourcelanguage-parser-2.c
index 0cdfd05..25b4e18 100644
--- a/gtksourceview/gtksourcelanguage-parser-2.c
+++ b/gtksourceview/gtksourcelanguage-parser-2.c
@@ -153,6 +153,7 @@ static gboolean   create_definition            (ParserState *parser_state,
                                                 gchar *id,
                                                 gchar *parent_id,
                                                 gchar *style,
+                                                GSList *context_classes,
 						GError **error);
 
 static void       handle_context_element       (ParserState *parser_state);
@@ -303,11 +304,56 @@ get_context_flags (ParserState *parser_state)
 	return flags;
 }
 
+static GSList *
+add_classes (GSList      *list,
+             gchar const *classes,
+             gboolean     enabled)
+{
+	gchar **parts;
+	gchar **ptr;
+	GSList *newlist = NULL;
+
+	if (classes == NULL)
+	{
+		return list;
+	}
+
+	parts = ptr = g_strsplit (classes, " ", -1);
+
+	while (*ptr)
+	{
+		GtkSourceContextClass *ctx = gtk_source_context_class_new (*ptr, enabled);
+		newlist = g_slist_prepend (newlist, ctx);
+
+		++ptr;
+	}
+
+	g_strfreev (parts);
+	return g_slist_concat (list, g_slist_reverse (newlist));
+}
+
+static GSList *
+parse_classes (ParserState *parser_state)
+{
+	GSList *ret = NULL;
+	xmlChar *en = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "class");
+	xmlChar *dis = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "class-disabled");
+
+	ret = add_classes (ret, (gchar const *)en, TRUE);
+	ret = add_classes (ret, (gchar const *)dis, FALSE);
+
+	xmlFree (en);
+	xmlFree (dis);
+
+	return ret;
+}
+
 static gboolean
 create_definition (ParserState *parser_state,
 		   gchar       *id,
 		   gchar       *parent_id,
 		   gchar       *style,
+		   GSList      *context_classes,
 		   GError     **error)
 {
 	gchar *match = NULL, *start = NULL, *end = NULL;
@@ -468,6 +514,7 @@ create_definition (ParserState *parser_state,
 	}
 
 	if (tmp_error == NULL)
+	{
 		_gtk_source_context_data_define_context (parser_state->ctx_data,
 							 id,
 							 parent_id,
@@ -475,8 +522,10 @@ create_definition (ParserState *parser_state,
 							 start,
 							 end,
 							 style,
+							 context_classes,
 							 flags,
 							 &tmp_error);
+	}
 
 	g_free (match);
 	g_free (start);
@@ -608,11 +657,12 @@ add_ref (ParserState               *parser_state,
 }
 
 static gboolean
-create_sub_pattern (ParserState *parser_state,
-                    gchar *id,
-                    gchar *sub_pattern,
-                    gchar *style,
-                    GError **error)
+create_sub_pattern (ParserState  *parser_state,
+                    gchar        *id,
+                    gchar        *sub_pattern,
+                    gchar        *style,
+                    GSList       *context_classes,
+                    GError      **error)
 {
 	gchar *container_id;
 	xmlChar *where;
@@ -634,6 +684,7 @@ create_sub_pattern (ParserState *parser_state,
 						  sub_pattern,
 						  (gchar*) where,
 						  style,
+						  context_classes,
 						  &tmp_error);
 
 	xmlFree (where);
@@ -656,6 +707,7 @@ handle_context_element (ParserState *parser_state)
 	gboolean success;
 	gboolean ignore_style = FALSE;
 	GtkSourceContextRefOptions options = 0;
+	GSList *context_classes;
 
 	GError *tmp_error = NULL;
 
@@ -706,6 +758,8 @@ handle_context_element (ParserState *parser_state)
 		g_warning ("in file %s: style '%s' not defined", parser_state->filename, style_ref);
 	}
 
+	context_classes = parse_classes (parser_state);
+
 	if (ref != NULL)
 	{
 		tmp = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "original");
@@ -716,7 +770,11 @@ handle_context_element (ParserState *parser_state)
 		if (style_ref != NULL)
 			options |= GTK_SOURCE_CONTEXT_OVERRIDE_STYLE;
 
-		add_ref (parser_state, (gchar*) ref, options, style_ref, &tmp_error);
+		add_ref (parser_state,
+		         (gchar*) ref,
+		         options,
+		         style_ref,
+		         &tmp_error);
 	}
 	else
 	{
@@ -741,9 +799,12 @@ handle_context_element (ParserState *parser_state)
 		{
 			if (sub_pattern != NULL)
 			{
-				create_sub_pattern (parser_state, id,
-						(gchar *)sub_pattern, style_ref,
-						&tmp_error);
+				create_sub_pattern (parser_state,
+				                    id,
+				                    (gchar *)sub_pattern,
+				                    style_ref,
+				                    context_classes,
+				                    &tmp_error);
 			}
 			else
 			{
@@ -761,11 +822,16 @@ handle_context_element (ParserState *parser_state)
 											   NULL,
 											   NULL,
 											   NULL,
+											   NULL,
 											   0,
 											   &tmp_error);
 				else
-					success = create_definition (parser_state, id, parent_id,
-								     style_ref, &tmp_error);
+					success = create_definition (parser_state,
+					                             id,
+					                             parent_id,
+					                             style_ref,
+					                             context_classes,
+					                             &tmp_error);
 
 				if (success && !is_empty)
 				{
@@ -781,6 +847,9 @@ handle_context_element (ParserState *parser_state)
 		g_free (id);
 	}
 
+	g_slist_foreach (context_classes, (GFunc)gtk_source_context_class_free, NULL);
+	g_slist_free (context_classes);
+
 	g_free (style_ref);
 	xmlFree (sub_pattern);
 	xmlFree (ref);
diff --git a/gtksourceview/language-specs/language2.rng b/gtksourceview/language-specs/language2.rng
index 87ead1c..babdd87 100644
--- a/gtksourceview/language-specs/language2.rng
+++ b/gtksourceview/language-specs/language2.rng
@@ -236,7 +236,12 @@
                 <ref name="boolean-value"/>
             </attribute>
         </optional>
-
+        <optional>
+            <attribute name="class"/>
+        </optional>
+        <optional>
+            <attribute name="class-disabled"/>
+        </optional>
         <choice>
             <element name="match">
                 <ref name="regex-options"/>
@@ -308,6 +313,12 @@
                 <ref name="boolean-value"/>
             </attribute>
         </optional>
+        <optional>
+            <attribute name="class"/>
+        </optional>
+        <optional>
+            <attribute name="class-disabled"/>
+        </optional>
 
         <element name="start">
             <ref name="regex-options"/>
@@ -347,6 +358,12 @@
         <attribute name="id">
             <ref name="id-type"/>
         </attribute>
+        <optional>
+            <attribute name="class"/>
+        </optional>
+        <optional>
+            <attribute name="class-disabled"/>
+        </optional>
 
         <element name="include">
             <interleave>
@@ -379,6 +396,12 @@
                 <ref name="id-type"/>
             </attribute>
         </optional>
+        <optional>
+            <attribute name="class"/>
+        </optional>
+        <optional>
+            <attribute name="class-disabled"/>
+        </optional>
 
         <attribute name="sub-pattern"/>
     </element>
@@ -396,6 +419,12 @@
                 <ref name="id-type"/>
             </attribute>
         </optional>
+        <optional>
+            <attribute name="class"/>
+        </optional>
+        <optional>
+            <attribute name="class-disabled"/>
+        </optional>
 
         <attribute name="sub-pattern"/>
 
diff --git a/tests/test-widget.c b/tests/test-widget.c
index 5e7d527..46f5f4e 100644
--- a/tests/test-widget.c
+++ b/tests/test-widget.c
@@ -92,6 +92,13 @@ static void       indent_toggled_cb              (GtkAction       *action,
 						  GtkAction       *current,
 						  gpointer         user_data);
 
+static void       forward_string_cb              (GtkAction       *action,
+						  gpointer         user_data);
+
+static void       backward_string_cb             (GtkAction       *action,
+						  gpointer         user_data);
+
+
 static GtkWidget *create_view_window             (GtkSourceBuffer *buffer,
 						  GtkSourceView   *from);
 
@@ -119,6 +126,10 @@ static GtkActionEntry view_action_entries[] = {
 	  "Find", G_CALLBACK (find_cb) },
 	{ "Replace", GTK_STOCK_FIND_AND_REPLACE, "Search and _Replace", "<control>R",
 	  "Search and Replace", G_CALLBACK (replace_cb) },
+	{ "ForwardString", NULL, "_Forward to string toggle", "<control>S",
+	  "Forward to the start or end of the next string", G_CALLBACK (forward_string_cb) },
+	{ "BackwardString", NULL, "_Backward to string toggle", "<control><shift>S",
+	  "Backward to the start or end of the next string", G_CALLBACK (backward_string_cb) }
 };
 
 static GtkToggleActionEntry toggle_entries[] = {
@@ -223,6 +234,9 @@ static const gchar *view_ui_description =
 "        <menuitem action=\"SmartHomeEndAfter\"/>"
 "        <menuitem action=\"SmartHomeEndAlways\"/>"
 "      </menu>"
+"      <separator/>"
+"      <menuitem action=\"ForwardString\"/>"
+"      <menuitem action=\"BackwardString\"/>"
 "    </menu>"
 "  </menubar>"
 "</ui>";
@@ -619,6 +633,61 @@ new_view_cb (GtkAction *action, gpointer user_data)
 	gtk_widget_show (window);
 }
 
+static void
+forward_string_cb (GtkAction *action,
+		   gpointer   user_data)
+{
+	GtkSourceBuffer *buffer;
+	GtkSourceView *view;
+	GtkTextIter iter;
+	GtkTextMark *insert;
+
+	g_return_if_fail (GTK_IS_SOURCE_VIEW (user_data));
+
+	view = GTK_SOURCE_VIEW (user_data);
+	buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+	insert = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer));
+
+	gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer),
+	                                  &iter,
+	                                  insert);
+
+	if (gtk_source_buffer_iter_forward_to_context_class_toggle (buffer,
+	                                                            &iter,
+	                                                            "string"))
+	{
+		gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (buffer), &iter);
+		gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (view), insert);
+	}
+}
+
+static void
+backward_string_cb (GtkAction *action,
+		    gpointer   user_data)
+{
+	GtkSourceBuffer *buffer;
+	GtkSourceView *view;
+	GtkTextIter iter;
+	GtkTextMark *insert;
+
+	g_return_if_fail (GTK_IS_SOURCE_VIEW (user_data));
+
+	view = GTK_SOURCE_VIEW (user_data);
+	buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+	insert = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer));
+
+	gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer),
+	                                  &iter,
+	                                  insert);
+
+	if (gtk_source_buffer_iter_backward_to_context_class_toggle (buffer,
+	                                                             &iter,
+	                                                             "string"))
+	{
+		gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (buffer), &iter);
+		gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (view), insert);
+	}
+}
 
 /* Buffer action callbacks ------------------------------------------------------------ */
 
@@ -1030,6 +1099,9 @@ update_cursor_position (GtkTextBuffer *buffer, gpointer user_data)
 	GtkTextIter iter, start;
 	GtkSourceView *view;
 	GtkLabel *pos_label;
+	gchar **classes;
+	gchar **classes_ptr;
+	GString *str;
 
 	g_return_if_fail (GTK_IS_SOURCE_VIEW (user_data));
 
@@ -1060,9 +1132,30 @@ update_cursor_position (GtkTextBuffer *buffer, gpointer user_data)
 		gtk_text_iter_forward_char (&start);
 	}
 
-	msg = g_strdup_printf ("char: %d, line: %d, column: %d", chars, row, col);
+	classes = gtk_source_buffer_get_context_classes_at_iter (GTK_SOURCE_BUFFER (buffer),
+	                                                         &iter);
+
+	str = g_string_new ("");
+	classes_ptr = classes;
+
+	while (classes_ptr && *classes_ptr)
+	{
+		if (classes_ptr != classes)
+		{
+			g_string_append (str, ", ");
+		}
+
+		g_string_append_printf (str, "%s", *classes_ptr);
+		++classes_ptr;
+	}
+
+	g_strfreev (classes);
+
+	msg = g_strdup_printf ("char: %d, line: %d, column: %d, classes: %s", chars, row, col, str->str);
 	gtk_label_set_text (pos_label, msg);
-      	g_free (msg);
+
+	g_free (msg);
+	g_string_free (str, TRUE);
 }
 
 static void



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